renderer.test.ts 12 KB

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