result_transformer.test.ts 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967
  1. import { DataFrame, FieldType, DataQueryRequest, DataQueryResponse, MutableDataFrame } from '@grafana/data';
  2. import { transform, transformV2, transformDFToTable, parseSampleValue } from './result_transformer';
  3. import { PromQuery } from './types';
  4. jest.mock('@grafana/runtime', () => ({
  5. getTemplateSrv: () => ({
  6. replace: (str: string) => str,
  7. }),
  8. getDataSourceSrv: () => {
  9. return {
  10. getInstanceSettings: () => {
  11. return { name: 'Tempo' };
  12. },
  13. };
  14. },
  15. }));
  16. const matrixResponse = {
  17. status: 'success',
  18. data: {
  19. resultType: 'matrix',
  20. result: [
  21. {
  22. metric: { __name__: 'test', job: 'testjob' },
  23. values: [
  24. [1, '10'],
  25. [2, '0'],
  26. ],
  27. },
  28. ],
  29. },
  30. };
  31. describe('Prometheus Result Transformer', () => {
  32. describe('parse variants of "+Inf" and "-Inf" strings', () => {
  33. it('+Inf', () => {
  34. expect(parseSampleValue('+Inf')).toEqual(Number.POSITIVE_INFINITY);
  35. });
  36. it('Inf', () => {
  37. expect(parseSampleValue('Inf')).toEqual(Number.POSITIVE_INFINITY);
  38. });
  39. it('inf', () => {
  40. expect(parseSampleValue('inf')).toEqual(Number.POSITIVE_INFINITY);
  41. });
  42. it('+Infinity', () => {
  43. expect(parseSampleValue('+Infinity')).toEqual(Number.POSITIVE_INFINITY);
  44. });
  45. it('+infinity', () => {
  46. expect(parseSampleValue('+infinity')).toEqual(Number.POSITIVE_INFINITY);
  47. });
  48. it('infinity', () => {
  49. expect(parseSampleValue('infinity')).toEqual(Number.POSITIVE_INFINITY);
  50. });
  51. it('-Inf', () => {
  52. expect(parseSampleValue('-Inf')).toEqual(Number.NEGATIVE_INFINITY);
  53. });
  54. it('-inf', () => {
  55. expect(parseSampleValue('-inf')).toEqual(Number.NEGATIVE_INFINITY);
  56. });
  57. it('-Infinity', () => {
  58. expect(parseSampleValue('-Infinity')).toEqual(Number.NEGATIVE_INFINITY);
  59. });
  60. it('-infinity', () => {
  61. expect(parseSampleValue('-infinity')).toEqual(Number.NEGATIVE_INFINITY);
  62. });
  63. });
  64. describe('transformV2', () => {
  65. it('results with time_series format should be enriched with preferredVisualisationType', () => {
  66. const request = {
  67. targets: [
  68. {
  69. format: 'time_series',
  70. refId: 'A',
  71. },
  72. ],
  73. } as unknown as DataQueryRequest<PromQuery>;
  74. const response = {
  75. state: 'Done',
  76. data: [
  77. {
  78. fields: [],
  79. length: 2,
  80. name: 'ALERTS',
  81. refId: 'A',
  82. },
  83. ],
  84. } as unknown as DataQueryResponse;
  85. const series = transformV2(response, request, {});
  86. expect(series).toEqual({
  87. data: [{ fields: [], length: 2, meta: { preferredVisualisationType: 'graph' }, name: 'ALERTS', refId: 'A' }],
  88. state: 'Done',
  89. });
  90. });
  91. it('results with table format should be transformed to table dataFrames', () => {
  92. const request = {
  93. targets: [
  94. {
  95. format: 'table',
  96. refId: 'A',
  97. },
  98. ],
  99. } as unknown as DataQueryRequest<PromQuery>;
  100. const response = {
  101. state: 'Done',
  102. data: [
  103. new MutableDataFrame({
  104. refId: 'A',
  105. fields: [
  106. { name: 'time', type: FieldType.time, values: [6, 5, 4] },
  107. {
  108. name: 'value',
  109. type: FieldType.number,
  110. values: [6, 5, 4],
  111. labels: { label1: 'value1', label2: 'value2' },
  112. },
  113. ],
  114. }),
  115. ],
  116. } as unknown as DataQueryResponse;
  117. const series = transformV2(response, request, {});
  118. expect(series.data[0].fields[0].name).toEqual('Time');
  119. expect(series.data[0].fields[1].name).toEqual('label1');
  120. expect(series.data[0].fields[2].name).toEqual('label2');
  121. expect(series.data[0].fields[3].name).toEqual('Value');
  122. expect(series.data[0].meta?.preferredVisualisationType).toEqual('table');
  123. });
  124. it('results with table format and multiple data frames should be transformed to 1 table dataFrame', () => {
  125. const request = {
  126. targets: [
  127. {
  128. format: 'table',
  129. refId: 'A',
  130. },
  131. ],
  132. } as unknown as DataQueryRequest<PromQuery>;
  133. const response = {
  134. state: 'Done',
  135. data: [
  136. new MutableDataFrame({
  137. refId: 'A',
  138. fields: [
  139. { name: 'time', type: FieldType.time, values: [6, 5, 4] },
  140. {
  141. name: 'value',
  142. type: FieldType.number,
  143. values: [6, 5, 4],
  144. labels: { label1: 'value1', label2: 'value2' },
  145. },
  146. ],
  147. }),
  148. new MutableDataFrame({
  149. refId: 'A',
  150. fields: [
  151. { name: 'time', type: FieldType.time, values: [2, 3, 7] },
  152. {
  153. name: 'value',
  154. type: FieldType.number,
  155. values: [2, 3, 7],
  156. labels: { label3: 'value3', label4: 'value4' },
  157. },
  158. ],
  159. }),
  160. ],
  161. } as unknown as DataQueryResponse;
  162. const series = transformV2(response, request, {});
  163. expect(series.data.length).toEqual(1);
  164. expect(series.data[0].fields[0].name).toEqual('Time');
  165. expect(series.data[0].fields[1].name).toEqual('label1');
  166. expect(series.data[0].fields[2].name).toEqual('label2');
  167. expect(series.data[0].fields[3].name).toEqual('label3');
  168. expect(series.data[0].fields[4].name).toEqual('label4');
  169. expect(series.data[0].fields[5].name).toEqual('Value');
  170. expect(series.data[0].meta?.preferredVisualisationType).toEqual('table');
  171. });
  172. it('results with table and time_series format should be correctly transformed', () => {
  173. const options = {
  174. targets: [
  175. {
  176. format: 'table',
  177. refId: 'A',
  178. },
  179. {
  180. format: 'time_series',
  181. refId: 'B',
  182. },
  183. ],
  184. } as unknown as DataQueryRequest<PromQuery>;
  185. const response = {
  186. state: 'Done',
  187. data: [
  188. new MutableDataFrame({
  189. refId: 'A',
  190. fields: [
  191. { name: 'time', type: FieldType.time, values: [6, 5, 4] },
  192. {
  193. name: 'value',
  194. type: FieldType.number,
  195. values: [6, 5, 4],
  196. labels: { label1: 'value1', label2: 'value2' },
  197. },
  198. ],
  199. }),
  200. new MutableDataFrame({
  201. refId: 'B',
  202. fields: [
  203. { name: 'time', type: FieldType.time, values: [6, 5, 4] },
  204. {
  205. name: 'value',
  206. type: FieldType.number,
  207. values: [6, 5, 4],
  208. labels: { label1: 'value1', label2: 'value2' },
  209. },
  210. ],
  211. }),
  212. ],
  213. } as unknown as DataQueryResponse;
  214. const series = transformV2(response, options, {});
  215. expect(series.data[0].fields.length).toEqual(2);
  216. expect(series.data[0].meta?.preferredVisualisationType).toEqual('graph');
  217. expect(series.data[1].fields.length).toEqual(4);
  218. expect(series.data[1].meta?.preferredVisualisationType).toEqual('table');
  219. });
  220. it('results with heatmap format should be correctly transformed', () => {
  221. const options = {
  222. targets: [
  223. {
  224. format: 'heatmap',
  225. refId: 'A',
  226. },
  227. ],
  228. } as unknown as DataQueryRequest<PromQuery>;
  229. const response = {
  230. state: 'Done',
  231. data: [
  232. new MutableDataFrame({
  233. refId: 'A',
  234. fields: [
  235. { name: 'Time', type: FieldType.time, values: [6, 5, 4] },
  236. {
  237. name: 'Value',
  238. type: FieldType.number,
  239. values: [10, 10, 0],
  240. labels: { le: '1' },
  241. },
  242. ],
  243. }),
  244. new MutableDataFrame({
  245. refId: 'A',
  246. fields: [
  247. { name: 'Time', type: FieldType.time, values: [6, 5, 4] },
  248. {
  249. name: 'Value',
  250. type: FieldType.number,
  251. values: [20, 10, 30],
  252. labels: { le: '2' },
  253. },
  254. ],
  255. }),
  256. new MutableDataFrame({
  257. refId: 'A',
  258. fields: [
  259. { name: 'Time', type: FieldType.time, values: [6, 5, 4] },
  260. {
  261. name: 'Value',
  262. type: FieldType.number,
  263. values: [30, 10, 40],
  264. labels: { le: '3' },
  265. },
  266. ],
  267. }),
  268. ],
  269. } as unknown as DataQueryResponse;
  270. const series = transformV2(response, options, {});
  271. expect(series.data[0].fields.length).toEqual(4);
  272. expect(series.data[0].fields[1].values.toArray()).toEqual([10, 10, 0]);
  273. expect(series.data[0].fields[2].values.toArray()).toEqual([10, 0, 30]);
  274. expect(series.data[0].fields[3].values.toArray()).toEqual([10, 0, 10]);
  275. });
  276. it('Retains exemplar frames when data returned is a heatmap', () => {
  277. const options = {
  278. targets: [
  279. {
  280. format: 'heatmap',
  281. refId: 'A',
  282. },
  283. ],
  284. } as unknown as DataQueryRequest<PromQuery>;
  285. const response = {
  286. state: 'Done',
  287. data: [
  288. new MutableDataFrame({
  289. refId: 'A',
  290. fields: [
  291. { name: 'Time', type: FieldType.time, values: [6, 5, 4] },
  292. {
  293. name: 'Value',
  294. type: FieldType.number,
  295. values: [10, 10, 0],
  296. labels: { le: '1' },
  297. },
  298. ],
  299. }),
  300. new MutableDataFrame({
  301. refId: 'A',
  302. name: 'exemplar',
  303. meta: {
  304. custom: {
  305. resultType: 'exemplar',
  306. },
  307. },
  308. fields: [
  309. { name: 'Time', type: FieldType.time, values: [6, 5, 4, 3, 2, 1] },
  310. {
  311. name: 'Value',
  312. type: FieldType.number,
  313. values: [30, 10, 40, 90, 14, 21],
  314. labels: { le: '6' },
  315. },
  316. {
  317. name: 'Test',
  318. type: FieldType.string,
  319. values: ['hello', 'doctor', 'name', 'continue', 'yesterday', 'tomorrow'],
  320. labels: { le: '6' },
  321. },
  322. ],
  323. }),
  324. ],
  325. } as unknown as DataQueryResponse;
  326. const series = transformV2(response, options, {});
  327. expect(series.data[0].fields.length).toEqual(2);
  328. expect(series.data.length).toEqual(2);
  329. expect(series.data[1].fields[2].values.toArray()).toEqual([
  330. 'hello',
  331. 'doctor',
  332. 'name',
  333. 'continue',
  334. 'yesterday',
  335. 'tomorrow',
  336. ]);
  337. expect(series.data[1].fields.length).toEqual(3);
  338. });
  339. });
  340. describe('transformDFToTable', () => {
  341. it('transforms dataFrame with response length 1 to table dataFrame', () => {
  342. const df = new MutableDataFrame({
  343. refId: 'A',
  344. fields: [
  345. { name: 'time', type: FieldType.time, values: [6, 5, 4] },
  346. {
  347. name: 'value',
  348. type: FieldType.number,
  349. values: [6, 5, 4],
  350. labels: { label1: 'value1', label2: 'value2' },
  351. },
  352. ],
  353. });
  354. const tableDf = transformDFToTable([df])[0];
  355. expect(tableDf.fields.length).toBe(4);
  356. expect(tableDf.fields[0].name).toBe('Time');
  357. expect(tableDf.fields[1].name).toBe('label1');
  358. expect(tableDf.fields[1].values.get(0)).toBe('value1');
  359. expect(tableDf.fields[2].name).toBe('label2');
  360. expect(tableDf.fields[2].values.get(0)).toBe('value2');
  361. expect(tableDf.fields[3].name).toBe('Value');
  362. });
  363. it('transforms dataFrame with response length 2 to table dataFrame', () => {
  364. const df = new MutableDataFrame({
  365. refId: 'A',
  366. fields: [
  367. { name: 'time', type: FieldType.time, values: [6, 5, 4] },
  368. {
  369. name: 'value',
  370. type: FieldType.number,
  371. values: [6, 5, 4],
  372. labels: { label1: 'value1', label2: 'value2' },
  373. },
  374. ],
  375. });
  376. const tableDf = transformDFToTable([df])[0];
  377. expect(tableDf.fields.length).toBe(4);
  378. expect(tableDf.fields[0].name).toBe('Time');
  379. expect(tableDf.fields[1].name).toBe('label1');
  380. expect(tableDf.fields[1].values.get(0)).toBe('value1');
  381. expect(tableDf.fields[2].name).toBe('label2');
  382. expect(tableDf.fields[2].values.get(0)).toBe('value2');
  383. expect(tableDf.fields[3].name).toBe('Value');
  384. });
  385. });
  386. describe('transform', () => {
  387. const options: any = { target: {}, query: {} };
  388. describe('When nothing is returned', () => {
  389. it('should return empty array', () => {
  390. const response = {
  391. status: 'success',
  392. data: {
  393. resultType: '',
  394. result: null,
  395. },
  396. };
  397. const series = transform({ data: response } as any, options);
  398. expect(series).toEqual([]);
  399. });
  400. it('should return empty array', () => {
  401. const response = {
  402. status: 'success',
  403. data: {
  404. resultType: '',
  405. result: null,
  406. },
  407. };
  408. const result = transform({ data: response } as any, { ...options, target: { format: 'table' } });
  409. expect(result).toHaveLength(0);
  410. });
  411. });
  412. describe('When resultFormat is table', () => {
  413. const response = {
  414. status: 'success',
  415. data: {
  416. resultType: 'matrix',
  417. result: [
  418. {
  419. metric: { __name__: 'test', job: 'testjob' },
  420. values: [
  421. [1443454528, '3846'],
  422. [1443454530, '3848'],
  423. ],
  424. },
  425. {
  426. metric: {
  427. __name__: 'test2',
  428. instance: 'localhost:8080',
  429. job: 'otherjob',
  430. },
  431. values: [
  432. [1443454529, '3847'],
  433. [1443454531, '3849'],
  434. ],
  435. },
  436. ],
  437. },
  438. };
  439. it('should return data frame', () => {
  440. const result = transform({ data: response } as any, {
  441. ...options,
  442. target: {
  443. responseListLength: 0,
  444. refId: 'A',
  445. format: 'table',
  446. },
  447. });
  448. expect(result[0].fields[0].values.toArray()).toEqual([
  449. 1443454528000, 1443454530000, 1443454529000, 1443454531000,
  450. ]);
  451. expect(result[0].fields[0].name).toBe('Time');
  452. expect(result[0].fields[0].type).toBe(FieldType.time);
  453. expect(result[0].fields[1].values.toArray()).toEqual(['test', 'test', 'test2', 'test2']);
  454. expect(result[0].fields[1].name).toBe('__name__');
  455. expect(result[0].fields[1].config.filterable).toBe(true);
  456. expect(result[0].fields[1].type).toBe(FieldType.string);
  457. expect(result[0].fields[2].values.toArray()).toEqual(['', '', 'localhost:8080', 'localhost:8080']);
  458. expect(result[0].fields[2].name).toBe('instance');
  459. expect(result[0].fields[2].type).toBe(FieldType.string);
  460. expect(result[0].fields[3].values.toArray()).toEqual(['testjob', 'testjob', 'otherjob', 'otherjob']);
  461. expect(result[0].fields[3].name).toBe('job');
  462. expect(result[0].fields[3].type).toBe(FieldType.string);
  463. expect(result[0].fields[4].values.toArray()).toEqual([3846, 3848, 3847, 3849]);
  464. expect(result[0].fields[4].name).toEqual('Value');
  465. expect(result[0].fields[4].type).toBe(FieldType.number);
  466. expect(result[0].refId).toBe('A');
  467. });
  468. it('should include refId if response count is more than 2', () => {
  469. const result = transform({ data: response } as any, {
  470. ...options,
  471. target: {
  472. refId: 'B',
  473. format: 'table',
  474. },
  475. responseListLength: 2,
  476. });
  477. expect(result[0].fields[4].name).toEqual('Value #B');
  478. });
  479. });
  480. describe('When resultFormat is table and instant = true', () => {
  481. const response = {
  482. status: 'success',
  483. data: {
  484. resultType: 'vector',
  485. result: [
  486. {
  487. metric: { __name__: 'test', job: 'testjob' },
  488. value: [1443454528, '3846'],
  489. },
  490. ],
  491. },
  492. };
  493. it('should return data frame', () => {
  494. const result = transform({ data: response } as any, { ...options, target: { format: 'table' } });
  495. expect(result[0].fields[0].values.toArray()).toEqual([1443454528000]);
  496. expect(result[0].fields[0].name).toBe('Time');
  497. expect(result[0].fields[1].values.toArray()).toEqual(['test']);
  498. expect(result[0].fields[1].name).toBe('__name__');
  499. expect(result[0].fields[2].values.toArray()).toEqual(['testjob']);
  500. expect(result[0].fields[2].name).toBe('job');
  501. expect(result[0].fields[3].values.toArray()).toEqual([3846]);
  502. expect(result[0].fields[3].name).toEqual('Value');
  503. });
  504. it('should return le label values parsed as numbers', () => {
  505. const response = {
  506. status: 'success',
  507. data: {
  508. resultType: 'vector',
  509. result: [
  510. {
  511. metric: { le: '102' },
  512. value: [1594908838, '0'],
  513. },
  514. ],
  515. },
  516. };
  517. const result = transform({ data: response } as any, { ...options, target: { format: 'table' } });
  518. expect(result[0].fields[1].values.toArray()).toEqual([102]);
  519. expect(result[0].fields[1].type).toEqual(FieldType.number);
  520. });
  521. });
  522. describe('When instant = true', () => {
  523. const response = {
  524. status: 'success',
  525. data: {
  526. resultType: 'vector',
  527. result: [
  528. {
  529. metric: { __name__: 'test', job: 'testjob' },
  530. value: [1443454528, '3846'],
  531. },
  532. ],
  533. },
  534. };
  535. it('should return data frame', () => {
  536. const result: DataFrame[] = transform({ data: response } as any, { ...options, query: { instant: true } });
  537. expect(result[0].name).toBe('test{job="testjob"}');
  538. });
  539. });
  540. describe('When resultFormat is heatmap', () => {
  541. const getResponse = (result: any) => ({
  542. status: 'success',
  543. data: {
  544. resultType: 'matrix',
  545. result,
  546. },
  547. });
  548. const options = {
  549. format: 'heatmap',
  550. start: 1445000010,
  551. end: 1445000030,
  552. legendFormat: '{{le}}',
  553. };
  554. it('should convert cumulative histogram to regular', () => {
  555. const response = getResponse([
  556. {
  557. metric: { __name__: 'test', job: 'testjob', le: '1' },
  558. values: [
  559. [1445000010, '10'],
  560. [1445000020, '10'],
  561. [1445000030, '0'],
  562. ],
  563. },
  564. {
  565. metric: { __name__: 'test', job: 'testjob', le: '2' },
  566. values: [
  567. [1445000010, '20'],
  568. [1445000020, '10'],
  569. [1445000030, '30'],
  570. ],
  571. },
  572. {
  573. metric: { __name__: 'test', job: 'testjob', le: '3' },
  574. values: [
  575. [1445000010, '30'],
  576. [1445000020, '10'],
  577. [1445000030, '40'],
  578. ],
  579. },
  580. ]);
  581. const result = transform({ data: response } as any, { query: options, target: options } as any);
  582. expect(result[0].fields[0].values.toArray()).toEqual([1445000010000, 1445000020000, 1445000030000]);
  583. expect(result[0].fields[1].values.toArray()).toEqual([10, 10, 0]);
  584. expect(result[0].fields[2].values.toArray()).toEqual([10, 0, 30]);
  585. expect(result[0].fields[3].values.toArray()).toEqual([10, 0, 10]);
  586. });
  587. it('should handle missing datapoints', () => {
  588. const response = getResponse([
  589. {
  590. metric: { __name__: 'test', job: 'testjob', le: '1' },
  591. values: [
  592. [1445000010, '1'],
  593. [1445000020, '2'],
  594. ],
  595. },
  596. {
  597. metric: { __name__: 'test', job: 'testjob', le: '2' },
  598. values: [
  599. [1445000010, '2'],
  600. [1445000020, '5'],
  601. [1445000030, '1'],
  602. ],
  603. },
  604. {
  605. metric: { __name__: 'test', job: 'testjob', le: '3' },
  606. values: [
  607. [1445000010, '3'],
  608. [1445000020, '7'],
  609. ],
  610. },
  611. ]);
  612. const result = transform({ data: response } as any, { query: options, target: options } as any);
  613. expect(result[0].fields[1].values.toArray()).toEqual([1, 2]);
  614. expect(result[0].fields[2].values.toArray()).toEqual([1, 3, 1]);
  615. expect(result[0].fields[3].values.toArray()).toEqual([1, 2]);
  616. });
  617. });
  618. describe('When the response is a matrix', () => {
  619. it('should have labels with the value field', () => {
  620. const response = {
  621. status: 'success',
  622. data: {
  623. resultType: 'matrix',
  624. result: [
  625. {
  626. metric: { __name__: 'test', job: 'testjob', instance: 'testinstance' },
  627. values: [
  628. [0, '10'],
  629. [1, '10'],
  630. [2, '0'],
  631. ],
  632. },
  633. ],
  634. },
  635. };
  636. const result: DataFrame[] = transform({ data: response } as any, {
  637. ...options,
  638. });
  639. expect(result[0].fields[1].labels).toBeDefined();
  640. expect(result[0].fields[1].labels?.instance).toBe('testinstance');
  641. expect(result[0].fields[1].labels?.job).toBe('testjob');
  642. });
  643. it('should transform into a data frame', () => {
  644. const response = {
  645. status: 'success',
  646. data: {
  647. resultType: 'matrix',
  648. result: [
  649. {
  650. metric: { __name__: 'test', job: 'testjob' },
  651. values: [
  652. [0, '10'],
  653. [1, '10'],
  654. [2, '0'],
  655. ],
  656. },
  657. ],
  658. },
  659. };
  660. const result: DataFrame[] = transform({ data: response } as any, {
  661. ...options,
  662. query: {
  663. start: 0,
  664. end: 2,
  665. },
  666. });
  667. expect(result[0].fields[0].values.toArray()).toEqual([0, 1000, 2000]);
  668. expect(result[0].fields[1].values.toArray()).toEqual([10, 10, 0]);
  669. expect(result[0].name).toBe('test{job="testjob"}');
  670. });
  671. it('should fill null values', () => {
  672. const result = transform({ data: matrixResponse } as any, {
  673. ...options,
  674. query: { step: 1, start: 0, end: 2 },
  675. });
  676. expect(result[0].fields[0].values.toArray()).toEqual([0, 1000, 2000]);
  677. expect(result[0].fields[1].values.toArray()).toEqual([null, 10, 0]);
  678. });
  679. it('should use __name__ label as series name', () => {
  680. const result = transform({ data: matrixResponse } as any, {
  681. ...options,
  682. query: {
  683. step: 1,
  684. start: 0,
  685. end: 2,
  686. },
  687. });
  688. expect(result[0].name).toEqual('test{job="testjob"}');
  689. });
  690. it('should use query as series name when __name__ is not available and metric is empty', () => {
  691. const response = {
  692. status: 'success',
  693. data: {
  694. resultType: 'matrix',
  695. result: [
  696. {
  697. metric: {},
  698. values: [[0, '10']],
  699. },
  700. ],
  701. },
  702. };
  703. const expr = 'histogram_quantile(0.95, sum(rate(tns_request_duration_seconds_bucket[5m])) by (le))';
  704. const result = transform({ data: response } as any, {
  705. ...options,
  706. query: {
  707. step: 1,
  708. start: 0,
  709. end: 2,
  710. expr,
  711. },
  712. });
  713. expect(result[0].name).toEqual(expr);
  714. });
  715. it('should set frame name to undefined if no __name__ label but there are other labels', () => {
  716. const response = {
  717. status: 'success',
  718. data: {
  719. resultType: 'matrix',
  720. result: [
  721. {
  722. metric: { job: 'testjob' },
  723. values: [
  724. [1, '10'],
  725. [2, '0'],
  726. ],
  727. },
  728. ],
  729. },
  730. };
  731. const result = transform({ data: response } as any, {
  732. ...options,
  733. query: {
  734. step: 1,
  735. start: 0,
  736. end: 2,
  737. },
  738. });
  739. expect(result[0].name).toBe('{job="testjob"}');
  740. });
  741. it('should not set displayName for ValueFields', () => {
  742. const result = transform({ data: matrixResponse } as any, options);
  743. expect(result[0].fields[1].config.displayName).toBeUndefined();
  744. expect(result[0].fields[1].config.displayNameFromDS).toBe('test{job="testjob"}');
  745. });
  746. it('should align null values with step', () => {
  747. const response = {
  748. status: 'success',
  749. data: {
  750. resultType: 'matrix',
  751. result: [
  752. {
  753. metric: { __name__: 'test', job: 'testjob' },
  754. values: [
  755. [4, '10'],
  756. [8, '10'],
  757. ],
  758. },
  759. ],
  760. },
  761. };
  762. const result = transform({ data: response } as any, { ...options, query: { step: 2, start: 0, end: 8 } });
  763. expect(result[0].fields[0].values.toArray()).toEqual([0, 2000, 4000, 6000, 8000]);
  764. expect(result[0].fields[1].values.toArray()).toEqual([null, null, 10, null, 10]);
  765. });
  766. });
  767. describe('When infinity values are returned', () => {
  768. describe('When resultType is scalar', () => {
  769. const response = {
  770. status: 'success',
  771. data: {
  772. resultType: 'scalar',
  773. result: [1443454528, '+Inf'],
  774. },
  775. };
  776. it('should correctly parse values', () => {
  777. const result: DataFrame[] = transform({ data: response } as any, {
  778. ...options,
  779. target: { format: 'table' },
  780. });
  781. expect(result[0].fields[1].values.toArray()).toEqual([Number.POSITIVE_INFINITY]);
  782. });
  783. });
  784. describe('When resultType is vector', () => {
  785. const response = {
  786. status: 'success',
  787. data: {
  788. resultType: 'vector',
  789. result: [
  790. {
  791. metric: { __name__: 'test', job: 'testjob' },
  792. value: [1443454528, '+Inf'],
  793. },
  794. {
  795. metric: { __name__: 'test', job: 'testjob' },
  796. value: [1443454528, '-Inf'],
  797. },
  798. ],
  799. },
  800. };
  801. describe('When format is table', () => {
  802. it('should correctly parse values', () => {
  803. const result: DataFrame[] = transform({ data: response } as any, {
  804. ...options,
  805. target: { format: 'table' },
  806. });
  807. expect(result[0].fields[3].values.toArray()).toEqual([Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY]);
  808. });
  809. });
  810. });
  811. });
  812. const exemplarsResponse = {
  813. status: 'success',
  814. data: [
  815. {
  816. seriesLabels: { __name__: 'test' },
  817. exemplars: [
  818. {
  819. timestamp: 1610449069.957,
  820. labels: { traceID: '5020b5bc45117f07' },
  821. value: 0.002074123,
  822. },
  823. ],
  824. },
  825. ],
  826. };
  827. describe('When the response is exemplar data', () => {
  828. it('should return as an data frame with a dataTopic annotations', () => {
  829. const result = transform({ data: exemplarsResponse } as any, options);
  830. expect(result[0].meta?.dataTopic).toBe('annotations');
  831. expect(result[0].fields.length).toBe(4); // __name__, traceID, Time, Value
  832. expect(result[0].length).toBe(1);
  833. });
  834. it('should return with an empty array when data is empty', () => {
  835. const result = transform(
  836. {
  837. data: {
  838. status: 'success',
  839. data: [],
  840. },
  841. } as any,
  842. options
  843. );
  844. expect(result).toHaveLength(0);
  845. });
  846. it('should remove exemplars that are too close to each other', () => {
  847. const response = {
  848. status: 'success',
  849. data: [
  850. {
  851. exemplars: [
  852. {
  853. timestamp: 1610449070.0,
  854. value: 5,
  855. },
  856. {
  857. timestamp: 1610449070.0,
  858. value: 1,
  859. },
  860. {
  861. timestamp: 1610449070.5,
  862. value: 13,
  863. },
  864. {
  865. timestamp: 1610449070.3,
  866. value: 20,
  867. },
  868. ],
  869. },
  870. ],
  871. };
  872. /**
  873. * the standard deviation for the above values is 8.4 this means that we show the highest
  874. * value (20) and then the next value should be 2 times the standard deviation which is 1
  875. **/
  876. const result = transform({ data: response } as any, options);
  877. expect(result[0].length).toBe(2);
  878. });
  879. describe('data link', () => {
  880. it('should be added to the field if found with url', () => {
  881. const result = transform({ data: exemplarsResponse } as any, {
  882. ...options,
  883. exemplarTraceIdDestinations: [{ name: 'traceID', url: 'http://localhost' }],
  884. });
  885. expect(result[0].fields.some((f) => f.config.links?.length)).toBe(true);
  886. });
  887. it('should be added to the field if found with internal link', () => {
  888. const result = transform({ data: exemplarsResponse } as any, {
  889. ...options,
  890. exemplarTraceIdDestinations: [{ name: 'traceID', datasourceUid: 'jaeger' }],
  891. });
  892. expect(result[0].fields.some((f) => f.config.links?.length)).toBe(true);
  893. });
  894. it('should not add link if exemplarTraceIdDestinations is not configured', () => {
  895. const result = transform({ data: exemplarsResponse } as any, options);
  896. expect(result[0].fields.some((f) => f.config.links?.length)).toBe(false);
  897. });
  898. });
  899. });
  900. });
  901. });