From 66fb4d5f1ab34148419d2565b648fc887f3fd615 Mon Sep 17 00:00:00 2001 From: Matias Chomicki Date: Fri, 19 Jul 2024 16:26:06 +0200 Subject: [PATCH] Combine responses: add support for frames with repeated field names (#90650) * Combine responses: add support for frames with repeated field names * Formatting --- .../src/combineResponses.test.ts | 141 ++++++++++++++++++ .../src/combineResponses.ts | 18 ++- 2 files changed, 156 insertions(+), 3 deletions(-) diff --git a/packages/grafana-o11y-ds-frontend/src/combineResponses.test.ts b/packages/grafana-o11y-ds-frontend/src/combineResponses.test.ts index 4b7424ca394..5292420cb8f 100644 --- a/packages/grafana-o11y-ds-frontend/src/combineResponses.test.ts +++ b/packages/grafana-o11y-ds-frontend/src/combineResponses.test.ts @@ -525,6 +525,147 @@ describe('combineResponses', () => { data: [metricFrameA, metricFrameB], }); }); + + it('when fields with the same name are present, uses labels to find the right field to combine', () => { + const { metricFrameA, metricFrameB } = getMockFrames(); + + metricFrameA.fields.push({ + name: 'Value', + type: FieldType.number, + config: {}, + values: [9, 8], + labels: { + test: 'true', + }, + }); + metricFrameB.fields.push({ + name: 'Value', + type: FieldType.number, + config: {}, + values: [11, 10], + labels: { + test: 'true', + }, + }); + + const responseA: DataQueryResponse = { + data: [metricFrameA], + }; + const responseB: DataQueryResponse = { + data: [metricFrameB], + }; + + expect(combineResponses(responseA, responseB)).toEqual({ + data: [ + { + fields: [ + { + config: {}, + name: 'Time', + type: 'time', + values: [1000000, 2000000, 3000000, 4000000], + }, + { + config: {}, + name: 'Value', + type: 'number', + values: [6, 7, 5, 4], + labels: { + level: 'debug', + }, + }, + { + config: {}, + name: 'Value', + type: 'number', + values: [11, 10, 9, 8], + labels: { + test: 'true', + }, + }, + ], + length: 4, + meta: { + type: 'timeseries-multi', + stats: [ + { + displayName: 'Summary: total bytes processed', + unit: 'decbytes', + value: 33, + }, + ], + }, + refId: 'A', + }, + ], + }); + }); + + it('when fields with the same name are present and labels are not present, falls back to indexes', () => { + const { metricFrameA, metricFrameB } = getMockFrames(); + + delete metricFrameA.fields[1].labels; + delete metricFrameB.fields[1].labels; + + metricFrameA.fields.push({ + name: 'Value', + type: FieldType.number, + config: {}, + values: [9, 8], + }); + metricFrameB.fields.push({ + name: 'Value', + type: FieldType.number, + config: {}, + values: [11, 10], + }); + + const responseA: DataQueryResponse = { + data: [metricFrameA], + }; + const responseB: DataQueryResponse = { + data: [metricFrameB], + }; + + expect(combineResponses(responseA, responseB)).toEqual({ + data: [ + { + fields: [ + { + config: {}, + name: 'Time', + type: 'time', + values: [1000000, 2000000, 3000000, 4000000], + }, + { + config: {}, + name: 'Value', + type: 'number', + values: [6, 7, 5, 4], + }, + { + config: {}, + name: 'Value', + type: 'number', + values: [11, 10, 9, 8], + }, + ], + length: 4, + meta: { + type: 'timeseries-multi', + stats: [ + { + displayName: 'Summary: total bytes processed', + unit: 'decbytes', + value: 33, + }, + ], + }, + refId: 'A', + }, + ], + }); + }); }); describe('combinePanelData', () => { diff --git a/packages/grafana-o11y-ds-frontend/src/combineResponses.ts b/packages/grafana-o11y-ds-frontend/src/combineResponses.ts index eb7925ce90c..02613fad394 100644 --- a/packages/grafana-o11y-ds-frontend/src/combineResponses.ts +++ b/packages/grafana-o11y-ds-frontend/src/combineResponses.ts @@ -66,9 +66,7 @@ function combineFrames(dest: DataFrame, source: DataFrame) { } // Index is not reliable when frames have disordered fields, or an extra/missing field, so we find them by name. // If the field has no name, we fallback to the old index version. - const sourceField = dest.fields[i].name - ? source.fields.find((f) => f.name === dest.fields[i].name) - : source.fields[i]; + const sourceField = findSourceField(dest.fields[i], source.fields, i); if (!sourceField) { continue; } @@ -85,6 +83,20 @@ function combineFrames(dest: DataFrame, source: DataFrame) { }; } +function findSourceField(referenceField: Field, sourceFields: Field[], index: number) { + const candidates = sourceFields.filter((f) => f.name === referenceField.name); + + if (candidates.length === 1) { + return candidates[0]; + } + + if (referenceField.labels) { + return candidates.find((candidate) => shallowCompare(referenceField.labels ?? {}, candidate.labels ?? {})); + } + + return sourceFields[index]; +} + const TOTAL_BYTES_STAT = 'Summary: total bytes processed'; // This is specific for Loki function getCombinedMetadataStats(