diff --git a/packages/grafana-data/src/vector/SortedVector.ts b/packages/grafana-data/src/vector/SortedVector.ts index 0d66dff6ded..e2a318e95d4 100644 --- a/packages/grafana-data/src/vector/SortedVector.ts +++ b/packages/grafana-data/src/vector/SortedVector.ts @@ -23,4 +23,8 @@ export class SortedVector implements Vector { toJSON(): T[] { return vectorToArray(this); } + + getOrderArray(): number[] { + return this.order; + } } diff --git a/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx b/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx index 9b422fb5ad8..78203d51ba0 100644 --- a/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx +++ b/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx @@ -1,6 +1,6 @@ import React, { useMemo } from 'react'; -import { Field, PanelProps, getLinksSupplier } from '@grafana/data'; +import { Field, PanelProps } from '@grafana/data'; import { PanelDataErrorView } from '@grafana/runtime'; import { TooltipDisplayMode } from '@grafana/schema'; import { usePanelContext, TimeSeries, TooltipPlugin, ZoomPlugin, KeyboardPlugin } from '@grafana/ui'; @@ -14,7 +14,7 @@ import { ExemplarsPlugin } from './plugins/ExemplarsPlugin'; import { OutsideRangePlugin } from './plugins/OutsideRangePlugin'; import { ThresholdControlsPlugin } from './plugins/ThresholdControlsPlugin'; import { TimeSeriesOptions } from './types'; -import { getTimezones, prepareGraphableFields } from './utils'; +import { getTimezones, prepareGraphableFields, regenerateLinksSupplier } from './utils'; interface TimeSeriesPanelProps extends PanelProps {} @@ -65,15 +65,11 @@ export const TimeSeriesPanel: React.FC = ({ options={options} > {(config, alignedDataFrame) => { - alignedDataFrame.fields.forEach((field) => { - field.getLinks = getLinksSupplier( - alignedDataFrame, - field, - field.state!.scopedVars!, - replaceVariables, - timeZone - ); - }); + if ( + alignedDataFrame.fields.filter((f) => f.config.links !== undefined && f.config.links.length > 0).length > 0 + ) { + alignedDataFrame = regenerateLinksSupplier(alignedDataFrame, frames, replaceVariables, timeZone); + } return ( <> diff --git a/public/app/plugins/panel/timeseries/utils.test.ts b/public/app/plugins/panel/timeseries/utils.test.ts index 2eb3ca95674..9be6bde5c11 100644 --- a/public/app/plugins/panel/timeseries/utils.test.ts +++ b/public/app/plugins/panel/timeseries/utils.test.ts @@ -43,7 +43,7 @@ describe('prepare timeseries graph', () => { const frames = prepareGraphableFields(input, createTheme()); const out = frames![0]; - expect(out.fields.map((f) => f.name)).toEqual(['a', 'c', 'd']); + expect(out.fields.map((f) => f.name)).toEqual(['a', 'b', 'c', 'd']); const field = out.fields.find((f) => f.name === 'c'); expect(field?.display).toBeDefined(); diff --git a/public/app/plugins/panel/timeseries/utils.ts b/public/app/plugins/panel/timeseries/utils.ts index 241e775b1ff..f8008bb0c27 100644 --- a/public/app/plugins/panel/timeseries/utils.ts +++ b/public/app/plugins/panel/timeseries/utils.ts @@ -4,8 +4,11 @@ import { Field, FieldType, getDisplayProcessor, + getLinksSupplier, GrafanaTheme2, + InterpolateFunction, isBooleanUnit, + SortedVector, TimeRange, } from '@grafana/data'; import { GraphFieldConfig, LineInterpolation } from '@grafana/schema'; @@ -60,6 +63,14 @@ export function prepareGraphableFields( ), }; + fields.push(copy); + break; // ok + case FieldType.string: + copy = { + ...field, + values: new ArrayVector(field.values.toArray()), + }; + fields.push(copy); break; // ok case FieldType.boolean: @@ -123,3 +134,46 @@ export function getTimezones(timezones: string[] | undefined, defaultTimezone: s } return timezones.map((v) => (v?.length ? v : defaultTimezone)); } + +export function regenerateLinksSupplier( + alignedDataFrame: DataFrame, + frames: DataFrame[], + replaceVariables: InterpolateFunction, + timeZone: string +): DataFrame { + alignedDataFrame.fields.forEach((field) => { + const frameIndex = field.state?.origin?.frameIndex; + + if (frameIndex === undefined) { + return; + } + + const frame = frames[frameIndex]; + const tempFields: Field[] = []; + + /* check if field has sortedVector values + if it does, sort all string fields in the original frame by the order array already used for the field + otherwise just attach the fields to the temporary frame used to get the links + */ + for (const frameField of frame.fields) { + if (frameField.type === FieldType.string) { + if (field.values instanceof SortedVector) { + const copiedField = { ...frameField }; + copiedField.values = new SortedVector(frameField.values, field.values.getOrderArray()); + tempFields.push(copiedField); + } else { + tempFields.push(frameField); + } + } + } + + const tempFrame: DataFrame = { + fields: [...alignedDataFrame.fields, ...tempFields], + length: alignedDataFrame.fields.length + tempFields.length, + }; + + field.getLinks = getLinksSupplier(tempFrame, field, field.state!.scopedVars!, replaceVariables, timeZone); + }); + + return alignedDataFrame; +}