From 0f2284d5e6cc130ba4a7cf449aa00fddd8b93b82 Mon Sep 17 00:00:00 2001 From: Kristina Date: Mon, 7 Jul 2025 12:05:55 -0500 Subject: [PATCH] Transformations: Reduce > Reduce fields should not persist labels between reducers (#107590) Reduce fields should not persist labels updates between reducers --- .../transformers/reduce.test.ts | 60 ++++++++++++++++++- .../transformations/transformers/reduce.ts | 3 +- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/packages/grafana-data/src/transformations/transformers/reduce.test.ts b/packages/grafana-data/src/transformations/transformers/reduce.test.ts index bae31b5b8f9..f50072e0f30 100644 --- a/packages/grafana-data/src/transformations/transformers/reduce.test.ts +++ b/packages/grafana-data/src/transformations/transformers/reduce.test.ts @@ -8,7 +8,7 @@ import { notTimeFieldMatcher } from '../matchers/predicates'; import { transformDataFrame } from '../transformDataFrame'; import { DataTransformerID } from './ids'; -import { reduceFields, reduceTransformer, ReduceTransformerOptions } from './reduce'; +import { reduceFields, reduceTransformer, ReduceTransformerMode, ReduceTransformerOptions } from './reduce'; const seriesAWithSingleField = toDataFrame({ name: 'A', @@ -582,4 +582,62 @@ describe('Reducer Transformer', () => { `); }); }); + + it('reduce fields mode keeps distinct label values with multiple reducers', async () => { + const cfg: DataTransformerConfig = { + id: DataTransformerID.reduce, + options: { + mode: ReduceTransformerMode.ReduceFields, + reducers: [ReducerID.max, ReducerID.count], + labelsToFields: false, + }, + }; + + const seriesA = toDataFrame({ + length: 3, + fields: [{ name: 'value', config: {}, labels: { category: 'apple' }, type: FieldType.number, values: [3, 4, 5] }], + }); + + const seriesB = toDataFrame({ + fields: [ + { name: 'value', config: {}, labels: { category: 'orange' }, type: FieldType.number, values: [6, 7, 8] }, + ], + }); + + await expect(transformDataFrame([cfg], [seriesA, seriesB])).toEmitValuesWith((received) => { + const processed = received[0]; + + expect(processed.length).toEqual(2); + expect(processed[0].fields).toMatchInlineSnapshot(` + [ + { + "config": {}, + "labels": { + "category": "apple", + "reducer": "Max", + }, + "name": "value", + "state": undefined, + "type": "number", + "values": [ + 5, + ], + }, + { + "config": {}, + "labels": { + "category": "apple", + "reducer": "Count", + }, + "name": "value", + "state": undefined, + "type": "number", + "values": [ + 3, + ], + }, + ] + `); + }); + }); }); diff --git a/packages/grafana-data/src/transformations/transformers/reduce.ts b/packages/grafana-data/src/transformations/transformers/reduce.ts index dba2bc286e0..f9d2cd01d4c 100644 --- a/packages/grafana-data/src/transformations/transformers/reduce.ts +++ b/packages/grafana-data/src/transformations/transformers/reduce.ts @@ -230,10 +230,11 @@ export function reduceFields(data: DataFrame[], matcher: FieldMatcher, reducerId const value = results[reducer]; const copy = { ...field, + labels: { ...field.labels }, type: getFieldType(reducer, field), values: [value], + state: undefined, }; - copy.state = undefined; if (reducers.length > 1) { if (!copy.labels) { copy.labels = {};