DashboardMigrator.test.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  1. import _ from 'lodash';
  2. import { DashboardModel } from '../state/DashboardModel';
  3. import { PanelModel } from '../state/PanelModel';
  4. import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN } from 'app/core/constants';
  5. import { expect } from 'test/lib/common';
  6. import { DataLinkBuiltInVars } from '@grafana/ui';
  7. jest.mock('app/core/services/context_srv', () => ({}));
  8. describe('DashboardModel', () => {
  9. describe('when creating dashboard with old schema', () => {
  10. let model: any;
  11. let graph: any;
  12. let singlestat: any;
  13. let table: any;
  14. beforeEach(() => {
  15. model = new DashboardModel({
  16. services: {
  17. filter: { time: { from: 'now-1d', to: 'now' }, list: [{}] },
  18. },
  19. pulldowns: [
  20. { type: 'filtering', enable: true },
  21. { type: 'annotations', enable: true, annotations: [{ name: 'old' }] },
  22. ],
  23. panels: [
  24. {
  25. type: 'graph',
  26. legend: true,
  27. aliasYAxis: { test: 2 },
  28. y_formats: ['kbyte', 'ms'],
  29. grid: {
  30. min: 1,
  31. max: 10,
  32. rightMin: 5,
  33. rightMax: 15,
  34. leftLogBase: 1,
  35. rightLogBase: 2,
  36. threshold1: 200,
  37. threshold2: 400,
  38. threshold1Color: 'yellow',
  39. threshold2Color: 'red',
  40. },
  41. leftYAxisLabel: 'left label',
  42. targets: [{ refId: 'A' }, {}],
  43. },
  44. {
  45. type: 'singlestat',
  46. legend: true,
  47. thresholds: '10,20,30',
  48. aliasYAxis: { test: 2 },
  49. grid: { min: 1, max: 10 },
  50. targets: [{ refId: 'A' }, {}],
  51. },
  52. {
  53. type: 'table',
  54. legend: true,
  55. styles: [{ thresholds: ['10', '20', '30'] }, { thresholds: ['100', '200', '300'] }],
  56. targets: [{ refId: 'A' }, {}],
  57. },
  58. ],
  59. });
  60. graph = model.panels[0];
  61. singlestat = model.panels[1];
  62. table = model.panels[2];
  63. });
  64. it('should have title', () => {
  65. expect(model.title).toBe('No Title');
  66. });
  67. it('should have panel id', () => {
  68. expect(graph.id).toBe(1);
  69. });
  70. it('should move time and filtering list', () => {
  71. expect(model.time.from).toBe('now-1d');
  72. expect(model.templating.list[0].allFormat).toBe('glob');
  73. });
  74. it('graphite panel should change name too graph', () => {
  75. expect(graph.type).toBe('graph');
  76. });
  77. it('single stat panel should have two thresholds', () => {
  78. expect(singlestat.thresholds).toBe('20,30');
  79. });
  80. it('queries without refId should get it', () => {
  81. expect(graph.targets[1].refId).toBe('B');
  82. });
  83. it('update legend setting', () => {
  84. expect(graph.legend.show).toBe(true);
  85. });
  86. it('move aliasYAxis to series override', () => {
  87. expect(graph.seriesOverrides[0].alias).toBe('test');
  88. expect(graph.seriesOverrides[0].yaxis).toBe(2);
  89. });
  90. it('should move pulldowns to new schema', () => {
  91. expect(model.annotations.list[1].name).toBe('old');
  92. });
  93. it('table panel should only have two thresholds values', () => {
  94. expect(table.styles[0].thresholds[0]).toBe('20');
  95. expect(table.styles[0].thresholds[1]).toBe('30');
  96. expect(table.styles[1].thresholds[0]).toBe('200');
  97. expect(table.styles[1].thresholds[1]).toBe('300');
  98. });
  99. it('graph grid to yaxes options', () => {
  100. expect(graph.yaxes[0].min).toBe(1);
  101. expect(graph.yaxes[0].max).toBe(10);
  102. expect(graph.yaxes[0].format).toBe('kbyte');
  103. expect(graph.yaxes[0].label).toBe('left label');
  104. expect(graph.yaxes[0].logBase).toBe(1);
  105. expect(graph.yaxes[1].min).toBe(5);
  106. expect(graph.yaxes[1].max).toBe(15);
  107. expect(graph.yaxes[1].format).toBe('ms');
  108. expect(graph.yaxes[1].logBase).toBe(2);
  109. expect(graph.grid.rightMax).toBe(undefined);
  110. expect(graph.grid.rightLogBase).toBe(undefined);
  111. expect(graph.y_formats).toBe(undefined);
  112. });
  113. it('dashboard schema version should be set to latest', () => {
  114. expect(model.schemaVersion).toBe(20);
  115. });
  116. it('graph thresholds should be migrated', () => {
  117. expect(graph.thresholds.length).toBe(2);
  118. expect(graph.thresholds[0].op).toBe('gt');
  119. expect(graph.thresholds[0].value).toBe(200);
  120. expect(graph.thresholds[0].fillColor).toBe('yellow');
  121. expect(graph.thresholds[1].value).toBe(400);
  122. expect(graph.thresholds[1].fillColor).toBe('red');
  123. });
  124. });
  125. describe('when migrating to the grid layout', () => {
  126. let model: any;
  127. beforeEach(() => {
  128. model = {
  129. rows: [],
  130. };
  131. });
  132. it('should create proper grid', () => {
  133. model.rows = [createRow({ collapse: false, height: 8 }, [[6], [6]])];
  134. const dashboard = new DashboardModel(model);
  135. const panelGridPos = getGridPositions(dashboard);
  136. const expectedGrid = [{ x: 0, y: 0, w: 12, h: 8 }, { x: 12, y: 0, w: 12, h: 8 }];
  137. expect(panelGridPos).toEqual(expectedGrid);
  138. });
  139. it('should add special "row" panel if row is collapsed', () => {
  140. model.rows = [createRow({ collapse: true, height: 8 }, [[6], [6]]), createRow({ height: 8 }, [[12]])];
  141. const dashboard = new DashboardModel(model);
  142. const panelGridPos = getGridPositions(dashboard);
  143. const expectedGrid = [
  144. { x: 0, y: 0, w: 24, h: 8 }, // row
  145. { x: 0, y: 1, w: 24, h: 8 }, // row
  146. { x: 0, y: 2, w: 24, h: 8 },
  147. ];
  148. expect(panelGridPos).toEqual(expectedGrid);
  149. });
  150. it('should add special "row" panel if row has visible title', () => {
  151. model.rows = [
  152. createRow({ showTitle: true, title: 'Row', height: 8 }, [[6], [6]]),
  153. createRow({ height: 8 }, [[12]]),
  154. ];
  155. const dashboard = new DashboardModel(model);
  156. const panelGridPos = getGridPositions(dashboard);
  157. const expectedGrid = [
  158. { x: 0, y: 0, w: 24, h: 8 }, // row
  159. { x: 0, y: 1, w: 12, h: 8 },
  160. { x: 12, y: 1, w: 12, h: 8 },
  161. { x: 0, y: 9, w: 24, h: 8 }, // row
  162. { x: 0, y: 10, w: 24, h: 8 },
  163. ];
  164. expect(panelGridPos).toEqual(expectedGrid);
  165. });
  166. it('should not add "row" panel if row has not visible title or not collapsed', () => {
  167. model.rows = [
  168. createRow({ collapse: true, height: 8 }, [[12]]),
  169. createRow({ height: 8 }, [[12]]),
  170. createRow({ height: 8 }, [[12], [6], [6]]),
  171. createRow({ collapse: true, height: 8 }, [[12]]),
  172. ];
  173. const dashboard = new DashboardModel(model);
  174. const panelGridPos = getGridPositions(dashboard);
  175. const expectedGrid = [
  176. { x: 0, y: 0, w: 24, h: 8 }, // row
  177. { x: 0, y: 1, w: 24, h: 8 }, // row
  178. { x: 0, y: 2, w: 24, h: 8 },
  179. { x: 0, y: 10, w: 24, h: 8 }, // row
  180. { x: 0, y: 11, w: 24, h: 8 },
  181. { x: 0, y: 19, w: 12, h: 8 },
  182. { x: 12, y: 19, w: 12, h: 8 },
  183. { x: 0, y: 27, w: 24, h: 8 }, // row
  184. ];
  185. expect(panelGridPos).toEqual(expectedGrid);
  186. });
  187. it('should add all rows if even one collapsed or titled row is present', () => {
  188. model.rows = [createRow({ collapse: true, height: 8 }, [[6], [6]]), createRow({ height: 8 }, [[12]])];
  189. const dashboard = new DashboardModel(model);
  190. const panelGridPos = getGridPositions(dashboard);
  191. const expectedGrid = [
  192. { x: 0, y: 0, w: 24, h: 8 }, // row
  193. { x: 0, y: 1, w: 24, h: 8 }, // row
  194. { x: 0, y: 2, w: 24, h: 8 },
  195. ];
  196. expect(panelGridPos).toEqual(expectedGrid);
  197. });
  198. it('should properly place panels with fixed height', () => {
  199. model.rows = [
  200. createRow({ height: 6 }, [[6], [6, 3], [6, 3]]),
  201. createRow({ height: 6 }, [[4], [4], [4, 3], [4, 3]]),
  202. ];
  203. const dashboard = new DashboardModel(model);
  204. const panelGridPos = getGridPositions(dashboard);
  205. const expectedGrid = [
  206. { x: 0, y: 0, w: 12, h: 6 },
  207. { x: 12, y: 0, w: 12, h: 3 },
  208. { x: 12, y: 3, w: 12, h: 3 },
  209. { x: 0, y: 6, w: 8, h: 6 },
  210. { x: 8, y: 6, w: 8, h: 6 },
  211. { x: 16, y: 6, w: 8, h: 3 },
  212. { x: 16, y: 9, w: 8, h: 3 },
  213. ];
  214. expect(panelGridPos).toEqual(expectedGrid);
  215. });
  216. it('should place panel to the right side of panel having bigger height', () => {
  217. model.rows = [createRow({ height: 6 }, [[4], [2, 3], [4, 6], [2, 3], [2, 3]])];
  218. const dashboard = new DashboardModel(model);
  219. const panelGridPos = getGridPositions(dashboard);
  220. const expectedGrid = [
  221. { x: 0, y: 0, w: 8, h: 6 },
  222. { x: 8, y: 0, w: 4, h: 3 },
  223. { x: 12, y: 0, w: 8, h: 6 },
  224. { x: 20, y: 0, w: 4, h: 3 },
  225. { x: 20, y: 3, w: 4, h: 3 },
  226. ];
  227. expect(panelGridPos).toEqual(expectedGrid);
  228. });
  229. it('should fill current row if it possible', () => {
  230. model.rows = [createRow({ height: 9 }, [[4], [2, 3], [4, 6], [2, 3], [2, 3], [8, 3]])];
  231. const dashboard = new DashboardModel(model);
  232. const panelGridPos = getGridPositions(dashboard);
  233. const expectedGrid = [
  234. { x: 0, y: 0, w: 8, h: 9 },
  235. { x: 8, y: 0, w: 4, h: 3 },
  236. { x: 12, y: 0, w: 8, h: 6 },
  237. { x: 20, y: 0, w: 4, h: 3 },
  238. { x: 20, y: 3, w: 4, h: 3 },
  239. { x: 8, y: 6, w: 16, h: 3 },
  240. ];
  241. expect(panelGridPos).toEqual(expectedGrid);
  242. });
  243. it('should fill current row if it possible (2)', () => {
  244. model.rows = [createRow({ height: 8 }, [[4], [2, 3], [4, 6], [2, 3], [2, 3], [8, 3]])];
  245. const dashboard = new DashboardModel(model);
  246. const panelGridPos = getGridPositions(dashboard);
  247. const expectedGrid = [
  248. { x: 0, y: 0, w: 8, h: 8 },
  249. { x: 8, y: 0, w: 4, h: 3 },
  250. { x: 12, y: 0, w: 8, h: 6 },
  251. { x: 20, y: 0, w: 4, h: 3 },
  252. { x: 20, y: 3, w: 4, h: 3 },
  253. { x: 8, y: 6, w: 16, h: 3 },
  254. ];
  255. expect(panelGridPos).toEqual(expectedGrid);
  256. });
  257. it('should fill current row if panel height more than row height', () => {
  258. model.rows = [createRow({ height: 6 }, [[4], [2, 3], [4, 8], [2, 3], [2, 3]])];
  259. const dashboard = new DashboardModel(model);
  260. const panelGridPos = getGridPositions(dashboard);
  261. const expectedGrid = [
  262. { x: 0, y: 0, w: 8, h: 6 },
  263. { x: 8, y: 0, w: 4, h: 3 },
  264. { x: 12, y: 0, w: 8, h: 8 },
  265. { x: 20, y: 0, w: 4, h: 3 },
  266. { x: 20, y: 3, w: 4, h: 3 },
  267. ];
  268. expect(panelGridPos).toEqual(expectedGrid);
  269. });
  270. it('should wrap panels to multiple rows', () => {
  271. model.rows = [createRow({ height: 6 }, [[6], [6], [12], [6], [3], [3]])];
  272. const dashboard = new DashboardModel(model);
  273. const panelGridPos = getGridPositions(dashboard);
  274. const expectedGrid = [
  275. { x: 0, y: 0, w: 12, h: 6 },
  276. { x: 12, y: 0, w: 12, h: 6 },
  277. { x: 0, y: 6, w: 24, h: 6 },
  278. { x: 0, y: 12, w: 12, h: 6 },
  279. { x: 12, y: 12, w: 6, h: 6 },
  280. { x: 18, y: 12, w: 6, h: 6 },
  281. ];
  282. expect(panelGridPos).toEqual(expectedGrid);
  283. });
  284. it('should add repeated row if repeat set', () => {
  285. model.rows = [
  286. createRow({ showTitle: true, title: 'Row', height: 8, repeat: 'server' }, [[6]]),
  287. createRow({ height: 8 }, [[12]]),
  288. ];
  289. const dashboard = new DashboardModel(model);
  290. const panelGridPos = getGridPositions(dashboard);
  291. const expectedGrid = [
  292. { x: 0, y: 0, w: 24, h: 8 },
  293. { x: 0, y: 1, w: 12, h: 8 },
  294. { x: 0, y: 9, w: 24, h: 8 },
  295. { x: 0, y: 10, w: 24, h: 8 },
  296. ];
  297. expect(panelGridPos).toEqual(expectedGrid);
  298. expect(dashboard.panels[0].repeat).toBe('server');
  299. expect(dashboard.panels[1].repeat).toBeUndefined();
  300. expect(dashboard.panels[2].repeat).toBeUndefined();
  301. expect(dashboard.panels[3].repeat).toBeUndefined();
  302. });
  303. it('should ignore repeated row', () => {
  304. model.rows = [
  305. createRow({ showTitle: true, title: 'Row1', height: 8, repeat: 'server' }, [[6]]),
  306. createRow(
  307. {
  308. showTitle: true,
  309. title: 'Row2',
  310. height: 8,
  311. repeatIteration: 12313,
  312. repeatRowId: 1,
  313. },
  314. [[6]]
  315. ),
  316. ];
  317. const dashboard = new DashboardModel(model);
  318. expect(dashboard.panels[0].repeat).toBe('server');
  319. expect(dashboard.panels.length).toBe(2);
  320. });
  321. it('should assign id', () => {
  322. model.rows = [createRow({ collapse: true, height: 8 }, [[6], [6]])];
  323. model.rows[0].panels[0] = {};
  324. const dashboard = new DashboardModel(model);
  325. expect(dashboard.panels[0].id).toBe(1);
  326. });
  327. });
  328. describe('when migrating from minSpan to maxPerRow', () => {
  329. it('maxPerRow should be correct', () => {
  330. const model = {
  331. panels: [{ minSpan: 8 }],
  332. };
  333. const dashboard = new DashboardModel(model);
  334. expect(dashboard.panels[0].maxPerRow).toBe(3);
  335. });
  336. });
  337. describe('when migrating panel links', () => {
  338. let model: any;
  339. beforeEach(() => {
  340. model = new DashboardModel({
  341. panels: [
  342. {
  343. links: [
  344. {
  345. url: 'http://mylink.com',
  346. keepTime: true,
  347. title: 'test',
  348. },
  349. {
  350. url: 'http://mylink.com?existingParam',
  351. params: 'customParam',
  352. title: 'test',
  353. },
  354. {
  355. url: 'http://mylink.com?existingParam',
  356. includeVars: true,
  357. title: 'test',
  358. },
  359. {
  360. dashboard: 'my other dashboard',
  361. title: 'test',
  362. },
  363. {
  364. dashUri: '',
  365. title: 'test',
  366. },
  367. {
  368. type: 'dashboard',
  369. keepTime: true,
  370. },
  371. ],
  372. },
  373. ],
  374. });
  375. });
  376. it('should add keepTime as variable', () => {
  377. expect(model.panels[0].links[0].url).toBe(`http://mylink.com?$${DataLinkBuiltInVars.keepTime}`);
  378. });
  379. it('should add params to url', () => {
  380. expect(model.panels[0].links[1].url).toBe('http://mylink.com?existingParam&customParam');
  381. });
  382. it('should add includeVars to url', () => {
  383. expect(model.panels[0].links[2].url).toBe(`http://mylink.com?existingParam&$${DataLinkBuiltInVars.includeVars}`);
  384. });
  385. it('should slugify dashboard name', () => {
  386. expect(model.panels[0].links[3].url).toBe(`/dashboard/db/my-other-dashboard`);
  387. });
  388. });
  389. describe('when migrating variables', () => {
  390. let model: any;
  391. beforeEach(() => {
  392. model = new DashboardModel({
  393. panels: [
  394. {
  395. //graph panel
  396. options: {
  397. dataLinks: [
  398. {
  399. url: 'http://mylink.com?series=${__series_name}',
  400. },
  401. {
  402. url: 'http://mylink.com?series=${__value_time}',
  403. },
  404. ],
  405. },
  406. },
  407. {
  408. // panel with field options
  409. options: {
  410. fieldOptions: {
  411. defaults: {
  412. links: [
  413. {
  414. url: 'http://mylink.com?series=${__series_name}',
  415. },
  416. {
  417. url: 'http://mylink.com?series=${__value_time}',
  418. },
  419. ],
  420. title: '$__cell_0 * $__field_name * $__series_name',
  421. },
  422. },
  423. },
  424. },
  425. ],
  426. });
  427. });
  428. describe('data links', () => {
  429. it('should replace __series_name variable with __series.name', () => {
  430. expect(model.panels[0].options.dataLinks[0].url).toBe('http://mylink.com?series=${__series.name}');
  431. expect(model.panels[1].options.fieldOptions.defaults.links[0].url).toBe(
  432. 'http://mylink.com?series=${__series.name}'
  433. );
  434. });
  435. it('should replace __value_time variable with __value.time', () => {
  436. expect(model.panels[0].options.dataLinks[1].url).toBe('http://mylink.com?series=${__value.time}');
  437. expect(model.panels[1].options.fieldOptions.defaults.links[1].url).toBe(
  438. 'http://mylink.com?series=${__value.time}'
  439. );
  440. });
  441. });
  442. describe('field display', () => {
  443. it('should replace __series_name and __field_name variables with new syntax', () => {
  444. expect(model.panels[1].options.fieldOptions.defaults.title).toBe(
  445. '$__cell_0 * ${__field.name} * ${__series.name}'
  446. );
  447. });
  448. });
  449. });
  450. });
  451. function createRow(options: any, panelDescriptions: any[]) {
  452. const PANEL_HEIGHT_STEP = GRID_CELL_HEIGHT + GRID_CELL_VMARGIN;
  453. const { collapse, showTitle, title, repeat, repeatIteration } = options;
  454. let { height } = options;
  455. height = height * PANEL_HEIGHT_STEP;
  456. const panels: any[] = [];
  457. _.each(panelDescriptions, panelDesc => {
  458. const panel = { span: panelDesc[0] };
  459. if (panelDesc.length > 1) {
  460. //@ts-ignore
  461. panel['height'] = panelDesc[1] * PANEL_HEIGHT_STEP;
  462. }
  463. panels.push(panel);
  464. });
  465. const row = {
  466. collapse,
  467. height,
  468. showTitle,
  469. title,
  470. panels,
  471. repeat,
  472. repeatIteration,
  473. };
  474. return row;
  475. }
  476. function getGridPositions(dashboard: DashboardModel) {
  477. return _.map(dashboard.panels, (panel: PanelModel) => {
  478. return panel.gridPos;
  479. });
  480. }