From 9682022b1d0048ff58f10e84ff42d45d9063aaa4 Mon Sep 17 00:00:00 2001 From: Leon Sorokin Date: Wed, 17 Apr 2024 09:07:23 -0500 Subject: [PATCH] Transforms: Fix 'Filter data by values' removing rows in unrelated frames (#86087) --- .../transformers/filterByValue.test.ts | 81 +++++++++++++++++++ .../transformers/filterByValue.ts | 31 +++---- 2 files changed, 98 insertions(+), 14 deletions(-) diff --git a/packages/grafana-data/src/transformations/transformers/filterByValue.test.ts b/packages/grafana-data/src/transformations/transformers/filterByValue.test.ts index b61174d2e13..5d2ba4b10f9 100644 --- a/packages/grafana-data/src/transformations/transformers/filterByValue.test.ts +++ b/packages/grafana-data/src/transformations/transformers/filterByValue.test.ts @@ -26,6 +26,25 @@ const seriesAWithSingleField = toDataFrame({ ], }); +const multiSeriesWithSingleField = [ + toDataFrame({ + name: 'A', + length: 3, + fields: [ + { name: 'time', type: FieldType.time, values: [1000, 2000, 3000] }, + { name: 'value', type: FieldType.number, values: [1, 0, 1] }, + ], + }), + toDataFrame({ + name: 'B', + length: 3, + fields: [ + { name: 'time', type: FieldType.time, values: [5000, 6000, 7000] }, + { name: 'value', type: FieldType.number, values: [0, 1, 1] }, + ], + }), +]; + describe('FilterByValue transformer', () => { beforeAll(() => { mockTransformationsRegistry([filterByValueTransformer]); @@ -72,6 +91,68 @@ describe('FilterByValue transformer', () => { }); }); + it('should not cross frame boundaries', async () => { + const cfg: DataTransformerConfig = { + id: DataTransformerID.filterByValue, + options: { + type: FilterByValueType.exclude, + match: FilterByValueMatch.any, + filters: [ + { + fieldName: 'A value', + config: { + id: ValueMatcherID.equal, + options: { value: 0 }, + }, + }, + { + fieldName: 'B value', + config: { + id: ValueMatcherID.equal, + options: { value: 0 }, + }, + }, + ], + }, + }; + + await expect(transformDataFrame([cfg], multiSeriesWithSingleField)).toEmitValuesWith((received) => { + const processed = received[0]; + + expect(processed.length).toEqual(2); + + expect(processed[0].fields).toEqual([ + { + name: 'time', + type: FieldType.time, + values: [1000, 3000], + state: {}, + }, + { + name: 'value', + type: FieldType.number, + values: [1, 1], + state: {}, + }, + ]); + + expect(processed[1].fields).toEqual([ + { + name: 'time', + type: FieldType.time, + values: [6000, 7000], + state: {}, + }, + { + name: 'value', + type: FieldType.number, + values: [1, 1], + state: {}, + }, + ]); + }); + }); + it('should include values', async () => { const lowerOrEqual: MatcherConfig> = { id: ValueMatcherID.lowerOrEqual, diff --git a/packages/grafana-data/src/transformations/transformers/filterByValue.ts b/packages/grafana-data/src/transformations/transformers/filterByValue.ts index 766c98fcc2d..39e80a2d824 100644 --- a/packages/grafana-data/src/transformations/transformers/filterByValue.ts +++ b/packages/grafana-data/src/transformations/transformers/filterByValue.ts @@ -92,14 +92,16 @@ export const filterByValueTransformer: DataTransformerInfo { - if (!Array.isArray(data) || data.length === 0) { + if (data.length === 0) { return data; } - const rows = new Set(); + const processed: DataFrame[] = []; + + const fieldIndexByName = groupFieldIndexByName(data); for (const frame of data) { - const fieldIndexByName = groupFieldIndexByName(frame, data); + const rows = new Set(); let matchers; if (transformationsVariableSupport()) { @@ -135,13 +137,9 @@ export const filterByValueTransformer: DataTransformerInfo => { - return frame.fields.reduce((all: Record, field, fieldIndex) => { - const fieldName = getFieldDisplayName(field, frame, data); - all[fieldName] = fieldIndex; - return all; - }, {}); +const groupFieldIndexByName = (data: DataFrame[]) => { + const lookup: Record = {}; + + for (const frame of data) { + frame.fields.forEach((field, fieldIndex) => { + const fieldName = getFieldDisplayName(field, frame, data); + lookup[fieldName] = fieldIndex; + }); + } + + return lookup; };