InfluxDB: Ad hoc filters support for expressions (#109344)

* Support adhoc filters

* Tests
This commit is contained in:
Andreas Christou
2025-08-08 12:32:04 +01:00
committed by GitHub
parent 01c1a6ce5b
commit 67c75de942
2 changed files with 170 additions and 3 deletions
@@ -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([]);
});
});
@@ -221,7 +221,11 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
return this.templateSrv.containsTemplate(queryText);
}
interpolateVariablesInQueries(queries: InfluxQuery[], scopedVars: ScopedVars): InfluxQuery[] {
interpolateVariablesInQueries(
queries: InfluxQuery[],
scopedVars: ScopedVars,
filters?: AdHocVariableFilter[]
): InfluxQuery[] {
if (!queries || queries.length === 0) {
return [];
}
@@ -240,10 +244,19 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
};
}
const queryWithVariables = this.applyVariables(query, scopedVars, filters);
if (queryWithVariables.adhocFilters?.length) {
queryWithVariables.adhocFilters = (queryWithVariables.adhocFilters ?? []).map((af) => {
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),
};
});
}