renderer.test.ts 13 KB

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