renderer.test.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. import _ from 'lodash';
  2. import TableModel from 'app/core/table_model';
  3. import { TableRenderer } from '../renderer';
  4. import { getColorDefinitionByName } from '@grafana/ui';
  5. describe('when rendering table', () => {
  6. const SemiDarkOrange = getColorDefinitionByName('semi-dark-orange');
  7. describe('given 13 columns', () => {
  8. const table = new TableModel();
  9. table.columns = [
  10. { text: 'Time' },
  11. { text: 'Value' },
  12. { text: 'Colored' },
  13. { text: 'Undefined' },
  14. { text: 'String' },
  15. { text: 'United', unit: 'bps' },
  16. { text: 'Sanitized' },
  17. { text: 'Link' },
  18. { text: 'Array' },
  19. { text: 'Mapping' },
  20. { text: 'RangeMapping' },
  21. { text: 'MappingColored' },
  22. { text: 'RangeMappingColored' },
  23. ];
  24. table.rows = [
  25. [1388556366666, 1230, 40, undefined, '', '', 'my.host.com', 'host1', ['value1', 'value2'], 1, 2, 1, 2],
  26. ];
  27. const panel = {
  28. pageSize: 10,
  29. styles: [
  30. {
  31. pattern: 'Time',
  32. type: 'date',
  33. format: 'LLL',
  34. alias: 'Timestamp',
  35. },
  36. {
  37. pattern: '/(Val)ue/',
  38. type: 'number',
  39. unit: 'ms',
  40. decimals: 3,
  41. alias: '$1',
  42. },
  43. {
  44. pattern: 'Colored',
  45. type: 'number',
  46. unit: 'none',
  47. decimals: 1,
  48. colorMode: 'value',
  49. thresholds: [50, 80],
  50. colors: ['#00ff00', SemiDarkOrange.name, 'rgb(1,0,0)'],
  51. },
  52. {
  53. pattern: 'String',
  54. type: 'string',
  55. },
  56. {
  57. pattern: 'String',
  58. type: 'string',
  59. },
  60. {
  61. pattern: 'United',
  62. type: 'number',
  63. unit: 'ms',
  64. decimals: 2,
  65. },
  66. {
  67. pattern: 'Sanitized',
  68. type: 'string',
  69. sanitize: true,
  70. },
  71. {
  72. pattern: 'Link',
  73. type: 'string',
  74. link: true,
  75. linkUrl: '/dashboard?param=$__cell&param_1=$__cell_1&param_2=$__cell_2',
  76. linkTooltip: '$__cell $__cell_1 $__cell_6',
  77. linkTargetBlank: true,
  78. },
  79. {
  80. pattern: 'Array',
  81. type: 'number',
  82. unit: 'ms',
  83. decimals: 3,
  84. },
  85. {
  86. pattern: 'Mapping',
  87. type: 'string',
  88. mappingType: 1,
  89. valueMaps: [
  90. {
  91. value: '1',
  92. text: 'on',
  93. },
  94. {
  95. value: '0',
  96. text: 'off',
  97. },
  98. {
  99. value: 'HELLO WORLD',
  100. text: 'HELLO GRAFANA',
  101. },
  102. {
  103. value: 'value1, value2',
  104. text: 'value3, value4',
  105. },
  106. ],
  107. },
  108. {
  109. pattern: 'RangeMapping',
  110. type: 'string',
  111. mappingType: 2,
  112. rangeMaps: [
  113. {
  114. from: '1',
  115. to: '3',
  116. text: 'on',
  117. },
  118. {
  119. from: '3',
  120. to: '6',
  121. text: 'off',
  122. },
  123. ],
  124. },
  125. {
  126. pattern: 'MappingColored',
  127. type: 'string',
  128. mappingType: 1,
  129. valueMaps: [
  130. {
  131. value: '1',
  132. text: 'on',
  133. },
  134. {
  135. value: '0',
  136. text: 'off',
  137. },
  138. ],
  139. colorMode: 'value',
  140. thresholds: [1, 2],
  141. colors: ['#00ff00', SemiDarkOrange.name, 'rgb(1,0,0)'],
  142. },
  143. {
  144. pattern: 'RangeMappingColored',
  145. type: 'string',
  146. mappingType: 2,
  147. rangeMaps: [
  148. {
  149. from: '1',
  150. to: '3',
  151. text: 'on',
  152. },
  153. {
  154. from: '3',
  155. to: '6',
  156. text: 'off',
  157. },
  158. ],
  159. colorMode: 'value',
  160. thresholds: [2, 5],
  161. colors: ['#00ff00', SemiDarkOrange.name, 'rgb(1,0,0)'],
  162. },
  163. ],
  164. };
  165. const sanitize = value => {
  166. return 'sanitized';
  167. };
  168. const templateSrv = {
  169. replace: (value, scopedVars) => {
  170. if (scopedVars) {
  171. // For testing variables replacement in link
  172. _.each(scopedVars, (val, key) => {
  173. value = value.replace('$' + key, val.value);
  174. });
  175. }
  176. return value;
  177. },
  178. };
  179. const renderer = new TableRenderer(panel, table, 'utc', sanitize, templateSrv);
  180. it('time column should be formated', () => {
  181. const html = renderer.renderCell(0, 0, 1388556366666);
  182. expect(html).toBe('<td>2014-01-01T06:06:06Z</td>');
  183. });
  184. it('time column with epoch as string should be formatted', () => {
  185. const html = renderer.renderCell(0, 0, '1388556366666');
  186. expect(html).toBe('<td>2014-01-01T06:06:06Z</td>');
  187. });
  188. it('time column with RFC2822 date as string should be formatted', () => {
  189. const html = renderer.renderCell(0, 0, 'Sat, 01 Dec 2018 01:00:00 GMT');
  190. expect(html).toBe('<td>2018-12-01T01:00:00Z</td>');
  191. });
  192. it('time column with ISO date as string should be formatted', () => {
  193. const html = renderer.renderCell(0, 0, '2018-12-01T01:00:00Z');
  194. expect(html).toBe('<td>2018-12-01T01:00:00Z</td>');
  195. });
  196. it('undefined time column should be rendered as -', () => {
  197. const html = renderer.renderCell(0, 0, undefined);
  198. expect(html).toBe('<td>-</td>');
  199. });
  200. it('null time column should be rendered as -', () => {
  201. const html = renderer.renderCell(0, 0, null);
  202. expect(html).toBe('<td>-</td>');
  203. });
  204. it('number column with unit specified should ignore style unit', () => {
  205. const html = renderer.renderCell(5, 0, 1230);
  206. expect(html).toBe('<td>1.23 kbps</td>');
  207. });
  208. it('number column should be formated', () => {
  209. const html = renderer.renderCell(1, 0, 1230);
  210. expect(html).toBe('<td>1.230 s</td>');
  211. });
  212. it('number style should ignore string values', () => {
  213. const html = renderer.renderCell(1, 0, 'asd');
  214. expect(html).toBe('<td>asd</td>');
  215. });
  216. it('colored cell should have style (handles HEX color values)', () => {
  217. const html = renderer.renderCell(2, 0, 40);
  218. expect(html).toBe('<td style="color:#00ff00">40.0</td>');
  219. });
  220. it('colored cell should have style (handles named color values', () => {
  221. const html = renderer.renderCell(2, 0, 55);
  222. expect(html).toBe(`<td style="color:${SemiDarkOrange.variants.dark}">55.0</td>`);
  223. });
  224. it('colored cell should have style handles(rgb color values)', () => {
  225. const html = renderer.renderCell(2, 0, 85);
  226. expect(html).toBe('<td style="color:rgb(1,0,0)">85.0</td>');
  227. });
  228. it('unformated undefined should be rendered as string', () => {
  229. const html = renderer.renderCell(3, 0, 'value');
  230. expect(html).toBe('<td>value</td>');
  231. });
  232. it('string style with escape html should return escaped html', () => {
  233. const html = renderer.renderCell(4, 0, '&breaking <br /> the <br /> row');
  234. expect(html).toBe('<td>&amp;breaking &lt;br /&gt; the &lt;br /&gt; row</td>');
  235. });
  236. it('undefined formater should return escaped html', () => {
  237. const html = renderer.renderCell(3, 0, '&breaking <br /> the <br /> row');
  238. expect(html).toBe('<td>&amp;breaking &lt;br /&gt; the &lt;br /&gt; row</td>');
  239. });
  240. it('undefined value should render as -', () => {
  241. const html = renderer.renderCell(3, 0, undefined);
  242. expect(html).toBe('<td></td>');
  243. });
  244. it('sanitized value should render as', () => {
  245. const html = renderer.renderCell(6, 0, 'text <a href="http://google.com">link</a>');
  246. expect(html).toBe('<td>sanitized</td>');
  247. });
  248. it('Time column title should be Timestamp', () => {
  249. expect(table.columns[0].title).toBe('Timestamp');
  250. });
  251. it('Value column title should be Val', () => {
  252. expect(table.columns[1].title).toBe('Val');
  253. });
  254. it('Colored column title should be Colored', () => {
  255. expect(table.columns[2].title).toBe('Colored');
  256. });
  257. it('link should render as', () => {
  258. const html = renderer.renderCell(7, 0, 'host1');
  259. const expectedHtml = `
  260. <td class="table-panel-cell-link">
  261. <a href="/dashboard?param=host1&param_1=1230&param_2=40"
  262. target="_blank" data-link-tooltip data-original-title="host1 1230 my.host.com" data-placement="right">
  263. host1
  264. </a>
  265. </td>
  266. `;
  267. expect(normalize(html)).toBe(normalize(expectedHtml));
  268. });
  269. it('Array column should not use number as formatter', () => {
  270. const html = renderer.renderCell(8, 0, ['value1', 'value2']);
  271. expect(html).toBe('<td>value1, value2</td>');
  272. });
  273. it('numeric value should be mapped to text', () => {
  274. const html = renderer.renderCell(9, 0, 1);
  275. expect(html).toBe('<td>on</td>');
  276. });
  277. it('string numeric value should be mapped to text', () => {
  278. const html = renderer.renderCell(9, 0, '0');
  279. expect(html).toBe('<td>off</td>');
  280. });
  281. it('string value should be mapped to text', () => {
  282. const html = renderer.renderCell(9, 0, 'HELLO WORLD');
  283. expect(html).toBe('<td>HELLO GRAFANA</td>');
  284. });
  285. it('array column value should be mapped to text', () => {
  286. const html = renderer.renderCell(9, 0, ['value1', 'value2']);
  287. expect(html).toBe('<td>value3, value4</td>');
  288. });
  289. it('value should be mapped to text (range)', () => {
  290. const html = renderer.renderCell(10, 0, 2);
  291. expect(html).toBe('<td>on</td>');
  292. });
  293. it('value should be mapped to text (range)', () => {
  294. const html = renderer.renderCell(10, 0, 5);
  295. expect(html).toBe('<td>off</td>');
  296. });
  297. it('array column value should not be mapped to text', () => {
  298. const html = renderer.renderCell(10, 0, ['value1', 'value2']);
  299. expect(html).toBe('<td>value1, value2</td>');
  300. });
  301. it('value should be mapped to text and colored cell should have style', () => {
  302. const html = renderer.renderCell(11, 0, 1);
  303. expect(html).toBe(`<td style="color:${SemiDarkOrange.variants.dark}">on</td>`);
  304. });
  305. it('value should be mapped to text and colored cell should have style', () => {
  306. const html = renderer.renderCell(11, 0, '1');
  307. expect(html).toBe(`<td style="color:${SemiDarkOrange.variants.dark}">on</td>`);
  308. });
  309. it('value should be mapped to text and colored cell should have style', () => {
  310. const html = renderer.renderCell(11, 0, 0);
  311. expect(html).toBe('<td style="color:#00ff00">off</td>');
  312. });
  313. it('value should be mapped to text and colored cell should have style', () => {
  314. const html = renderer.renderCell(11, 0, '0');
  315. expect(html).toBe('<td style="color:#00ff00">off</td>');
  316. });
  317. it('value should be mapped to text and colored cell should have style', () => {
  318. const html = renderer.renderCell(11, 0, '2.1');
  319. expect(html).toBe('<td style="color:rgb(1,0,0)">2.1</td>');
  320. });
  321. it('value should be mapped to text (range) and colored cell should have style', () => {
  322. const html = renderer.renderCell(12, 0, 0);
  323. expect(html).toBe('<td style="color:#00ff00">0</td>');
  324. });
  325. it('value should be mapped to text (range) and colored cell should have style', () => {
  326. const html = renderer.renderCell(12, 0, 1);
  327. expect(html).toBe('<td style="color:#00ff00">on</td>');
  328. });
  329. it('value should be mapped to text (range) and colored cell should have style', () => {
  330. const html = renderer.renderCell(12, 0, 4);
  331. expect(html).toBe(`<td style="color:${SemiDarkOrange.variants.dark}">off</td>`);
  332. });
  333. it('value should be mapped to text (range) and colored cell should have style', () => {
  334. const html = renderer.renderCell(12, 0, '7.1');
  335. expect(html).toBe('<td style="color:rgb(1,0,0)">7.1</td>');
  336. });
  337. });
  338. });
  339. function normalize(str) {
  340. return str.replace(/\s+/gm, ' ').trim();
  341. }