D7net
Home
Console
Upload
information
Create File
Create Folder
About
Tools
:
/
usr
/
share
/
grafana
/
public
/
app
/
plugins
/
datasource
/
prometheus
/
Filename :
result_transformer.test.ts
back
Copy
import { cacheFieldDisplayNames, createDataFrame, DataFrame, DataQueryRequest, DataQueryResponse, FieldType, PreferredVisualisationType, } from '@grafana/data'; import { parseSampleValue, sortSeriesByLabel, transform, transformDFToTable, transformV2 } from './result_transformer'; import { PromQuery } from './types'; jest.mock('@grafana/runtime', () => ({ getTemplateSrv: () => ({ replace: (str: string) => str, }), getDataSourceSrv: () => { return { getInstanceSettings: (uid: string) => { const uids = ['Tempo', 'jaeger']; return uids.find((u) => u === uid) ? { name: uid } : undefined; }, }; }, config: { featureToggles: { prometheusDataplane: true, }, }, })); const matrixResponse = { status: 'success', data: { resultType: 'matrix', result: [ { metric: { __name__: 'test', job: 'testjob' }, values: [ [1, '10'], [2, '0'], ], }, ], }, }; describe('Prometheus Result Transformer', () => { describe('parse variants of "+Inf" and "-Inf" strings', () => { it('+Inf', () => { expect(parseSampleValue('+Inf')).toEqual(Number.POSITIVE_INFINITY); }); it('Inf', () => { expect(parseSampleValue('Inf')).toEqual(Number.POSITIVE_INFINITY); }); it('inf', () => { expect(parseSampleValue('inf')).toEqual(Number.POSITIVE_INFINITY); }); it('+Infinity', () => { expect(parseSampleValue('+Infinity')).toEqual(Number.POSITIVE_INFINITY); }); it('+infinity', () => { expect(parseSampleValue('+infinity')).toEqual(Number.POSITIVE_INFINITY); }); it('infinity', () => { expect(parseSampleValue('infinity')).toEqual(Number.POSITIVE_INFINITY); }); it('-Inf', () => { expect(parseSampleValue('-Inf')).toEqual(Number.NEGATIVE_INFINITY); }); it('-inf', () => { expect(parseSampleValue('-inf')).toEqual(Number.NEGATIVE_INFINITY); }); it('-Infinity', () => { expect(parseSampleValue('-Infinity')).toEqual(Number.NEGATIVE_INFINITY); }); it('-infinity', () => { expect(parseSampleValue('-infinity')).toEqual(Number.NEGATIVE_INFINITY); }); }); describe('sortSeriesByLabel() should use frame.fields[1].state?.displayName when available', () => { let frames = [ createDataFrame({ refId: 'A', fields: [ { name: 'Time', type: FieldType.time, values: [1, 2, 3] }, { type: FieldType.number, values: [4, 5, 6], config: { displayNameFromDS: '2', }, labels: { offset_days: '2', }, }, ], }), createDataFrame({ refId: 'A', fields: [ { name: 'Time', type: FieldType.time, values: [1, 2, 3] }, { type: FieldType.number, values: [7, 8, 9], config: { displayNameFromDS: '1', }, labels: { offset_days: '1', }, }, ], }), ]; it('sorts by displayNameFromDS', () => { cacheFieldDisplayNames(frames); let sorted = frames.slice().sort(sortSeriesByLabel); expect(sorted[0]).toEqual(frames[1]); expect(sorted[1]).toEqual(frames[0]); }); }); describe('transformV2', () => { it('results with time_series format should be enriched with preferredVisualisationType', () => { const request = { targets: [ { format: 'time_series', refId: 'A', }, ], } as unknown as DataQueryRequest<PromQuery>; const response = { state: 'Done', data: [ { fields: [], length: 2, name: 'ALERTS', refId: 'A', }, ], } as unknown as DataQueryResponse; const series = transformV2(response, request, {}); expect(series).toEqual({ data: [{ fields: [], length: 2, meta: { preferredVisualisationType: 'graph' }, name: 'ALERTS', refId: 'A' }], state: 'Done', }); }); it('dataplane handling, adds displayNameFromDs from calculateFieldDisplayName() when __name__ is the field name when legendFormat is auto', () => { const request = { targets: [ { format: 'time_series', refId: 'A', legendFormat: '__auto', }, ], } as unknown as DataQueryRequest<PromQuery>; const response = { state: 'Done', data: [ { fields: [ { name: 'Time', type: 'time', values: [1], typeInfo: { frame: 'time.Time' }, }, { name: 'up', labels: { __name__: 'up' }, config: {}, values: [1], }, ], length: 1, refId: 'A', meta: { type: 'timeseries-multi', typeVersion: [0, 1], }, }, ], } as unknown as DataQueryResponse; const series = transformV2(response, request, {}); expect(series).toEqual({ data: [ { fields: [ { name: 'Time', type: 'time', values: [1], typeInfo: { frame: 'time.Time' }, }, { config: { displayNameFromDS: 'up' }, labels: { __name__: 'up' }, name: 'up', values: [1], }, ], length: 1, meta: { type: 'timeseries-multi', typeVersion: [0, 1], preferredVisualisationType: 'graph', }, refId: 'A', }, ], state: 'Done', }); }); it('results with table format should be transformed to table dataFrames', () => { const request = { targets: [ { format: 'table', refId: 'A', }, ], } as unknown as DataQueryRequest<PromQuery>; const response = { state: 'Done', data: [ createDataFrame({ refId: 'A', fields: [ { name: 'time', type: FieldType.time, values: [6, 5, 4] }, { name: 'value', type: FieldType.number, values: [6, 5, 4], labels: { label1: 'value1', label2: 'value2' }, }, ], }), ], } as unknown as DataQueryResponse; const series = transformV2(response, request, {}); expect(series.data[0].fields[0].name).toEqual('Time'); expect(series.data[0].fields[1].name).toEqual('label1'); expect(series.data[0].fields[2].name).toEqual('label2'); expect(series.data[0].fields[3].name).toEqual('Value'); expect(series.data[0].meta?.preferredVisualisationType).toEqual('rawPrometheus'); }); it('results with table format and multiple data frames should be transformed to 1 table dataFrame', () => { const request = { targets: [ { format: 'table', refId: 'A', }, ], } as unknown as DataQueryRequest<PromQuery>; const response = { state: 'Done', data: [ createDataFrame({ refId: 'A', fields: [ { name: 'time', type: FieldType.time, values: [6, 5, 4] }, { name: 'value', type: FieldType.number, values: [6, 5, 4], labels: { label1: 'value1', label2: 'value2' }, }, ], }), createDataFrame({ refId: 'A', fields: [ { name: 'time', type: FieldType.time, values: [2, 3, 7] }, { name: 'value', type: FieldType.number, values: [2, 3, 7], labels: { label3: 'value3', label4: 'value4' }, }, ], }), ], } as unknown as DataQueryResponse; const series = transformV2(response, request, {}); expect(series.data.length).toEqual(1); expect(series.data[0].fields[0].name).toEqual('Time'); expect(series.data[0].fields[1].name).toEqual('label1'); expect(series.data[0].fields[2].name).toEqual('label2'); expect(series.data[0].fields[3].name).toEqual('label3'); expect(series.data[0].fields[4].name).toEqual('label4'); expect(series.data[0].fields[5].name).toEqual('Value'); expect(series.data[0].meta?.preferredVisualisationType).toEqual('rawPrometheus' as PreferredVisualisationType); }); it('results with table and time_series format should be correctly transformed', () => { const options = { targets: [ { format: 'table', refId: 'A', }, { format: 'time_series', refId: 'B', }, ], } as unknown as DataQueryRequest<PromQuery>; const response = { state: 'Done', data: [ createDataFrame({ refId: 'A', fields: [ { name: 'time', type: FieldType.time, values: [6, 5, 4] }, { name: 'value', type: FieldType.number, values: [6, 5, 4], labels: { label1: 'value1', label2: 'value2' }, }, ], }), createDataFrame({ refId: 'B', fields: [ { name: 'time', type: FieldType.time, values: [6, 5, 4] }, { name: 'value', type: FieldType.number, values: [6, 5, 4], labels: { label1: 'value1', label2: 'value2' }, }, ], }), ], } as unknown as DataQueryResponse; const series = transformV2(response, options, {}); expect(series.data[0].fields.length).toEqual(2); expect(series.data[0].meta?.preferredVisualisationType).toEqual('graph'); expect(series.data[1].fields.length).toEqual(4); expect(series.data[1].meta?.preferredVisualisationType).toEqual('rawPrometheus' as PreferredVisualisationType); }); // Heatmap frames can either have a name of the metric, or if there is no metric, a name of "Value" it('results with heatmap format (no metric name) should be correctly transformed', () => { const options = { targets: [ { format: 'heatmap', refId: 'A', }, ], } as unknown as DataQueryRequest<PromQuery>; const response = { state: 'Done', data: [ createDataFrame({ refId: 'A', fields: [ { name: 'Time', type: FieldType.time, values: [6, 5, 4] }, { name: 'Value', type: FieldType.number, values: [10, 10, 0], labels: { le: '1' }, }, ], }), createDataFrame({ refId: 'A', fields: [ { name: 'Time', type: FieldType.time, values: [6, 5, 4] }, { name: 'Value', type: FieldType.number, values: [30, 10, 40], labels: { le: '+Inf' }, }, ], }), createDataFrame({ refId: 'A', fields: [ { name: 'Time', type: FieldType.time, values: [6, 5, 4] }, { name: 'Value', type: FieldType.number, values: [20, 10, 30], labels: { le: '2' }, }, ], }), ], } as unknown as DataQueryResponse; const series = transformV2(response, options, {}); expect(series.data[0].fields.length).toEqual(4); expect(series.data[0].fields[1].values).toEqual([10, 10, 0]); expect(series.data[0].fields[2].values).toEqual([10, 0, 30]); expect(series.data[0].fields[3].values).toEqual([10, 0, 10]); expect(series.data[0].fields[1].name).toEqual('1'); expect(series.data[0].fields[2].name).toEqual('2'); expect(series.data[0].fields[3].name).toEqual('+Inf'); }); it('results with heatmap format (with metric name) should be correctly transformed', () => { const options = { targets: [ { format: 'heatmap', refId: 'A', }, ], } as unknown as DataQueryRequest<PromQuery>; const response = { state: 'Done', data: [ createDataFrame({ refId: 'A', fields: [ { name: 'Time', type: FieldType.time, values: [6, 5, 4] }, { name: 'metric_name', type: FieldType.number, values: [10, 10, 0], labels: { le: '1', __name__: 'metric_name' }, }, ], }), createDataFrame({ refId: 'A', fields: [ { name: 'Time', type: FieldType.time, values: [6, 5, 4] }, { name: 'metric_name', type: FieldType.number, values: [30, 10, 40], labels: { le: '+Inf', __name__: 'metric_name' }, }, ], }), createDataFrame({ refId: 'A', fields: [ { name: 'Time', type: FieldType.time, values: [6, 5, 4] }, { name: 'metric_name', type: FieldType.number, values: [20, 10, 30], labels: { le: '2', __name__: 'metric_name' }, }, ], }), ], } as unknown as DataQueryResponse; const series = transformV2(response, options, {}); expect(series.data[0].fields.length).toEqual(4); expect(series.data[0].fields[1].values).toEqual([10, 10, 0]); expect(series.data[0].fields[2].values).toEqual([10, 0, 30]); expect(series.data[0].fields[3].values).toEqual([10, 0, 10]); expect(series.data[0].fields[1].name).toEqual('1'); expect(series.data[0].fields[2].name).toEqual('2'); expect(series.data[0].fields[3].name).toEqual('+Inf'); }); it('results with heatmap format (no metric name) from multiple queries should be correctly transformed', () => { const options = { targets: [ { format: 'heatmap', refId: 'A', }, { format: 'heatmap', refId: 'B', }, ], } as unknown as DataQueryRequest<PromQuery>; const response = { state: 'Done', data: [ createDataFrame({ refId: 'A', fields: [ { name: 'Time', type: FieldType.time, values: [6, 5, 4] }, { name: 'Value', type: FieldType.number, values: [10, 10, 0], labels: { le: '1' }, }, ], }), createDataFrame({ refId: 'A', fields: [ { name: 'Time', type: FieldType.time, values: [6, 5, 4] }, { name: 'Value', type: FieldType.number, values: [20, 10, 30], labels: { le: '2' }, }, ], }), createDataFrame({ refId: 'A', fields: [ { name: 'Time', type: FieldType.time, values: [6, 5, 4] }, { name: 'Value', type: FieldType.number, values: [30, 10, 40], labels: { le: '+Inf' }, }, ], }), createDataFrame({ refId: 'B', fields: [ { name: 'Time', type: FieldType.time, values: [6, 5, 4] }, { name: 'Value', type: FieldType.number, values: [10, 10, 0], labels: { le: '1' }, }, ], }), createDataFrame({ refId: 'B', fields: [ { name: 'Time', type: FieldType.time, values: [6, 5, 4] }, { name: 'Value', type: FieldType.number, values: [20, 10, 30], labels: { le: '2' }, }, ], }), createDataFrame({ refId: 'B', fields: [ { name: 'Time', type: FieldType.time, values: [6, 5, 4] }, { name: 'Value', type: FieldType.number, values: [30, 10, 40], labels: { le: '+Inf' }, }, ], }), ], } as unknown as DataQueryResponse; const series = transformV2(response, options, {}); expect(series.data[0].fields.length).toEqual(4); expect(series.data[0].fields[1].values).toEqual([10, 10, 0]); expect(series.data[0].fields[2].values).toEqual([10, 0, 30]); expect(series.data[0].fields[3].values).toEqual([10, 0, 10]); }); it('results with heatmap format (with metric name) from multiple queries should be correctly transformed', () => { const options = { targets: [ { format: 'heatmap', refId: 'A', }, { format: 'heatmap', refId: 'B', }, ], } as unknown as DataQueryRequest<PromQuery>; const response = { state: 'Done', data: [ createDataFrame({ refId: 'A', fields: [ { name: 'Time', type: FieldType.time, values: [6, 5, 4] }, { name: 'metric_name', type: FieldType.number, values: [10, 10, 0], labels: { le: '1', __name__: 'metric_name' }, }, ], }), createDataFrame({ refId: 'A', fields: [ { name: 'Time', type: FieldType.time, values: [6, 5, 4] }, { name: 'metric_name', type: FieldType.number, values: [20, 10, 30], labels: { le: '2', __name__: 'metric_name' }, }, ], }), createDataFrame({ refId: 'A', fields: [ { name: 'Time', type: FieldType.time, values: [6, 5, 4] }, { name: 'metric_name', type: FieldType.number, values: [30, 10, 40], labels: { le: '+Inf', __name__: 'metric_name' }, }, ], }), createDataFrame({ refId: 'B', fields: [ { name: 'Time', type: FieldType.time, values: [6, 5, 4] }, { name: 'metric_name', type: FieldType.number, values: [10, 10, 0], labels: { le: '1', __name__: 'metric_name' }, }, ], }), createDataFrame({ refId: 'B', fields: [ { name: 'Time', type: FieldType.time, values: [6, 5, 4] }, { name: 'metric_name', type: FieldType.number, values: [20, 10, 30], labels: { le: '2', __name__: 'metric_name' }, }, ], }), createDataFrame({ refId: 'B', fields: [ { name: 'Time', type: FieldType.time, values: [6, 5, 4] }, { name: 'metric_name', type: FieldType.number, values: [30, 10, 40], labels: { le: '+Inf', __name__: 'metric_name' }, }, ], }), ], } as unknown as DataQueryResponse; const series = transformV2(response, options, {}); expect(series.data[0].fields.length).toEqual(4); expect(series.data[0].fields[1].values).toEqual([10, 10, 0]); expect(series.data[0].fields[2].values).toEqual([10, 0, 30]); expect(series.data[0].fields[3].values).toEqual([10, 0, 10]); }); it('results with heatmap format and multiple histograms should be grouped and de-accumulated by non-le labels', () => { const options = { targets: [ { format: 'heatmap', refId: 'A', }, ], } as unknown as DataQueryRequest<PromQuery>; const response = { state: 'Done', data: [ // 10 createDataFrame({ refId: 'A', fields: [ { name: 'Time', type: FieldType.time, values: [6, 5, 4] }, { name: 'Value', type: FieldType.number, values: [10, 10, 0], labels: { le: '1', additionalProperty: '10' }, }, ], }), createDataFrame({ refId: 'A', fields: [ { name: 'Time', type: FieldType.time, values: [6, 5, 4] }, { name: 'Value', type: FieldType.number, values: [20, 10, 30], labels: { le: '2', additionalProperty: '10' }, }, ], }), createDataFrame({ refId: 'A', fields: [ { name: 'Time', type: FieldType.time, values: [6, 5, 4] }, { name: 'Value', type: FieldType.number, values: [30, 10, 40], labels: { le: '+Inf', additionalProperty: '10' }, }, ], }), // 20 createDataFrame({ refId: 'A', fields: [ { name: 'Time', type: FieldType.time, values: [6, 5, 4] }, { name: 'Value', type: FieldType.number, values: [0, 10, 10], labels: { le: '1', additionalProperty: '20' }, }, ], }), createDataFrame({ refId: 'A', fields: [ { name: 'Time', type: FieldType.time, values: [6, 5, 4] }, { name: 'Value', type: FieldType.number, values: [20, 10, 40], labels: { le: '2', additionalProperty: '20' }, }, ], }), createDataFrame({ refId: 'A', fields: [ { name: 'Time', type: FieldType.time, values: [6, 5, 4] }, { name: 'Value', type: FieldType.number, values: [30, 10, 60], labels: { le: '+Inf', additionalProperty: '20' }, }, ], }), // 30 createDataFrame({ refId: 'A', fields: [ { name: 'Time', type: FieldType.time, values: [6, 5, 4] }, { name: 'Value', type: FieldType.number, values: [30, 30, 60], labels: { le: '1', additionalProperty: '30' }, }, ], }), createDataFrame({ refId: 'A', fields: [ { name: 'Time', type: FieldType.time, values: [6, 5, 4] }, { name: 'Value', type: FieldType.number, values: [30, 40, 60], labels: { le: '2', additionalProperty: '30' }, }, ], }), createDataFrame({ refId: 'A', fields: [ { name: 'Time', type: FieldType.time, values: [6, 5, 4] }, { name: 'Value', type: FieldType.number, values: [40, 40, 60], labels: { le: '+Inf', additionalProperty: '30' }, }, ], }), ], } as unknown as DataQueryResponse; const series = transformV2(response, options, {}); expect(series.data[0].fields.length).toEqual(4); expect(series.data[0].fields[1].values).toEqual([10, 10, 0]); expect(series.data[0].fields[2].values).toEqual([10, 0, 30]); expect(series.data[0].fields[3].values).toEqual([10, 0, 10]); expect(series.data[1].fields[1].values).toEqual([0, 10, 10]); expect(series.data[1].fields[2].values).toEqual([20, 0, 30]); expect(series.data[1].fields[3].values).toEqual([10, 0, 20]); expect(series.data[2].fields[1].values).toEqual([30, 30, 60]); expect(series.data[2].fields[2].values).toEqual([0, 10, 0]); expect(series.data[2].fields[3].values).toEqual([10, 0, 0]); }); it('Retains exemplar frames when data returned is a heatmap', () => { const options = { targets: [ { format: 'heatmap', refId: 'A', }, ], } as unknown as DataQueryRequest<PromQuery>; const response = { state: 'Done', data: [ createDataFrame({ refId: 'A', fields: [ { name: 'Time', type: FieldType.time, values: [6, 5, 4] }, { name: 'Value', type: FieldType.number, values: [10, 10, 0], labels: { le: '1' }, }, ], }), createDataFrame({ refId: 'A', name: 'exemplar', meta: { custom: { resultType: 'exemplar', }, }, fields: [ { name: 'Time', type: FieldType.time, values: [6, 5, 4, 3, 2, 1] }, { name: 'Value', type: FieldType.number, values: [30, 10, 40, 90, 14, 21], labels: { le: '6' }, }, { name: 'Test', type: FieldType.string, values: ['hello', 'doctor', 'name', 'continue', 'yesterday', 'tomorrow'], labels: { le: '6' }, }, ], }), ], } as unknown as DataQueryResponse; const series = transformV2(response, options, {}); expect(series.data[0].fields.length).toEqual(2); expect(series.data.length).toEqual(2); expect(series.data[1].fields[2].values).toEqual(['hello', 'doctor', 'name', 'continue', 'yesterday', 'tomorrow']); expect(series.data[1].fields.length).toEqual(3); }); it('should not add a link with an error when exemplarTraceIdDestinations is not configured properly', () => { const response = { state: 'Done', data: [ createDataFrame({ refId: 'A', fields: [ { name: 'Time', type: FieldType.time, values: [6, 5, 4] }, { name: 'Value', type: FieldType.number, values: [10, 10, 0], labels: { le: '1' }, }, ], }), createDataFrame({ refId: 'A', name: 'exemplar', meta: { custom: { resultType: 'exemplar', }, }, fields: [ { name: 'Time', type: FieldType.time, values: [6, 5, 4, 3, 2, 1] }, { name: 'Value', type: FieldType.number, values: [30, 10, 40, 90, 14, 21], labels: { le: '6' }, }, { name: 'traceID', type: FieldType.string, values: ['unknown'], labels: { le: '6' }, }, ], }), ], } as unknown as DataQueryResponse; const request = { targets: [ { format: 'heatmap', refId: 'A', }, ], } as unknown as DataQueryRequest<PromQuery>; const testOptions: any = { exemplarTraceIdDestinations: [ { name: 'traceID', datasourceUid: 'unknown', }, ], }; const series = transformV2(response, request, testOptions); expect(series.data[1].fields.length).toEqual(3); expect(series.data[1].name).toEqual('exemplar'); const traceField = series.data[1].fields.find((f) => f.name === 'traceID'); expect(traceField).toBeDefined(); expect(traceField!.config.links?.length).toBe(0); }); }); describe('transformDFToTable', () => { it('transforms dataFrame with response length 1 to table dataFrame', () => { const df = createDataFrame({ refId: 'A', fields: [ { name: 'time', type: FieldType.time, values: [6, 5, 4] }, { name: 'value', type: FieldType.number, values: [6, 5, 4], labels: { label1: 'value1', label2: 'value2' }, }, ], }); const tableDf = transformDFToTable([df])[0]; expect(tableDf.fields.length).toBe(4); expect(tableDf.fields[0].name).toBe('Time'); expect(tableDf.fields[1].name).toBe('label1'); expect(tableDf.fields[1].values[0]).toBe('value1'); expect(tableDf.fields[2].name).toBe('label2'); expect(tableDf.fields[2].values[0]).toBe('value2'); expect(tableDf.fields[3].name).toBe('Value'); }); it('transforms dataFrame with response length 2 to table dataFrame', () => { const df = createDataFrame({ refId: 'A', fields: [ { name: 'time', type: FieldType.time, values: [6, 5, 4] }, { name: 'value', type: FieldType.number, values: [6, 5, 4], labels: { label1: 'value1', label2: 'value2' }, }, ], }); const tableDf = transformDFToTable([df])[0]; expect(tableDf.fields.length).toBe(4); expect(tableDf.fields[0].name).toBe('Time'); expect(tableDf.fields[1].name).toBe('label1'); expect(tableDf.fields[1].values[0]).toBe('value1'); expect(tableDf.fields[2].name).toBe('label2'); expect(tableDf.fields[2].values[0]).toBe('value2'); expect(tableDf.fields[3].name).toBe('Value'); }); // Queries do not always return results it('transforms dataFrame and empty dataFrame mock responses to table dataFrames', () => { const value1 = 'value1'; const value2 = 'value2'; const dataframes = [ createDataFrame({ refId: 'A', fields: [ { name: 'time', type: FieldType.time, values: [6, 5, 4] }, { name: 'value', type: FieldType.number, values: [6, 5, 4], labels: { label1: value1, label2: value2 }, }, ], }), createDataFrame({ refId: 'B', fields: [], }), ]; const transformedTableDataFrames = transformDFToTable(dataframes); // Expect the first query to still return valid results expect(transformedTableDataFrames[0].fields.length).toBe(4); expect(transformedTableDataFrames[0].fields[0].name).toBe('Time'); expect(transformedTableDataFrames[0].fields[1].name).toBe('label1'); expect(transformedTableDataFrames[0].fields[1].values[0]).toBe(value1); expect(transformedTableDataFrames[0].fields[2].name).toBe('label2'); expect(transformedTableDataFrames[0].fields[2].values[0]).toBe(value2); expect(transformedTableDataFrames[0].fields[3].name).toBe('Value #A'); // Expect the invalid/empty results not to throw an error and to return empty arrays expect(transformedTableDataFrames[1].fields[1].labels).toBe(undefined); expect(transformedTableDataFrames[1].fields[1].name).toBe('Value #B'); expect(transformedTableDataFrames[1].fields[1].values).toEqual([]); expect(transformedTableDataFrames[1].fields[0].values).toEqual([]); }); it('transforms dataframes with metadata resolving from their refIds', () => { const value1 = 'value1'; const value2 = 'value2'; const executedQueryForRefA = 'Expr: avg_over_time(access_evaluation_duration_bucket[15s])\nStep: 15s'; const executedQueryForRefB = 'Expr: avg_over_time(access_evaluation_duration_bucket[5m])\nStep: 15s'; const dataframes = [ createDataFrame({ refId: 'A', meta: { typeVersion: [0, 1], custom: { resultType: 'vector', }, executedQueryString: executedQueryForRefA, }, fields: [ { name: 'time', type: FieldType.time, values: [6, 5, 4] }, { name: 'value', type: FieldType.number, values: [6, 5, 4], labels: { label1: value1, label2: value2 }, }, ], }), createDataFrame({ refId: 'B', meta: { typeVersion: [0, 1], custom: { resultType: 'vector', }, executedQueryString: executedQueryForRefB, }, fields: [ { name: 'time', type: FieldType.time, values: [6, 5, 4] }, { name: 'value', type: FieldType.number, values: [6, 5, 4], labels: { label1: value1, label2: value2 }, }, ], }), ]; const transformedTableDataFrames = transformDFToTable(dataframes); expect(transformedTableDataFrames[0].meta).toBeTruthy(); expect(transformedTableDataFrames[1].meta).toBeTruthy(); expect(transformedTableDataFrames[0].meta?.executedQueryString).toEqual(executedQueryForRefA); expect(transformedTableDataFrames[1].meta?.executedQueryString).toEqual(executedQueryForRefB); }); }); describe('transform', () => { const options: any = { target: {}, query: {} }; describe('When nothing is returned', () => { it('should return empty array', () => { const response = { status: 'success', data: { resultType: '', result: null, }, }; const series = transform({ data: response } as any, options); expect(series).toEqual([]); }); it('should return empty array', () => { const response = { status: 'success', data: { resultType: '', result: null, }, }; const result = transform({ data: response } as any, { ...options, target: { format: 'table' } }); expect(result).toHaveLength(0); }); }); describe('When resultFormat is table', () => { const response = { status: 'success', data: { resultType: 'matrix', result: [ { metric: { __name__: 'test', job: 'testjob' }, values: [ [1443454528, '3846'], [1443454530, '3848'], ], }, { metric: { __name__: 'test2', instance: 'localhost:8080', job: 'otherjob', }, values: [ [1443454529, '3847'], [1443454531, '3849'], ], }, ], }, }; it('should return data frame', () => { const result = transform({ data: response } as any, { ...options, target: { responseListLength: 0, refId: 'A', format: 'table', }, }); expect(result[0].fields[0].values).toEqual([1443454528000, 1443454530000, 1443454529000, 1443454531000]); expect(result[0].fields[0].name).toBe('Time'); expect(result[0].fields[0].type).toBe(FieldType.time); expect(result[0].fields[1].values).toEqual(['test', 'test', 'test2', 'test2']); expect(result[0].fields[1].name).toBe('__name__'); expect(result[0].fields[1].config.filterable).toBe(true); expect(result[0].fields[1].type).toBe(FieldType.string); expect(result[0].fields[2].values).toEqual(['', '', 'localhost:8080', 'localhost:8080']); expect(result[0].fields[2].name).toBe('instance'); expect(result[0].fields[2].type).toBe(FieldType.string); expect(result[0].fields[3].values).toEqual(['testjob', 'testjob', 'otherjob', 'otherjob']); expect(result[0].fields[3].name).toBe('job'); expect(result[0].fields[3].type).toBe(FieldType.string); expect(result[0].fields[4].values).toEqual([3846, 3848, 3847, 3849]); expect(result[0].fields[4].name).toEqual('Value'); expect(result[0].fields[4].type).toBe(FieldType.number); expect(result[0].refId).toBe('A'); }); it('should include refId if response count is more than 2', () => { const result = transform({ data: response } as any, { ...options, target: { refId: 'B', format: 'table', }, responseListLength: 2, }); expect(result[0].fields[4].name).toEqual('Value #B'); }); }); describe('When resultFormat is table and instant = true', () => { const response = { status: 'success', data: { resultType: 'vector', result: [ { metric: { __name__: 'test', job: 'testjob' }, value: [1443454528, '3846'], }, ], }, }; it('should return data frame', () => { const result = transform({ data: response } as any, { ...options, target: { format: 'table' } }); expect(result[0].fields[0].values).toEqual([1443454528000]); expect(result[0].fields[0].name).toBe('Time'); expect(result[0].fields[1].values).toEqual(['test']); expect(result[0].fields[1].name).toBe('__name__'); expect(result[0].fields[2].values).toEqual(['testjob']); expect(result[0].fields[2].name).toBe('job'); expect(result[0].fields[3].values).toEqual([3846]); expect(result[0].fields[3].name).toEqual('Value'); }); it('should return le label values parsed as numbers', () => { const response = { status: 'success', data: { resultType: 'vector', result: [ { metric: { le: '102' }, value: [1594908838, '0'], }, ], }, }; const result = transform({ data: response } as any, { ...options, target: { format: 'table' } }); expect(result[0].fields[1].values).toEqual([102]); expect(result[0].fields[1].type).toEqual(FieldType.number); }); }); describe('When instant = true', () => { const response = { status: 'success', data: { resultType: 'vector', result: [ { metric: { __name__: 'test', job: 'testjob' }, value: [1443454528, '3846'], }, ], }, }; it('should return data frame', () => { const result: DataFrame[] = transform({ data: response } as any, { ...options, query: { instant: true } }); expect(result[0].name).toBe('test{job="testjob"}'); }); }); describe('When resultFormat is heatmap', () => { const getResponse = (result: any) => ({ status: 'success', data: { resultType: 'matrix', result, }, }); const options = { format: 'heatmap', start: 1445000010, end: 1445000030, legendFormat: '{{le}}', }; it('should convert cumulative histogram to regular', () => { const response = getResponse([ { metric: { __name__: 'test', job: 'testjob', le: '1' }, values: [ [1445000010, '10'], [1445000020, '10'], [1445000030, '0'], ], }, { metric: { __name__: 'test', job: 'testjob', le: '2' }, values: [ [1445000010, '20'], [1445000020, '10'], [1445000030, '30'], ], }, { metric: { __name__: 'test', job: 'testjob', le: '3' }, values: [ [1445000010, '30'], [1445000020, '10'], [1445000030, '40'], ], }, ]); const result = transform({ data: response } as any, { query: options, target: options } as any); expect(result[0].fields[0].values).toEqual([1445000010000, 1445000020000, 1445000030000]); expect(result[0].fields[1].values).toEqual([10, 10, 0]); expect(result[0].fields[2].values).toEqual([10, 0, 30]); expect(result[0].fields[3].values).toEqual([10, 0, 10]); }); it('should handle missing datapoints', () => { const response = getResponse([ { metric: { __name__: 'test', job: 'testjob', le: '1' }, values: [ [1445000010, '1'], [1445000020, '2'], ], }, { metric: { __name__: 'test', job: 'testjob', le: '2' }, values: [ [1445000010, '2'], [1445000020, '5'], [1445000030, '1'], ], }, { metric: { __name__: 'test', job: 'testjob', le: '3' }, values: [ [1445000010, '3'], [1445000020, '7'], ], }, ]); const result = transform({ data: response } as any, { query: options, target: options } as any); expect(result[0].fields[1].values).toEqual([1, 2]); expect(result[0].fields[2].values).toEqual([1, 3, 1]); expect(result[0].fields[3].values).toEqual([1, 2]); }); }); describe('When the response is a matrix', () => { it('should have labels with the value field', () => { const response = { status: 'success', data: { resultType: 'matrix', result: [ { metric: { __name__: 'test', job: 'testjob', instance: 'testinstance' }, values: [ [0, '10'], [1, '10'], [2, '0'], ], }, ], }, }; const result: DataFrame[] = transform({ data: response } as any, { ...options, }); expect(result[0].fields[1].labels).toBeDefined(); expect(result[0].fields[1].labels?.instance).toBe('testinstance'); expect(result[0].fields[1].labels?.job).toBe('testjob'); }); it('should transform into a data frame', () => { const response = { status: 'success', data: { resultType: 'matrix', result: [ { metric: { __name__: 'test', job: 'testjob' }, values: [ [0, '10'], [1, '10'], [2, '0'], ], }, ], }, }; const result: DataFrame[] = transform({ data: response } as any, { ...options, query: { start: 0, end: 2, }, }); expect(result[0].fields[0].values).toEqual([0, 1000, 2000]); expect(result[0].fields[1].values).toEqual([10, 10, 0]); expect(result[0].name).toBe('test{job="testjob"}'); }); it('should fill null values', () => { const result = transform({ data: matrixResponse } as any, { ...options, query: { step: 1, start: 0, end: 2 }, }); expect(result[0].fields[0].values).toEqual([0, 1000, 2000]); expect(result[0].fields[1].values).toEqual([null, 10, 0]); }); it('should use __name__ label as series name', () => { const result = transform({ data: matrixResponse } as any, { ...options, query: { step: 1, start: 0, end: 2, }, }); expect(result[0].name).toEqual('test{job="testjob"}'); }); it('should use query as series name when __name__ is not available and metric is empty', () => { const response = { status: 'success', data: { resultType: 'matrix', result: [ { metric: {}, values: [[0, '10']], }, ], }, }; const expr = 'histogram_quantile(0.95, sum(rate(tns_request_duration_seconds_bucket[5m])) by (le))'; const result = transform({ data: response } as any, { ...options, query: { step: 1, start: 0, end: 2, expr, }, }); expect(result[0].name).toEqual(expr); }); it('should set frame name to undefined if no __name__ label but there are other labels', () => { const response = { status: 'success', data: { resultType: 'matrix', result: [ { metric: { job: 'testjob' }, values: [ [1, '10'], [2, '0'], ], }, ], }, }; const result = transform({ data: response } as any, { ...options, query: { step: 1, start: 0, end: 2, }, }); expect(result[0].name).toBe('{job="testjob"}'); }); it('should not set displayName for ValueFields', () => { const result = transform({ data: matrixResponse } as any, options); expect(result[0].fields[1].config.displayName).toBeUndefined(); expect(result[0].fields[1].config.displayNameFromDS).toBe('test{job="testjob"}'); }); it('should align null values with step', () => { const response = { status: 'success', data: { resultType: 'matrix', result: [ { metric: { __name__: 'test', job: 'testjob' }, values: [ [4, '10'], [8, '10'], ], }, ], }, }; const result = transform({ data: response } as any, { ...options, query: { step: 2, start: 0, end: 8 } }); expect(result[0].fields[0].values).toEqual([0, 2000, 4000, 6000, 8000]); expect(result[0].fields[1].values).toEqual([null, null, 10, null, 10]); }); }); describe('When infinity values are returned', () => { describe('When resultType is scalar', () => { const response = { status: 'success', data: { resultType: 'scalar', result: [1443454528, '+Inf'], }, }; it('should correctly parse values', () => { const result: DataFrame[] = transform({ data: response } as any, { ...options, target: { format: 'table' }, }); expect(result[0].fields[1].values).toEqual([Number.POSITIVE_INFINITY]); }); }); describe('When resultType is vector', () => { const response = { status: 'success', data: { resultType: 'vector', result: [ { metric: { __name__: 'test', job: 'testjob' }, value: [1443454528, '+Inf'], }, { metric: { __name__: 'test', job: 'testjob' }, value: [1443454528, '-Inf'], }, ], }, }; describe('When format is table', () => { it('should correctly parse values', () => { const result: DataFrame[] = transform({ data: response } as any, { ...options, target: { format: 'table' }, }); expect(result[0].fields[3].values).toEqual([Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY]); }); }); }); }); const exemplarsResponse = { status: 'success', data: [ { seriesLabels: { __name__: 'test' }, exemplars: [ { timestamp: 1610449069.957, labels: { traceID: '5020b5bc45117f07' }, value: 0.002074123, }, ], }, ], }; describe('When the response is exemplar data', () => { it('should return as an data frame with a dataTopic annotations', () => { const result = transform({ data: exemplarsResponse } as any, options); expect(result[0].meta?.dataTopic).toBe('annotations'); expect(result[0].fields.length).toBe(4); // __name__, traceID, Time, Value expect(result[0].length).toBe(1); }); it('should return with an empty array when data is empty', () => { const result = transform( { data: { status: 'success', data: [], }, } as any, options ); expect(result).toHaveLength(0); }); it('should remove exemplars that are too close to each other', () => { const response = { status: 'success', data: [ { exemplars: [ { timestamp: 1610449070.0, value: 5, }, { timestamp: 1610449070.0, value: 1, }, { timestamp: 1610449070.5, value: 13, }, { timestamp: 1610449070.3, value: 20, }, ], }, ], }; /** * the standard deviation for the above values is 8.4 this means that we show the highest * value (20) and then the next value should be 2 times the standard deviation which is 1 **/ const result = transform({ data: response } as any, options); expect(result[0].length).toBe(2); }); describe('data link', () => { it('should be added to the field if found with url', () => { const result = transform({ data: exemplarsResponse } as any, { ...options, exemplarTraceIdDestinations: [{ name: 'traceID', url: 'http://localhost' }], }); expect(result[0].fields.some((f) => f.config.links?.length)).toBe(true); }); it('should be added to the field if found with internal link', () => { const result = transform({ data: exemplarsResponse } as any, { ...options, exemplarTraceIdDestinations: [{ name: 'traceID', datasourceUid: 'jaeger' }], }); expect(result[0].fields.some((f) => f.config.links?.length)).toBe(true); }); it('should not add link if exemplarTraceIdDestinations is not configured', () => { const result = transform({ data: exemplarsResponse } as any, options); expect(result[0].fields.some((f) => f.config.links?.length)).toBe(false); }); it('should not add a datalink with an error when exemplarTraceIdDestinations is not configured', () => { const testOptions: any = { target: {}, query: {}, exemplarTraceIdDestinations: [ { name: 'traceID', datasourceUid: 'unknown', }, ], }; const result = transform({ data: exemplarsResponse } as any, testOptions); const traceField = result[0].fields.find((f) => f.name === 'traceID'); expect(traceField).toBeDefined(); expect(traceField!.config.links?.length).toBe(0); }); }); }); }); });