From c8bb01d1ad98497bb7f9b9f64a992a8f6dc149cf Mon Sep 17 00:00:00 2001 From: Todd Treece <360020+toddtreece@users.noreply.github.com> Date: Thu, 17 Feb 2022 10:50:22 -0500 Subject: [PATCH] Expressions: Add support for variables (#41778) --- .../about-expressions.md | 2 - .../expressions/ExpressionDatasource.test.ts | 37 +++++++++++++++++++ .../expressions/ExpressionDatasource.ts | 36 +++++++++++++++++- 3 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 public/app/features/expressions/ExpressionDatasource.test.ts diff --git a/docs/sources/panels/query-a-data-source/use-expressions-to-manipulate-data/about-expressions.md b/docs/sources/panels/query-a-data-source/use-expressions-to-manipulate-data/about-expressions.md index 3563b22e542..9085d2a400e 100644 --- a/docs/sources/panels/query-a-data-source/use-expressions-to-manipulate-data/about-expressions.md +++ b/docs/sources/panels/query-a-data-source/use-expressions-to-manipulate-data/about-expressions.md @@ -16,8 +16,6 @@ Expressions are primarily used by the new [Grafana 8 alerts]({{< relref "../../. > **Note:** Expressions do not work with legacy dashboard alerts. -> **Note:** Expressions do not work with dashboard variables. - Expressions are meant to augment data sources by enabling queries from different data sources to be combined or by providing operations unavailable in a data source. > **Note:** When possible, you should do data processing inside the data source. Copying data from storage to the Grafana server for processing is inefficient, so expressions are targeted at lightweight data processing. diff --git a/public/app/features/expressions/ExpressionDatasource.test.ts b/public/app/features/expressions/ExpressionDatasource.test.ts new file mode 100644 index 00000000000..139ec38c23c --- /dev/null +++ b/public/app/features/expressions/ExpressionDatasource.test.ts @@ -0,0 +1,37 @@ +import { DataSourceInstanceSettings } from '@grafana/data'; +import { backendSrv } from 'app/core/services/backend_srv'; +import { ExpressionDatasourceApi } from './ExpressionDatasource'; +import { ExpressionQueryType } from './types'; + +jest.mock('@grafana/runtime', () => ({ + ...(jest.requireActual('@grafana/runtime') as unknown as object), + getBackendSrv: () => backendSrv, + getTemplateSrv: () => ({ + replace: (val: string) => (val ? val.replace('$input', '10').replace('$window', '10s') : val), + }), +})); + +describe('ExpressionDatasourceApi', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('expression queries with template variables', () => { + it('should interpolate template variables in expression query', () => { + const ds = new ExpressionDatasourceApi({} as DataSourceInstanceSettings); + const query = ds.applyTemplateVariables( + { type: ExpressionQueryType.math, refId: 'B', expression: '$input + 5 + $A' }, + {} + ); + expect(query.expression).toBe('10 + 5 + $A'); + }); + it('should interpolate template variables in expression query', () => { + const ds = new ExpressionDatasourceApi({} as DataSourceInstanceSettings); + const query = ds.applyTemplateVariables( + { type: ExpressionQueryType.resample, refId: 'B', window: '$window' }, + {} + ); + expect(query.window).toBe('10s'); + }); + }); +}); diff --git a/public/app/features/expressions/ExpressionDatasource.ts b/public/app/features/expressions/ExpressionDatasource.ts index cfc5be8e385..15983e97bf5 100644 --- a/public/app/features/expressions/ExpressionDatasource.ts +++ b/public/app/features/expressions/ExpressionDatasource.ts @@ -1,8 +1,16 @@ -import { DataSourceInstanceSettings, DataSourcePluginMeta, PluginType } from '@grafana/data'; +import { + DataQueryRequest, + DataQueryResponse, + DataSourceInstanceSettings, + DataSourcePluginMeta, + PluginType, + ScopedVars, +} from '@grafana/data'; import { ExpressionQuery, ExpressionQueryType } from './types'; import { ExpressionQueryEditor } from './ExpressionQueryEditor'; -import { DataSourceWithBackend } from '@grafana/runtime'; +import { DataSourceWithBackend, getDataSourceSrv, getTemplateSrv } from '@grafana/runtime'; import { ExpressionDatasourceRef } from '@grafana/runtime/src/utils/DataSourceWithBackend'; +import { Observable, from, mergeMap } from 'rxjs'; /** * This is a singleton instance that just pretends to be a DataSource @@ -12,10 +20,34 @@ export class ExpressionDatasourceApi extends DataSourceWithBackend { + const templateSrv = getTemplateSrv(); + return { + ...query, + expression: templateSrv.replace(query.expression, scopedVars), + window: templateSrv.replace(query.window, scopedVars), + }; + } + getCollapsedText(query: ExpressionQuery) { return `Expression: ${query.type}`; } + query(request: DataQueryRequest): Observable { + let targets = request.targets.map(async (query: ExpressionQuery): Promise => { + const ds = await getDataSourceSrv().get(query.datasource); + + if (!ds.interpolateVariablesInQueries) { + return query; + } + + return ds?.interpolateVariablesInQueries([query], {})[0] as ExpressionQuery; + }); + + let sub = from(Promise.all(targets)); + return sub.pipe(mergeMap((t) => super.query({ ...request, targets: t }))); + } + newQuery(query?: Partial): ExpressionQuery { return { refId: '--', // Replaced with query