From 67c75de94245cdfbc58512e2df54d4d26b8b21f6 Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Fri, 8 Aug 2025 13:32:04 +0200 Subject: [PATCH] InfluxDB: Ad hoc filters support for expressions (#109344) * Support adhoc filters * Tests --- .../datasource/influxdb/datasource.test.ts | 154 ++++++++++++++++++ .../plugins/datasource/influxdb/datasource.ts | 19 ++- 2 files changed, 170 insertions(+), 3 deletions(-) diff --git a/public/app/plugins/datasource/influxdb/datasource.test.ts b/public/app/plugins/datasource/influxdb/datasource.test.ts index 4633f20744a..12ae898d58d 100644 --- a/public/app/plugins/datasource/influxdb/datasource.test.ts +++ b/public/app/plugins/datasource/influxdb/datasource.test.ts @@ -419,3 +419,157 @@ describe('interpolateQueryExpr', () => { expect(result.adhocFilters![0].value).toBe(templateVarValue); }); }); + +describe('interpolateVariablesInQueries', () => { + const templateSrvStub = { + replace: jest.fn().mockImplementation((target: string) => { + if (target === '$database') { + return 'test_db'; + } + if (target === '$measurement') { + return 'cpu_usage'; + } + if (target === 'SELECT * FROM $measurement WHERE database = "$database"') { + return 'SELECT * FROM cpu_usage WHERE database = "test_db"'; + } + if (target === 'SELECT * FROM $measurement') { + return 'SELECT * FROM cpu_usage'; + } + if (target === '$server') { + return 'prod-server'; + } + return target; + }), + } as unknown as TemplateSrv; + let dsInfluxQL = getMockInfluxDS(getMockDSInstanceSettings(), templateSrvStub); + let dsFlux = getMockInfluxDS(getMockDSInstanceSettings({ version: InfluxVersion.Flux }), templateSrvStub); + let dsSQL = getMockInfluxDS(getMockDSInstanceSettings({ version: InfluxVersion.SQL }), templateSrvStub); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should return an empty array if there are no queries', () => { + const result = dsInfluxQL.interpolateVariablesInQueries([], {}, []); + expect(result).toEqual([]); + }); + + it('should interpolate template variables in query rawQuery (Flux)', () => { + const queries: InfluxQuery[] = [ + { + refId: 'A', + rawQuery: true, + query: 'SELECT * FROM $measurement WHERE database = "$database"', + } as InfluxQuery, + ]; + + const result = dsFlux.interpolateVariablesInQueries(queries, {}, []); + + expect(result).toHaveLength(1); + expect(result[0].query).toBe('SELECT * FROM cpu_usage WHERE database = "test_db"'); + }); + + it('should apply adhoc filters to queries (InfluxQL)', () => { + const queries: InfluxQuery[] = [ + { + refId: 'A', + rawQuery: false, + measurement: 'cpu', + tags: [], + } as InfluxQuery, + ]; + + const adhocFilters: AdHocVariableFilter[] = [ + { key: 'host', value: 'server1', operator: '=' }, + { key: 'region', value: 'us-east', operator: '=' }, + ]; + + const result = dsInfluxQL.interpolateVariablesInQueries(queries, {}, adhocFilters); + + expect(result).toHaveLength(1); + expect(result[0].adhocFilters).toEqual(adhocFilters); + }); + + it('should apply adhoc filters to queries (SQL)', () => { + const queries: InfluxQuery[] = [ + { + refId: 'A', + rawQuery: false, + measurement: 'cpu', + tags: [], + } as InfluxQuery, + ]; + + const adhocFilters: AdHocVariableFilter[] = [ + { key: 'host', value: 'server1', operator: '=' }, + { key: 'region', value: 'us-east', operator: '=' }, + ]; + + const result = dsSQL.interpolateVariablesInQueries(queries, {}, adhocFilters); + + expect(result).toHaveLength(1); + expect(result[0].adhocFilters).toEqual(adhocFilters); + }); + + it('should interpolate template variables in adhoc filter values (InfluxQL)', () => { + const templateSrvWithAdhoc = { + replace: jest.fn().mockImplementation((target: string) => { + if (target === '$server') { + return 'prod-server'; + } + return target; + }), + } as unknown as TemplateSrv; + + const ds = getMockInfluxDS(getMockDSInstanceSettings({ version: InfluxVersion.InfluxQL }), templateSrvWithAdhoc); + const queries: InfluxQuery[] = [ + { + refId: 'A', + rawQuery: false, + measurement: 'cpu', + tags: [], + } as InfluxQuery, + ]; + + const adhocFilters: AdHocVariableFilter[] = [{ key: 'host', value: '$server', operator: '=' }]; + + const result = ds.interpolateVariablesInQueries(queries, {}, adhocFilters); + + expect(result[0].adhocFilters![0].value).toBe('prod-server'); + }); + + it('should preserve existing tags when applying adhoc filters', () => { + const queries: InfluxQuery[] = [ + { + refId: 'A', + rawQuery: false, + measurement: 'cpu', + tags: [{ key: 'datacenter', value: 'dc1', operator: '=' }], + } as InfluxQuery, + ]; + + const adhocFilters: AdHocVariableFilter[] = [{ key: 'host', value: 'server1', operator: '=' }]; + + const result = dsInfluxQL.interpolateVariablesInQueries(queries, {}, adhocFilters); + + expect(result[0].tags?.length).toEqual(2); + expect(result[0].tags![0]).toEqual({ key: 'datacenter', value: 'dc1', operator: '=' }); + expect(result[0].tags![1]).toEqual({ key: 'host', value: 'server1', operator: '=' }); + expect(result[0].adhocFilters).toEqual(adhocFilters); + }); + + it('should handle empty adhoc filters array', () => { + const queries: InfluxQuery[] = [ + { + refId: 'A', + rawQuery: true, + query: 'SELECT * FROM cpu', + } as InfluxQuery, + ]; + + const result = dsInfluxQL.interpolateVariablesInQueries(queries, {}, []); + + expect(result).toHaveLength(1); + expect(result[0].adhocFilters).toEqual([]); + }); +}); diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts index a21031f20ab..b073f333fcd 100644 --- a/public/app/plugins/datasource/influxdb/datasource.ts +++ b/public/app/plugins/datasource/influxdb/datasource.ts @@ -221,7 +221,11 @@ export default class InfluxDatasource extends DataSourceWithBackend { + const { condition, ...asTag } = af; + asTag.value = this.templateSrv.replace(asTag.value ?? '', scopedVars); + return asTag; + }); + queryWithVariables.tags = [...(queryWithVariables.tags ?? []), ...queryWithVariables.adhocFilters]; + } + return { - ...query, + ...queryWithVariables, datasource: this.getRef(), - ...this.applyVariables(query, scopedVars), }; }); }