From ea7d5a618541fc1c07b74a3bfd62462b87a9a57c Mon Sep 17 00:00:00 2001 From: Tom Date: Fri, 18 Mar 2022 05:25:53 -0500 Subject: [PATCH] Transformers: Add template variable substitution (#44486) * Transformers: Add template variable replacement to transformer operator * Transformers: Add template variable replacement to calculateField transformer * Transformers: Add scopedVars to transformer template replacement * Transformers: Add calculateField template variable test * Transformers: Fix 'undefined' is not assignable to type 'string' * Transformers: Fix lint-frontend bug * Fix lint check * Fix lint --- .../src/transformations/transformDataFrame.ts | 4 +- .../transformers/calculateField.test.ts | 60 +++++++++++++++++++ .../transformers/calculateField.ts | 13 +++- .../grafana-data/src/types/transformations.ts | 10 +++- .../features/query/state/PanelQueryRunner.ts | 7 +++ 5 files changed, 90 insertions(+), 4 deletions(-) diff --git a/packages/grafana-data/src/transformations/transformDataFrame.ts b/packages/grafana-data/src/transformations/transformDataFrame.ts index 0616919bdae..7f46fee8e50 100644 --- a/packages/grafana-data/src/transformations/transformDataFrame.ts +++ b/packages/grafana-data/src/transformations/transformDataFrame.ts @@ -17,7 +17,9 @@ const getOperator = const options = { ...defaultOptions, ...config.options }; return source.pipe( - mergeMap((before) => of(before).pipe(info.transformation.operator(options), postProcessTransform(before, info))) + mergeMap((before) => + of(before).pipe(info.transformation.operator(options, config.replace), postProcessTransform(before, info)) + ) ); }; diff --git a/packages/grafana-data/src/transformations/transformers/calculateField.test.ts b/packages/grafana-data/src/transformations/transformers/calculateField.test.ts index d119af9bd19..5f0a6be0cd5 100644 --- a/packages/grafana-data/src/transformations/transformers/calculateField.test.ts +++ b/packages/grafana-data/src/transformations/transformers/calculateField.test.ts @@ -7,6 +7,7 @@ import { transformDataFrame } from '../transformDataFrame'; import { CalculateFieldMode, calculateFieldTransformer, ReduceOptions } from './calculateField'; import { DataFrameView } from '../../dataframe'; import { BinaryOperationID } from '../../utils'; +import { ScopedVars } from '../../types'; const seriesA = toDataFrame({ fields: [ @@ -219,4 +220,63 @@ describe('calculateField transformer w/ timeseries', () => { `); }); }); + + it('uses template variable substituion', async () => { + const cfg = { + id: DataTransformerID.calculateField, + options: { + alias: '$var1', + mode: CalculateFieldMode.BinaryOperation, + binary: { + left: 'A', + operator: BinaryOperationID.Add, + right: '$var2', + }, + replaceFields: true, + }, + replace: (target: string | undefined, scopedVars?: ScopedVars, format?: string | Function): string => { + if (!target) { + return ''; + } + const variables: ScopedVars = { + var1: { + value: 'Test', + text: 'Test', + }, + var2: { + value: 5, + text: '5', + }, + __interval: { + value: 10000, + text: '10000', + }, + }; + for (const key of Object.keys(variables)) { + if (target === `$${key}`) { + return variables[key].value + ''; + } + } + return target; + }, + }; + + await expect(transformDataFrame([cfg], [seriesA])).toEmitValuesWith((received) => { + const data = received[0]; + const filtered = data[0]; + const rows = new DataFrameView(filtered).toArray(); + expect(rows).toMatchInlineSnapshot(` + Array [ + Object { + "Test": 6, + "TheTime": 1000, + }, + Object { + "Test": 105, + "TheTime": 2000, + }, + ] + `); + }); + }); }); diff --git a/packages/grafana-data/src/transformations/transformers/calculateField.ts b/packages/grafana-data/src/transformations/transformers/calculateField.ts index 56758ae1454..e49920f803a 100644 --- a/packages/grafana-data/src/transformations/transformers/calculateField.ts +++ b/packages/grafana-data/src/transformations/transformers/calculateField.ts @@ -71,10 +71,12 @@ export const calculateFieldTransformer: DataTransformerInfo (outerSource) => { + operator: (options, replace) => (outerSource) => { const operator = options && options.timeSeries !== false ? ensureColumnsTransformer.operator(null) : noopTransformer.operator({}); + options.alias = replace ? replace(options.alias) : options.alias; + return outerSource.pipe( operator, map((data) => { @@ -84,7 +86,14 @@ export const calculateFieldTransformer: DataTransformerInfo extends RegistryItemWithOpt * Function that configures transformation and returns a transformer * @param options */ - operator: (options: TOptions) => MonoTypeOperatorFunction; + operator: ( + options: TOptions, + replace?: (target?: string, scopedVars?: ScopedVars, format?: string | Function) => string + ) => MonoTypeOperatorFunction; } /** @@ -42,6 +46,10 @@ export interface DataTransformerConfig { * Options to be passed to the transformer */ options: TOptions; + /** + * Function to apply template variable substitution to the DataTransformerConfig + */ + replace?: (target?: string, scopedVars?: ScopedVars, format?: string | Function) => string; } export type FrameMatcher = (frame: DataFrame) => boolean; diff --git a/public/app/features/query/state/PanelQueryRunner.ts b/public/app/features/query/state/PanelQueryRunner.ts index 6d9376bc552..90336b3f23b 100644 --- a/public/app/features/query/state/PanelQueryRunner.ts +++ b/public/app/features/query/state/PanelQueryRunner.ts @@ -183,6 +183,13 @@ export class PanelQueryRunner { return of(data); } + const replace = (option: string): string => { + return getTemplateSrv().replace(option, data?.request?.scopedVars); + }; + transformations.forEach((transform: any) => { + transform.replace = replace; + }); + return transformDataFrame(transformations, data.series).pipe(map((series) => ({ ...data, series }))); }) );