utils.test.ts 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. import { ArrayVector, createTheme, FieldType, ThresholdsMode, TimeRange, toDataFrame, dateTime } from '@grafana/data';
  2. import { LegendDisplayMode } from '@grafana/schema';
  3. import {
  4. findNextStateIndex,
  5. fmtDuration,
  6. getThresholdItems,
  7. prepareTimelineFields,
  8. prepareTimelineLegendItems,
  9. } from './utils';
  10. const theme = createTheme();
  11. describe('prepare timeline graph', () => {
  12. const timeRange: TimeRange = {
  13. from: dateTime(1),
  14. to: dateTime(3),
  15. raw: {} as any,
  16. };
  17. it('errors with no time fields', () => {
  18. const frames = [
  19. toDataFrame({
  20. fields: [
  21. { name: 'a', values: [1, 2, 3] },
  22. { name: 'b', values: ['a', 'b', 'c'] },
  23. ],
  24. }),
  25. ];
  26. const info = prepareTimelineFields(frames, true, timeRange, theme);
  27. expect(info.warn).toEqual('Data does not have a time field');
  28. });
  29. it('requires a number, string, or boolean value', () => {
  30. const frames = [
  31. toDataFrame({
  32. fields: [
  33. { name: 'a', type: FieldType.time, values: [1, 2, 3] },
  34. { name: 'b', type: FieldType.other, values: [{}, {}, {}] },
  35. ],
  36. }),
  37. ];
  38. const info = prepareTimelineFields(frames, true, timeRange, theme);
  39. expect(info.warn).toEqual('No graphable fields');
  40. });
  41. it('will merge duplicate values', () => {
  42. const frames = [
  43. toDataFrame({
  44. fields: [
  45. { name: 'a', type: FieldType.time, values: [1, 2, 3, 4, 5, 6, 7] },
  46. { name: 'b', values: [1, 1, undefined, 1, 2, 2, null, 2, 3] },
  47. ],
  48. }),
  49. ];
  50. const info = prepareTimelineFields(frames, true, timeRange, theme);
  51. expect(info.warn).toBeUndefined();
  52. const out = info.frames![0];
  53. const field = out.fields.find((f) => f.name === 'b');
  54. expect(field?.values.toArray()).toMatchInlineSnapshot(`
  55. Array [
  56. 1,
  57. 1,
  58. undefined,
  59. 1,
  60. 2,
  61. 2,
  62. null,
  63. 2,
  64. 3,
  65. ]
  66. `);
  67. });
  68. it('should try to sort time fields', () => {
  69. const frames = [
  70. toDataFrame({
  71. fields: [
  72. { name: 'a', type: FieldType.time, values: [4, 3, 1, 2] },
  73. { name: 'b', values: [1, 1, 2, 2] },
  74. ],
  75. }),
  76. ];
  77. const result = prepareTimelineFields(frames, true, timeRange, theme);
  78. expect(result.frames?.[0].fields[0].values.toArray()).toEqual([1, 2, 3, 4]);
  79. });
  80. });
  81. describe('findNextStateIndex', () => {
  82. it('handles leading datapoint index', () => {
  83. const field = {
  84. name: 'time',
  85. type: FieldType.number,
  86. values: new ArrayVector([1, undefined, undefined, 2, undefined, undefined]),
  87. } as any;
  88. const result = findNextStateIndex(field, 0);
  89. expect(result).toEqual(3);
  90. });
  91. it('handles trailing datapoint index', () => {
  92. const field = {
  93. name: 'time',
  94. type: FieldType.number,
  95. values: new ArrayVector([1, undefined, undefined, 2, undefined, 3]),
  96. } as any;
  97. const result = findNextStateIndex(field, 5);
  98. expect(result).toEqual(null);
  99. });
  100. it('handles trailing undefined', () => {
  101. const field = {
  102. name: 'time',
  103. type: FieldType.number,
  104. values: new ArrayVector([1, undefined, undefined, 2, undefined, 3, undefined]),
  105. } as any;
  106. const result = findNextStateIndex(field, 5);
  107. expect(result).toEqual(null);
  108. });
  109. it('handles datapoint index inside range', () => {
  110. const field = {
  111. name: 'time',
  112. type: FieldType.number,
  113. values: new ArrayVector([
  114. 1,
  115. undefined,
  116. undefined,
  117. 3,
  118. undefined,
  119. undefined,
  120. undefined,
  121. undefined,
  122. 2,
  123. undefined,
  124. undefined,
  125. ]),
  126. } as any;
  127. const result = findNextStateIndex(field, 3);
  128. expect(result).toEqual(8);
  129. });
  130. describe('single data points', () => {
  131. const field = {
  132. name: 'time',
  133. type: FieldType.number,
  134. values: new ArrayVector([1, 3, 2]),
  135. } as any;
  136. test('leading', () => {
  137. const result = findNextStateIndex(field, 0);
  138. expect(result).toEqual(1);
  139. });
  140. test('trailing', () => {
  141. const result = findNextStateIndex(field, 2);
  142. expect(result).toEqual(null);
  143. });
  144. test('inside', () => {
  145. const result = findNextStateIndex(field, 1);
  146. expect(result).toEqual(2);
  147. });
  148. });
  149. });
  150. describe('getThresholdItems', () => {
  151. it('should handle only one threshold', () => {
  152. const result = getThresholdItems(
  153. { thresholds: { mode: ThresholdsMode.Absolute, steps: [{ color: 'black', value: 0 }] } },
  154. theme
  155. );
  156. expect(result).toHaveLength(1);
  157. });
  158. });
  159. describe('prepareTimelineLegendItems', () => {
  160. it('should return legend items', () => {
  161. const frame: any = [
  162. {
  163. refId: 'A',
  164. fields: [
  165. {
  166. name: 'time',
  167. config: {
  168. color: {
  169. mode: 'thresholds',
  170. },
  171. thresholds: {
  172. mode: 'absolute',
  173. steps: [
  174. {
  175. color: 'green',
  176. value: null,
  177. },
  178. ],
  179. },
  180. },
  181. values: new ArrayVector([
  182. 1634092733455, 1634092763455, 1634092793455, 1634092823455, 1634092853455, 1634092883455, 1634092913455,
  183. 1634092943455, 1634092973455, 1634093003455,
  184. ]),
  185. display: (value: string) => ({
  186. text: value,
  187. color: undefined,
  188. numeric: NaN,
  189. }),
  190. },
  191. {
  192. name: 'A-series',
  193. config: {
  194. color: {
  195. mode: 'thresholds',
  196. },
  197. thresholds: {
  198. mode: 'absolute',
  199. steps: [
  200. {
  201. color: 'green',
  202. value: null,
  203. },
  204. ],
  205. },
  206. },
  207. values: new ArrayVector(['< -∞', null, null, null, null, null, null, null, null, null]),
  208. display: (value?: string) => ({
  209. text: value || '',
  210. color: 'green',
  211. numeric: NaN,
  212. }),
  213. },
  214. ],
  215. },
  216. ];
  217. const result = prepareTimelineLegendItems(frame, { displayMode: LegendDisplayMode.List } as any, theme);
  218. expect(result).toHaveLength(1);
  219. });
  220. });
  221. describe('duration', () => {
  222. it.each`
  223. value | expected
  224. ${-1} | ${''}
  225. ${20} | ${'20ms'}
  226. ${1000} | ${'1s'}
  227. ${1020} | ${'1s 20ms'}
  228. ${60000} | ${'1m'}
  229. ${61020} | ${'1m 1s'}
  230. ${3600000} | ${'1h'}
  231. ${6600000} | ${'1h 50m'}
  232. ${86400000} | ${'1d'}
  233. ${96640000} | ${'1d 2h'}
  234. ${604800000} | ${'1w'}
  235. ${691200000} | ${'1w 1d'}
  236. ${2419200000} | ${'4w'}
  237. ${2678400000} | ${'1mo 1d'}
  238. ${3196800000} | ${'1mo 1w'}
  239. ${3456000000} | ${'1mo 1w 3d'}
  240. ${6739200000} | ${'2mo 2w 4d'}
  241. ${31536000000} | ${'1y'}
  242. ${31968000000} | ${'1y 5d'}
  243. ${32140800000} | ${'1y 1w'}
  244. ${67910400000} | ${'2y 1mo 3w 5d'}
  245. ${40420800000} | ${'1y 3mo 1w 5d'}
  246. ${9007199254740991} | ${'285616y 5mo 1d'}
  247. `(' function should format $value ms to $expected', ({ value, expected }) => {
  248. const result = fmtDuration(value);
  249. expect(result).toEqual(expected);
  250. });
  251. });