From 383148bcd11224353bb60832e7e7823d2409e8d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Jamr=C3=B3z?= Date: Wed, 29 Mar 2023 13:09:27 +0200 Subject: [PATCH] Glue: Fix creating duplicated data links created by Correlations (#65490) * Ensure correlations do not create duplicated data links * Remove correlation links before processing each correlation * Improve test coverage --- packages/grafana-data/src/types/dataLink.ts | 11 ++ .../app/features/correlations/utils.test.ts | 164 +++++++++++------- public/app/features/correlations/utils.ts | 7 +- 3 files changed, 116 insertions(+), 66 deletions(-) diff --git a/packages/grafana-data/src/types/dataLink.ts b/packages/grafana-data/src/types/dataLink.ts index d8f4c45cf9aec..0e11beec14731 100644 --- a/packages/grafana-data/src/types/dataLink.ts +++ b/packages/grafana-data/src/types/dataLink.ts @@ -12,6 +12,15 @@ export interface DataLinkClickEvent { e?: any; // mouse|react event } +/** + * Data Links can be created by data source plugins or correlations. + * Origin is set in DataLink object and indicates where the link was created. + */ +export enum DataLinkConfigOrigin { + Datasource = 'Datasource', + Correlations = 'Correlations', +} + /** * Link configuration. The values may contain variables that need to be * processed before showing the link to user. @@ -39,6 +48,8 @@ export interface DataLink { // more custom onClick behaviour if needed. // @internal and subject to change in future releases internal?: InternalDataLink; + + origin?: DataLinkConfigOrigin; } /** @internal */ diff --git a/public/app/features/correlations/utils.test.ts b/public/app/features/correlations/utils.test.ts index c090728204513..829afa8ba7a19 100644 --- a/public/app/features/correlations/utils.test.ts +++ b/public/app/features/correlations/utils.test.ts @@ -5,78 +5,40 @@ import { attachCorrelationsToDataFrames } from './utils'; describe('correlations utils', () => { it('attaches correlations defined in the configuration', () => { - const loki = { uid: 'loki-uid', name: 'loki' } as DataSourceInstanceSettings; - const elastic = { uid: 'elastic-uid', name: 'elastic' } as DataSourceInstanceSettings; - const prometheus = { uid: 'prometheus-uid', name: 'prometheus' } as DataSourceInstanceSettings; - - const refIdMap = { - 'Loki Query': loki.uid, - 'Elastic Query': elastic.uid, - 'Prometheus Query': prometheus.uid, - }; - - const testDataFrames: DataFrame[] = [ - toDataFrame({ - name: 'Loki Logs', - refId: 'Loki Query', - fields: [ - { name: 'line', values: [] }, - { name: 'traceId', values: [] }, - ], - }), - toDataFrame({ - name: 'Elastic Logs', - refId: 'Elastic Query', - fields: [ - { name: 'line', values: [] }, - { name: 'traceId', values: [] }, - ], - }), - toDataFrame({ - name: 'Prometheus Metrics', - refId: 'Prometheus Query', - fields: [{ name: 'value', type: FieldType.number, values: [1, 2, 3, 4, 5] }], - }), - ]; + const { testDataFrames, correlations, refIdMap, prometheus, elastic } = setup(); + attachCorrelationsToDataFrames(testDataFrames, correlations, refIdMap); - const correlations: CorrelationData[] = [ + // Loki line (no links) + expect(testDataFrames[0].fields[0].config.links).toHaveLength(0); + // Loki traceId (linked to Prometheus and Elastic) + expect(testDataFrames[0].fields[1].config.links).toHaveLength(2); + expect(testDataFrames[0].fields[1].config.links).toMatchObject([ { - uid: 'loki-to-prometheus', - label: 'logs to metrics', - source: loki, - target: prometheus, - config: { type: 'query', field: 'traceId', target: { expr: 'target Prometheus query' } }, + title: 'logs to metrics', + internal: { + datasourceUid: prometheus.uid, + datasourceName: prometheus.name, + query: { + expr: 'target Prometheus query', + }, + }, }, { - uid: 'prometheus-to-elastic', - label: 'metrics to logs', - source: prometheus, - target: elastic, - config: { type: 'query', field: 'value', target: { expr: 'target Elastic query' } }, - }, - ]; - - attachCorrelationsToDataFrames(testDataFrames, correlations, refIdMap); - - // Loki line (no links) - expect(testDataFrames[0].fields[0].config.links).toBeUndefined(); - // Loki traceId (linked to Prometheus) - expect(testDataFrames[0].fields[1].config.links).toHaveLength(1); - expect(testDataFrames[0].fields[1].config.links![0]).toMatchObject({ - title: 'logs to metrics', - internal: { - datasourceUid: prometheus.uid, - datasourceName: prometheus.name, - query: { - expr: 'target Prometheus query', + title: 'logs to logs', + internal: { + datasourceUid: elastic.uid, + datasourceName: elastic.name, + query: { + expr: 'target Elastic query', + }, }, }, - }); + ]); // Elastic line (no links) - expect(testDataFrames[1].fields[0].config.links).toBeUndefined(); + expect(testDataFrames[1].fields[0].config.links).toHaveLength(0); // Elastic traceId (no links) - expect(testDataFrames[1].fields[0].config.links).toBeUndefined(); + expect(testDataFrames[1].fields[0].config.links).toHaveLength(0); // Prometheus value (linked to Elastic) expect(testDataFrames[2].fields[0].config.links).toHaveLength(1); @@ -91,4 +53,80 @@ describe('correlations utils', () => { }, }); }); + + it('does not create duplicates when attaching links to the same data frame', () => { + const { testDataFrames, correlations, refIdMap } = setup(); + attachCorrelationsToDataFrames(testDataFrames, correlations, refIdMap); + attachCorrelationsToDataFrames(testDataFrames, correlations, refIdMap); + + // Loki traceId (linked to Prometheus and Elastic) + expect(testDataFrames[0].fields[1].config.links).toHaveLength(2); + // Elastic line (no links) + expect(testDataFrames[1].fields[0].config.links).toHaveLength(0); + // Prometheus value (linked to Elastic) + expect(testDataFrames[2].fields[0].config.links).toHaveLength(1); + }); }); + +function setup() { + const loki = { uid: 'loki-uid', name: 'loki' } as DataSourceInstanceSettings; + const elastic = { uid: 'elastic-uid', name: 'elastic' } as DataSourceInstanceSettings; + const prometheus = { uid: 'prometheus-uid', name: 'prometheus' } as DataSourceInstanceSettings; + + const refIdMap = { + 'Loki Query': loki.uid, + 'Elastic Query': elastic.uid, + 'Prometheus Query': prometheus.uid, + }; + + const testDataFrames: DataFrame[] = [ + toDataFrame({ + name: 'Loki Logs', + refId: 'Loki Query', + fields: [ + { name: 'line', values: [] }, + { name: 'traceId', values: [] }, + ], + }), + toDataFrame({ + name: 'Elastic Logs', + refId: 'Elastic Query', + fields: [ + { name: 'line', values: [] }, + { name: 'traceId', values: [] }, + ], + }), + toDataFrame({ + name: 'Prometheus Metrics', + refId: 'Prometheus Query', + fields: [{ name: 'value', type: FieldType.number, values: [1, 2, 3, 4, 5] }], + }), + ]; + + const correlations: CorrelationData[] = [ + { + uid: 'loki-to-prometheus', + label: 'logs to metrics', + source: loki, + target: prometheus, + config: { type: 'query', field: 'traceId', target: { expr: 'target Prometheus query' } }, + }, + // Test multiple correlations attached to the same field + { + uid: 'loki-to-elastic', + label: 'logs to logs', + source: loki, + target: elastic, + config: { type: 'query', field: 'traceId', target: { expr: 'target Elastic query' } }, + }, + { + uid: 'prometheus-to-elastic', + label: 'metrics to logs', + source: prometheus, + target: elastic, + config: { type: 'query', field: 'value', target: { expr: 'target Elastic query' } }, + }, + ]; + + return { testDataFrames, correlations, refIdMap, prometheus, elastic }; +} diff --git a/public/app/features/correlations/utils.ts b/public/app/features/correlations/utils.ts index 98bf36654f8a8..71f4f5732b959 100644 --- a/public/app/features/correlations/utils.ts +++ b/public/app/features/correlations/utils.ts @@ -1,4 +1,4 @@ -import { DataFrame } from '@grafana/data'; +import { DataFrame, DataLinkConfigOrigin } from '@grafana/data'; import { CorrelationData } from './useCorrelations'; @@ -31,10 +31,10 @@ export const attachCorrelationsToDataFrames = ( const decorateDataFrameWithInternalDataLinks = (dataFrame: DataFrame, correlations: CorrelationData[]) => { dataFrame.fields.forEach((field) => { + field.config.links = field.config.links?.filter((link) => link.origin !== DataLinkConfigOrigin.Correlations) || []; correlations.map((correlation) => { if (correlation.config?.field === field.name) { - field.config.links = field.config.links || []; - field.config.links.push({ + field.config.links!.push({ internal: { query: correlation.config?.target, datasourceUid: correlation.target.uid, @@ -43,6 +43,7 @@ const decorateDataFrameWithInternalDataLinks = (dataFrame: DataFrame, correlatio }, url: '', title: correlation.label || correlation.target.name, + origin: DataLinkConfigOrigin.Correlations, }); } });