diff --git a/packages/grafana-prometheus/src/datasource.test.ts b/packages/grafana-prometheus/src/datasource.test.ts index dd94b4be299..08104df79b3 100644 --- a/packages/grafana-prometheus/src/datasource.test.ts +++ b/packages/grafana-prometheus/src/datasource.test.ts @@ -552,7 +552,7 @@ describe('PrometheusDatasource', () => { config.featureToggles.promQLScope = undefined; }); - it('should call replace function 2 times', () => { + it('should call replace function 3 times', () => { const query: PromQuery = { expr: 'test{job="testjob"}', format: 'time_series', @@ -563,7 +563,7 @@ describe('PrometheusDatasource', () => { replaceMock.mockReturnValue(interval); const queries = ds.interpolateVariablesInQueries([query], { Interval: { text: interval, value: interval } }); - expect(templateSrvStub.replace).toBeCalledTimes(2); + expect(templateSrvStub.replace).toBeCalledTimes(3); expect(queries[0].interval).toBe(interval); }); @@ -753,6 +753,55 @@ describe('PrometheusDatasource', () => { const result = ds.applyTemplateVariables(query, {}, filters); expect(result).toMatchObject({ expr: 'test{job="99", k1="v1", k2!="v2"} > 99' }); }); + + it('should replace variables in ad-hoc filters', () => { + const searchPattern = /\$A/g; + replaceMock.mockImplementation((a: string) => a?.replace(searchPattern, '99') ?? a); + + const query = { + expr: 'test', + refId: 'A', + }; + const filters = [ + { + key: 'job', + operator: '=~', + value: '$A', + }, + ]; + + const result = ds.applyTemplateVariables(query, {}, filters); + expect(result).toMatchObject({ expr: 'test{job=~"99"}' }); + }); + + it('should replace variables in adhoc filters on backend when promQLScope is enabled', () => { + config.featureToggles.promQLScope = true; + const searchPattern = /\$A/g; + replaceMock.mockImplementation((a: string) => a?.replace(searchPattern, '99') ?? a); + + const query = { + expr: 'test', + refId: 'A', + }; + const filters = [ + { + key: 'job', + operator: '=~', + value: '$A', + }, + ]; + const result = ds.applyTemplateVariables(query, {}, filters); + expect(result).toMatchObject({ + expr: 'test', + adhocFilters: [ + { + key: 'job', + operator: 'regex-match', + value: '99', + }, + ], + }); + }); }); describe('metricFindQuery', () => { diff --git a/packages/grafana-prometheus/src/datasource.ts b/packages/grafana-prometheus/src/datasource.ts index ac2e958eccf..34688f47465 100644 --- a/packages/grafana-prometheus/src/datasource.ts +++ b/packages/grafana-prometheus/src/datasource.ts @@ -659,14 +659,19 @@ export class PrometheusDatasource if (queries && queries.length) { expandedQueries = queries.map((query) => { const interpolatedQuery = this.templateSrv.replace(query.expr, scopedVars, this.interpolateQueryExpr); + const replacedInterpolatedQuery = config.featureToggles.promQLScope + ? interpolatedQuery + : this.templateSrv.replace( + this.enhanceExprWithAdHocFilters(filters, interpolatedQuery), + scopedVars, + this.interpolateQueryExpr + ); const expandedQuery = { ...query, ...(config.featureToggles.promQLScope ? { adhocFilters: this.generateScopeFilters(filters) } : {}), datasource: this.getRef(), - expr: config.featureToggles.promQLScope - ? interpolatedQuery - : this.enhanceExprWithAdHocFilters(filters, interpolatedQuery), + expr: replacedInterpolatedQuery, interval: this.templateSrv.replace(query.interval, scopedVars), }; @@ -824,7 +829,11 @@ export class PrometheusDatasource return []; } - return filters.map((f) => ({ ...f, operator: scopeFilterOperatorMap[f.operator] })); + return filters.map((f) => ({ + ...f, + value: this.templateSrv.replace(f.value, {}, this.interpolateQueryExpr), + operator: scopeFilterOperatorMap[f.operator], + })); } enhanceExprWithAdHocFilters(filters: AdHocVariableFilter[] | undefined, expr: string) { @@ -865,12 +874,21 @@ export class PrometheusDatasource }; // interpolate expression + + // We need a first replace to evaluate variables before applying adhoc filters + // This is required for an expression like `metric > $VAR` where $VAR is a float to which we must not add adhoc filters const expr = this.templateSrv.replace(target.expr, variables, this.interpolateQueryExpr); + // Apply ad-hoc filters + // When ad-hoc filters are applied, we replace again the variables in case the ad-hoc filters also reference a variable + const exprWithAdhoc = config.featureToggles.promQLScope + ? expr + : this.templateSrv.replace(this.enhanceExprWithAdHocFilters(filters, expr), variables, this.interpolateQueryExpr); + return { ...target, ...(config.featureToggles.promQLScope ? { adhocFilters: this.generateScopeFilters(filters) } : {}), - expr: config.featureToggles.promQLScope ? expr : this.enhanceExprWithAdHocFilters(filters, expr), + expr: exprWithAdhoc, interval: this.templateSrv.replace(target.interval, variables), legendFormat: this.templateSrv.replace(target.legendFormat, variables), };