From 47a8d79948d46837f26e14096976527485dbd9ca Mon Sep 17 00:00:00 2001 From: dualfroz Date: Sat, 17 Oct 2026 03:36:27 +0200 Subject: [PATCH] Transformations: Fix field named Time matching all time fields The byName and byNames matchers fall back to matching any time field when the configured name is Time or time, so configs keep working after the dataplane rename. The fallback also kicked in when the frame already had a field with that name, so hiding `time` in Organize fields hid every time field and Filter by name could not drop the other ones. The fallback now only applies when no field in the frame has that name or display name. --- .../matchers/nameMatcher.test.ts | 30 +++++++++++++++++++ .../transformations/matchers/nameMatcher.ts | 12 ++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/packages/grafana-data/src/transformations/matchers/nameMatcher.test.ts b/packages/grafana-data/src/transformations/matchers/nameMatcher.test.ts index 00b9d44..95b4daa 100644 --- a/packages/grafana-data/src/transformations/matchers/nameMatcher.test.ts +++ b/packages/grafana-data/src/transformations/matchers/nameMatcher.test.ts @@ -456,6 +456,36 @@ it('Support fallback multiple names matchers', () => { expect(matcher(frame.fields[1], frame, [])).toBeFalsy(); }); +it('Should only match the field named time when the frame has more time fields', () => { + const frame: DataFrame = { + fields: [ + { name: 'time', type: FieldType.time, config: {}, values: [1, 2] }, + { name: 'Shifted Time', type: FieldType.time, config: {}, values: [3, 4] }, + { name: 'A-series', type: FieldType.number, config: {}, values: [1, 2] }, + ], + length: 2, + }; + + const nameMatcher = getFieldMatcher({ + id: FieldMatcherID.byName, + options: 'time', + }); + expect(nameMatcher(frame.fields[0], frame, [frame])).toBeTruthy(); + expect(nameMatcher(frame.fields[1], frame, [frame])).toBeFalsy(); + expect(nameMatcher(frame.fields[2], frame, [frame])).toBeFalsy(); + + const namesMatcher = getFieldMatcher({ + id: FieldMatcherID.byNames, + options: { + mode: ByNamesMatcherMode.include, + names: ['time', 'A-series'], + }, + }); + expect(namesMatcher(frame.fields[0], frame, [frame])).toBeTruthy(); + expect(namesMatcher(frame.fields[1], frame, [frame])).toBeFalsy(); + expect(namesMatcher(frame.fields[2], frame, [frame])).toBeTruthy(); +}); + describe('Fields returned by query with refId', () => { it('Match all fields in frame with refId: A', () => { const data = [ diff --git a/packages/grafana-data/src/transformations/matchers/nameMatcher.ts b/packages/grafana-data/src/transformations/matchers/nameMatcher.ts index 2ab10da..03f1739 100644 --- a/packages/grafana-data/src/transformations/matchers/nameMatcher.ts +++ b/packages/grafana-data/src/transformations/matchers/nameMatcher.ts @@ -115,8 +115,16 @@ function fieldNameFallback(fields: Set) { ); }; } else if (fields.has('Time') || fields.has('time')) { - fallback = (field: Field, frame: DataFrame) => { - return frame.meta?.typeVersion == null && field.type === FieldType.time; + const timeNames = ['Time', 'time'].filter((name) => fields.has(name)); + fallback = (field: Field, frame: DataFrame, allFrames: DataFrame[]) => { + return ( + frame.meta?.typeVersion == null && + field.type === FieldType.time && + // A field that already carries the name is matched directly, so other time fields must not be pulled in + !frame.fields.some( + (f) => timeNames.includes(f.name) || timeNames.includes(getFieldDisplayName(f, frame, allFrames)) + ) + ); }; }