dashboard_model.jest.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. import _ from 'lodash';
  2. import {DashboardModel} from '../dashboard_model';
  3. import {PanelModel} from '../panel_model';
  4. jest.mock('app/core/services/context_srv', () => ({
  5. }));
  6. describe('DashboardModel', function() {
  7. describe('when creating new dashboard model defaults only', function() {
  8. var model;
  9. beforeEach(function() {
  10. model = new DashboardModel({}, {});
  11. });
  12. it('should have title', function() {
  13. expect(model.title).toBe('No Title');
  14. });
  15. it('should have meta', function() {
  16. expect(model.meta.canSave).toBe(true);
  17. expect(model.meta.canShare).toBe(true);
  18. });
  19. it('should have default properties', function() {
  20. expect(model.panels.length).toBe(0);
  21. });
  22. });
  23. describe('when getting next panel id', function() {
  24. var model;
  25. beforeEach(function() {
  26. model = new DashboardModel({
  27. panels: [{ id: 5 }]
  28. });
  29. });
  30. it('should return max id + 1', function() {
  31. expect(model.getNextPanelId()).toBe(6);
  32. });
  33. });
  34. describe('getSaveModelClone', function() {
  35. it('should sort keys', () => {
  36. var model = new DashboardModel({});
  37. var saveModel = model.getSaveModelClone();
  38. var keys = _.keys(saveModel);
  39. expect(keys[0]).toBe('annotations');
  40. expect(keys[1]).toBe('autoUpdate');
  41. });
  42. });
  43. describe('row and panel manipulation', function() {
  44. var dashboard;
  45. beforeEach(function() {
  46. dashboard = new DashboardModel({});
  47. });
  48. it('adding panel should new up panel model', function() {
  49. dashboard.addPanel({type: 'test', title: 'test'});
  50. expect(dashboard.panels[0] instanceof PanelModel).toBe(true);
  51. });
  52. it('duplicate panel should try to add to the right if there is space', function() {
  53. var panel = {id: 10, gridPos: {x: 0, y: 0, w: 6, h: 2}};
  54. dashboard.addPanel(panel);
  55. dashboard.duplicatePanel(dashboard.panels[0]);
  56. expect(dashboard.panels[1].gridPos).toMatchObject({x: 6, y: 0, h: 2, w: 6});
  57. });
  58. it('duplicate panel should remove repeat data', function() {
  59. var panel = {id: 10, gridPos: {x: 0, y: 0, w: 6, h: 2}, repeat: 'asd', scopedVars: {test: 'asd'}};
  60. dashboard.addPanel(panel);
  61. dashboard.duplicatePanel(dashboard.panels[0]);
  62. expect(dashboard.panels[1].repeat).toBe(undefined);
  63. expect(dashboard.panels[1].scopedVars).toBe(undefined);
  64. });
  65. });
  66. describe('when creating dashboard with old schema', function() {
  67. var model;
  68. var graph;
  69. var singlestat;
  70. var table;
  71. beforeEach(function() {
  72. model = new DashboardModel({
  73. services: { filter: { time: { from: 'now-1d', to: 'now'}, list: [{}] }},
  74. pulldowns: [
  75. {type: 'filtering', enable: true},
  76. {type: 'annotations', enable: true, annotations: [{name: 'old'}]}
  77. ],
  78. panels: [
  79. {
  80. type: 'graph', legend: true, aliasYAxis: { test: 2 },
  81. y_formats: ['kbyte', 'ms'],
  82. grid: {
  83. min: 1,
  84. max: 10,
  85. rightMin: 5,
  86. rightMax: 15,
  87. leftLogBase: 1,
  88. rightLogBase: 2,
  89. threshold1: 200,
  90. threshold2: 400,
  91. threshold1Color: 'yellow',
  92. threshold2Color: 'red',
  93. },
  94. leftYAxisLabel: 'left label',
  95. targets: [{refId: 'A'}, {}],
  96. },
  97. {
  98. type: 'singlestat', legend: true, thresholds: '10,20,30', aliasYAxis: { test: 2 }, grid: { min: 1, max: 10 },
  99. targets: [{refId: 'A'}, {}],
  100. },
  101. {
  102. type: 'table', legend: true, styles: [{ thresholds: ["10", "20", "30"]}, { thresholds: ["100", "200", "300"]}],
  103. targets: [{refId: 'A'}, {}],
  104. }
  105. ]
  106. });
  107. graph = model.panels[0];
  108. singlestat = model.panels[1];
  109. table = model.panels[2];
  110. });
  111. it('should have title', function() {
  112. expect(model.title).toBe('No Title');
  113. });
  114. it('should have panel id', function() {
  115. expect(graph.id).toBe(1);
  116. });
  117. it('should move time and filtering list', function() {
  118. expect(model.time.from).toBe('now-1d');
  119. expect(model.templating.list[0].allFormat).toBe('glob');
  120. });
  121. it('graphite panel should change name too graph', function() {
  122. expect(graph.type).toBe('graph');
  123. });
  124. it('single stat panel should have two thresholds', function() {
  125. expect(singlestat.thresholds).toBe('20,30');
  126. });
  127. it('queries without refId should get it', function() {
  128. expect(graph.targets[1].refId).toBe('B');
  129. });
  130. it('update legend setting', function() {
  131. expect(graph.legend.show).toBe(true);
  132. });
  133. it('move aliasYAxis to series override', function() {
  134. expect(graph.seriesOverrides[0].alias).toBe("test");
  135. expect(graph.seriesOverrides[0].yaxis).toBe(2);
  136. });
  137. it('should move pulldowns to new schema', function() {
  138. expect(model.annotations.list[1].name).toBe('old');
  139. });
  140. it('table panel should only have two thresholds values', function() {
  141. expect(table.styles[0].thresholds[0]).toBe("20");
  142. expect(table.styles[0].thresholds[1]).toBe("30");
  143. expect(table.styles[1].thresholds[0]).toBe("200");
  144. expect(table.styles[1].thresholds[1]).toBe("300");
  145. });
  146. it('graph grid to yaxes options', function() {
  147. expect(graph.yaxes[0].min).toBe(1);
  148. expect(graph.yaxes[0].max).toBe(10);
  149. expect(graph.yaxes[0].format).toBe('kbyte');
  150. expect(graph.yaxes[0].label).toBe('left label');
  151. expect(graph.yaxes[0].logBase).toBe(1);
  152. expect(graph.yaxes[1].min).toBe(5);
  153. expect(graph.yaxes[1].max).toBe(15);
  154. expect(graph.yaxes[1].format).toBe('ms');
  155. expect(graph.yaxes[1].logBase).toBe(2);
  156. expect(graph.grid.rightMax).toBe(undefined);
  157. expect(graph.grid.rightLogBase).toBe(undefined);
  158. expect(graph.y_formats).toBe(undefined);
  159. });
  160. it('dashboard schema version should be set to latest', function() {
  161. expect(model.schemaVersion).toBe(16);
  162. });
  163. it('graph thresholds should be migrated', function() {
  164. expect(graph.thresholds.length).toBe(2);
  165. expect(graph.thresholds[0].op).toBe('gt');
  166. expect(graph.thresholds[0].value).toBe(200);
  167. expect(graph.thresholds[0].fillColor).toBe('yellow');
  168. expect(graph.thresholds[1].value).toBe(400);
  169. expect(graph.thresholds[1].fillColor).toBe('red');
  170. });
  171. });
  172. describe('Given editable false dashboard', function() {
  173. var model;
  174. beforeEach(function() {
  175. model = new DashboardModel({editable: false});
  176. });
  177. it('Should set meta canEdit and canSave to false', function() {
  178. expect(model.meta.canSave).toBe(false);
  179. expect(model.meta.canEdit).toBe(false);
  180. });
  181. it('getSaveModelClone should remove meta', function() {
  182. var clone = model.getSaveModelClone();
  183. expect(clone.meta).toBe(undefined);
  184. });
  185. });
  186. describe('when loading dashboard with old influxdb query schema', function() {
  187. var model;
  188. var target;
  189. beforeEach(function() {
  190. model = new DashboardModel({
  191. panels: [{
  192. type: 'graph',
  193. grid: {},
  194. yaxes: [{}, {}],
  195. targets: [{
  196. "alias": "$tag_datacenter $tag_source $col",
  197. "column": "value",
  198. "measurement": "logins.count",
  199. "fields": [
  200. {
  201. "func": "mean",
  202. "name": "value",
  203. "mathExpr": "*2",
  204. "asExpr": "value"
  205. },
  206. {
  207. "name": "one-minute",
  208. "func": "mean",
  209. "mathExpr": "*3",
  210. "asExpr": "one-minute"
  211. }
  212. ],
  213. "tags": [],
  214. "fill": "previous",
  215. "function": "mean",
  216. "groupBy": [
  217. {
  218. "interval": "auto",
  219. "type": "time"
  220. },
  221. {
  222. "key": "source",
  223. "type": "tag"
  224. },
  225. {
  226. "type": "tag",
  227. "key": "datacenter"
  228. }
  229. ],
  230. }]
  231. }]
  232. });
  233. target = model.panels[0].targets[0];
  234. });
  235. it('should update query schema', function() {
  236. expect(target.fields).toBe(undefined);
  237. expect(target.select.length).toBe(2);
  238. expect(target.select[0].length).toBe(4);
  239. expect(target.select[0][0].type).toBe('field');
  240. expect(target.select[0][1].type).toBe('mean');
  241. expect(target.select[0][2].type).toBe('math');
  242. expect(target.select[0][3].type).toBe('alias');
  243. });
  244. });
  245. describe('when creating dashboard model with missing list for annoations or templating', function() {
  246. var model;
  247. beforeEach(function() {
  248. model = new DashboardModel({
  249. annotations: {
  250. enable: true,
  251. },
  252. templating: {
  253. enable: true
  254. }
  255. });
  256. });
  257. it('should add empty list', function() {
  258. expect(model.annotations.list.length).toBe(1);
  259. expect(model.templating.list.length).toBe(0);
  260. });
  261. it('should add builtin annotation query', function() {
  262. expect(model.annotations.list[0].builtIn).toBe(1);
  263. expect(model.templating.list.length).toBe(0);
  264. });
  265. });
  266. describe('Formatting epoch timestamp when timezone is set as utc', function() {
  267. var dashboard;
  268. beforeEach(function() {
  269. dashboard = new DashboardModel({timezone: 'utc'});
  270. });
  271. it('Should format timestamp with second resolution by default', function() {
  272. expect(dashboard.formatDate(1234567890000)).toBe('2009-02-13 23:31:30');
  273. });
  274. it('Should format timestamp with second resolution even if second format is passed as parameter', function() {
  275. expect(dashboard.formatDate(1234567890007,'YYYY-MM-DD HH:mm:ss')).toBe('2009-02-13 23:31:30');
  276. });
  277. it('Should format timestamp with millisecond resolution if format is passed as parameter', function() {
  278. expect(dashboard.formatDate(1234567890007,'YYYY-MM-DD HH:mm:ss.SSS')).toBe('2009-02-13 23:31:30.007');
  279. });
  280. });
  281. describe('updateSubmenuVisibility with empty lists', function() {
  282. var model;
  283. beforeEach(function() {
  284. model = new DashboardModel({});
  285. model.updateSubmenuVisibility();
  286. });
  287. it('should not enable submmenu', function() {
  288. expect(model.meta.submenuEnabled).toBe(false);
  289. });
  290. });
  291. describe('updateSubmenuVisibility with annotation', function() {
  292. var model;
  293. beforeEach(function() {
  294. model = new DashboardModel({
  295. annotations: {
  296. list: [{}]
  297. }
  298. });
  299. model.updateSubmenuVisibility();
  300. });
  301. it('should enable submmenu', function() {
  302. expect(model.meta.submenuEnabled).toBe(true);
  303. });
  304. });
  305. describe('updateSubmenuVisibility with template var', function() {
  306. var model;
  307. beforeEach(function() {
  308. model = new DashboardModel({
  309. templating: {
  310. list: [{}]
  311. }
  312. });
  313. model.updateSubmenuVisibility();
  314. });
  315. it('should enable submmenu', function() {
  316. expect(model.meta.submenuEnabled).toBe(true);
  317. });
  318. });
  319. describe('updateSubmenuVisibility with hidden template var', function() {
  320. var model;
  321. beforeEach(function() {
  322. model = new DashboardModel({
  323. templating: {
  324. list: [{hide: 2}]
  325. }
  326. });
  327. model.updateSubmenuVisibility();
  328. });
  329. it('should not enable submmenu', function() {
  330. expect(model.meta.submenuEnabled).toBe(false);
  331. });
  332. });
  333. describe('updateSubmenuVisibility with hidden annotation toggle', function() {
  334. var dashboard;
  335. beforeEach(function() {
  336. dashboard = new DashboardModel({
  337. annotations: {
  338. list: [{hide: true}]
  339. }
  340. });
  341. dashboard.updateSubmenuVisibility();
  342. });
  343. it('should not enable submmenu', function() {
  344. expect(dashboard.meta.submenuEnabled).toBe(false);
  345. });
  346. });
  347. describe('given dashboard with panel repeat in horizontal direction', function() {
  348. var dashboard;
  349. beforeEach(function() {
  350. dashboard = new DashboardModel({
  351. panels: [{id: 2, repeat: 'apps', repeatDirection: 'h', gridPos: {x: 0, y: 0, h: 2, w: 24}}],
  352. templating: {
  353. list: [{
  354. name: 'apps',
  355. current: {
  356. text: 'se1, se2, se3',
  357. value: ['se1', 'se2', 'se3']
  358. },
  359. options: [
  360. {text: 'se1', value: 'se1', selected: true},
  361. {text: 'se2', value: 'se2', selected: true},
  362. {text: 'se3', value: 'se3', selected: true},
  363. {text: 'se4', value: 'se4', selected: false}
  364. ]
  365. }]
  366. }
  367. });
  368. dashboard.processRepeats();
  369. });
  370. it('should repeat panel 3 times', function() {
  371. expect(dashboard.panels.length).toBe(3);
  372. });
  373. it('should mark panel repeated', function() {
  374. expect(dashboard.panels[0].repeat).toBe('apps');
  375. expect(dashboard.panels[1].repeatPanelId).toBe(2);
  376. });
  377. it('should set scopedVars on panels', function() {
  378. expect(dashboard.panels[0].scopedVars.apps.value).toBe('se1');
  379. expect(dashboard.panels[1].scopedVars.apps.value).toBe('se2');
  380. expect(dashboard.panels[2].scopedVars.apps.value).toBe('se3');
  381. });
  382. it('should place on first row and adjust width so all fit', function() {
  383. expect(dashboard.panels[0].gridPos).toMatchObject({x: 0, y: 0, h: 2, w: 8});
  384. expect(dashboard.panels[1].gridPos).toMatchObject({x: 8, y: 0, h: 2, w: 8});
  385. expect(dashboard.panels[2].gridPos).toMatchObject({x: 16, y: 0, h: 2, w: 8});
  386. });
  387. describe('After a second iteration', function() {
  388. var repeatedPanelAfterIteration1;
  389. beforeEach(function() {
  390. repeatedPanelAfterIteration1 = dashboard.panels[1];
  391. dashboard.panels[0].fill = 10;
  392. dashboard.processRepeats();
  393. });
  394. it('reused panel should copy properties from source', function() {
  395. expect(dashboard.panels[1].fill).toBe(10);
  396. });
  397. it('should have same panel count', function() {
  398. expect(dashboard.panels.length).toBe(3);
  399. });
  400. });
  401. describe('After a second iteration with different variable', function() {
  402. beforeEach(function() {
  403. dashboard.templating.list.push({
  404. name: 'server',
  405. current: { text: 'se1, se2, se3', value: ['se1']},
  406. options: [{text: 'se1', value: 'se1', selected: true}]
  407. });
  408. dashboard.panels[0].repeat = "server";
  409. dashboard.processRepeats();
  410. });
  411. it('should remove scopedVars value for last variable', function() {
  412. expect(dashboard.panels[0].scopedVars.apps).toBe(undefined);
  413. });
  414. it('should have new variable value in scopedVars', function() {
  415. expect(dashboard.panels[0].scopedVars.server.value).toBe("se1");
  416. });
  417. });
  418. describe('After a second iteration and selected values reduced', function() {
  419. beforeEach(function() {
  420. dashboard.templating.list[0].options[1].selected = false;
  421. dashboard.processRepeats();
  422. });
  423. it('should clean up repeated panel', function() {
  424. expect(dashboard.panels.length).toBe(2);
  425. });
  426. });
  427. describe('After a second iteration and panel repeat is turned off', function() {
  428. beforeEach(function() {
  429. dashboard.panels[0].repeat = null;
  430. dashboard.processRepeats();
  431. });
  432. it('should clean up repeated panel', function() {
  433. expect(dashboard.panels.length).toBe(1);
  434. });
  435. it('should remove scoped vars from reused panel', function() {
  436. expect(dashboard.panels[0].scopedVars).toBe(undefined);
  437. });
  438. });
  439. });
  440. describe('given dashboard with panel repeat in vertical direction', function() {
  441. var dashboard;
  442. beforeEach(function() {
  443. dashboard = new DashboardModel({
  444. panels: [{id: 2, repeat: 'apps', repeatDirection: 'v', gridPos: {x: 5, y: 0, h: 2, w: 8}}],
  445. templating: {
  446. list: [{
  447. name: 'apps',
  448. current: {
  449. text: 'se1, se2, se3',
  450. value: ['se1', 'se2', 'se3']
  451. },
  452. options: [
  453. {text: 'se1', value: 'se1', selected: true},
  454. {text: 'se2', value: 'se2', selected: true},
  455. {text: 'se3', value: 'se3', selected: true},
  456. {text: 'se4', value: 'se4', selected: false}
  457. ]
  458. }]
  459. }
  460. });
  461. dashboard.processRepeats();
  462. });
  463. it('should place on items on top of each other and keep witdh', function() {
  464. expect(dashboard.panels[0].gridPos).toMatchObject({x: 5, y: 0, h: 2, w: 8});
  465. expect(dashboard.panels[1].gridPos).toMatchObject({x: 5, y: 2, h: 2, w: 8});
  466. expect(dashboard.panels[2].gridPos).toMatchObject({x: 5, y: 4, h: 2, w: 8});
  467. });
  468. });
  469. describe('When collapsing row', function() {
  470. var dashboard;
  471. beforeEach(function() {
  472. dashboard = new DashboardModel({
  473. panels: [
  474. {id: 1, type: 'graph', gridPos: {x: 0, y: 0, w: 24, h: 2}},
  475. {id: 2, type: 'row', gridPos: {x: 0, y: 2, w: 24, h: 2}},
  476. {id: 3, type: 'graph', gridPos: {x: 0, y: 4, w: 12, h: 2}},
  477. {id: 4, type: 'graph', gridPos: {x: 12, y: 4, w: 12, h: 2}},
  478. {id: 5, type: 'row', gridPos: {x: 0, y: 6, w: 24, h: 2}},
  479. ],
  480. });
  481. dashboard.toggleRow(dashboard.panels[1]);
  482. });
  483. it('should remove panels and put them inside collapsed row', function() {
  484. expect(dashboard.panels.length).toBe(3);
  485. expect(dashboard.panels[1].panels.length).toBe(2);
  486. });
  487. });
  488. describe('When expanding row', function() {
  489. var dashboard;
  490. beforeEach(function() {
  491. dashboard = new DashboardModel({
  492. panels: [
  493. {id: 1, type: 'graph', gridPos: {x: 0, y: 0, w: 24, h: 6}},
  494. {
  495. id: 2,
  496. type: 'row',
  497. gridPos: {x: 0, y: 6, w: 24, h: 2},
  498. collapsed: true,
  499. panels: [
  500. {id: 3, type: 'graph', gridPos: {x: 0, y: 2, w: 12, h: 2}},
  501. {id: 4, type: 'graph', gridPos: {x: 12, y: 2, w: 12, h: 2}},
  502. ]
  503. },
  504. {id: 5, type: 'graph', gridPos: {x: 0, y: 6, w: 1, h: 1}},
  505. ],
  506. });
  507. dashboard.toggleRow(dashboard.panels[1]);
  508. });
  509. it('should add panels back', function() {
  510. expect(dashboard.panels.length).toBe(5);
  511. });
  512. it('should add them below row in array', function() {
  513. expect(dashboard.panels[2].id).toBe(3);
  514. expect(dashboard.panels[3].id).toBe(4);
  515. });
  516. it('should position them below row', function() {
  517. expect(dashboard.panels[2].gridPos).toMatchObject({x: 0, y: 8, w: 12, h: 2});
  518. });
  519. it('should move panels below down', function() {
  520. expect(dashboard.panels[4].gridPos).toMatchObject({x: 0, y: 10, w: 1, h: 1});
  521. });
  522. });
  523. });