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 }))); }) );