Chore: Use TemplateSrv from @grafana/runtime in influxdb datasource (#87813)

use templatesrv from @grafana/runtime
This commit is contained in:
ismail simsek 2024-05-15 12:42:09 +02:00 committed by GitHub
parent 699c5bfe79
commit 4fef6ff30d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 31 additions and 330 deletions

View File

@ -1,32 +1,23 @@
import { of } from 'rxjs';
import { AdHocVariableFilter, DataSourceInstanceSettings, PluginType, ScopedVars } from '@grafana/data';
import { FetchResponse, getBackendSrv, setBackendSrv, VariableInterpolation } from '@grafana/runtime';
import { DataSourceInstanceSettings, PluginType, ScopedVars } from '@grafana/data';
import { FetchResponse, getBackendSrv, setBackendSrv, TemplateSrv, VariableInterpolation } from '@grafana/runtime';
import { TemplateSrv } from '../../../../features/templating/template_srv';
import InfluxDatasource from '../datasource';
import { InfluxOptions, InfluxVersion } from '../types';
const getAdhocFiltersMock = jest.fn().mockImplementation(() => []);
const replaceMock = jest.fn().mockImplementation((a: string, ...rest: unknown[]) => a);
export const templateSrvStub = {
getAdhocFilters: getAdhocFiltersMock,
replace: replaceMock,
} as unknown as TemplateSrv;
export const replaceMock = jest.fn().mockImplementation((a: string, ...rest: unknown[]) => a);
export const templateSrvStub = mockTemplateSrv(replaceMock);
export function mockTemplateSrv(
getAdhocFiltersMock: (datasourceName: string) => AdHocVariableFilter[],
replaceMock: (
rm: (
target?: string,
scopedVars?: ScopedVars,
format?: string | Function | undefined,
interpolations?: VariableInterpolation[]
) => string
) => string = replaceMock
): TemplateSrv {
return {
getAdhocFilters: getAdhocFiltersMock,
replace: replaceMock,
replace: rm,
} as unknown as TemplateSrv;
}
@ -42,7 +33,7 @@ export function mockBackendService(response: FetchResponse) {
export function getMockInfluxDS(
instanceSettings: DataSourceInstanceSettings<InfluxOptions> = getMockDSInstanceSettings(),
templateSrv: TemplateSrv = templateSrvStub
templateSrv: TemplateSrv = mockTemplateSrv(replaceMock)
): InfluxDatasource {
return new InfluxDatasource(instanceSettings, templateSrv);
}

View File

@ -19,6 +19,7 @@ export const mockInfluxQueryRequest = (targets?: QueryType[]): DataQueryRequest<
from: dateTime(0),
to: dateTime(10),
},
filters: [{ key: 'adhoc_key', value: 'adhoc_val', operator: '=' }],
requestId: '',
scopedVars: {},
startTime: 0,

View File

@ -1,18 +1,15 @@
import { lastValueFrom, of } from 'rxjs';
import { BackendSrvRequest } from '@grafana/runtime';
import { BackendSrvRequest, TemplateSrv } from '@grafana/runtime';
import config from 'app/core/config';
import { TemplateSrv } from '../../../features/templating/template_srv';
import { queryBuilder } from '../../../features/variables/shared/testing/builders';
import { getMockDSInstanceSettings, getMockInfluxDS, mockBackendService } from './__mocks__/datasource';
import { queryOptions } from './__mocks__/query';
import { mockInfluxQueryRequest, mockInfluxQueryWithTemplateVars } from './__mocks__/request';
import { getMockDSInstanceSettings, getMockInfluxDS, mockBackendService, replaceMock } from './__mocks__/datasource';
import { mockInfluxQueryRequest } from './__mocks__/request';
import { mockInfluxFetchResponse, mockMetricFindQueryResponse } from './__mocks__/response';
import { BROWSER_MODE_DISABLED_MESSAGE } from './constants';
import InfluxDatasource from './datasource';
import { InfluxQuery, InfluxVersion } from './types';
const fetchMock = mockBackendService(mockInfluxFetchResponse());
@ -142,24 +139,11 @@ describe('InfluxDataSource Frontend Mode [influxdbBackendMigration=false]', () =
// Update this after starting to use TemplateSrv from @grafana/runtime package
describe('adhoc variables', () => {
const adhocFilters = [
{
key: 'adhoc_key',
operator: '=',
value: 'adhoc_val',
condition: '',
},
];
const mockTemplateService = new TemplateSrv();
mockTemplateService.getAdhocFilters = jest.fn((_: string) => adhocFilters);
let ds = getMockInfluxDS(getMockDSInstanceSettings(), mockTemplateService);
let ds = getMockInfluxDS(getMockDSInstanceSettings());
it('query should contain the ad-hoc variable', () => {
ds.query(mockInfluxQueryRequest());
const expected = encodeURIComponent(
'SELECT mean("value") FROM "cpu" WHERE time >= 0ms and time <= 10ms AND "adhoc_key" = \'adhoc_val\' GROUP BY time($__interval) fill(null)'
);
expect(fetchMock.mock.calls[0][0].data).toBe(`q=${expected}`);
expect(replaceMock.mock.calls[0][0]).toBe('adhoc_val');
});
it('should make the fetch call for adhoc filter keys', () => {
@ -257,125 +241,6 @@ describe('InfluxDataSource Frontend Mode [influxdbBackendMigration=false]', () =
expect(ds.database).toBe('fallback');
});
});
describe('variable interpolation', () => {
const variablesMock = [
queryBuilder().withId('var1').withName('var1').withCurrent('var1_value').build(),
queryBuilder().withId('path').withName('path').withCurrent('/etc/hosts').build(),
];
const mockTemplateService = new TemplateSrv({
getVariables: () => variablesMock,
getVariableWithName: (name: string) => variablesMock.filter((v) => v.name === name)[0],
getFilteredVariables: jest.fn(),
});
// Remove this after start using TemplateSrv from @grafana/runtime
mockTemplateService.getAdhocFilters = jest.fn();
describe('when interpolating query variables for dashboard->explore', () => {
it('should interpolate all variables with Flux mode', () => {
const ds = getMockInfluxDS(getMockDSInstanceSettings({ version: InfluxVersion.Flux }), mockTemplateService);
const fluxQuery = {
refId: 'x',
query: 'some query with $var1 and $path',
};
const queries = ds.interpolateVariablesInQueries([fluxQuery], {});
expect(queries[0].query).toBe('some query with var1_value and /etc/hosts');
});
it('should interpolate all variables with InfluxQL mode', () => {
const ds = getMockInfluxDS(getMockDSInstanceSettings({ version: InfluxVersion.InfluxQL }), mockTemplateService);
const [query] = ds.interpolateVariablesInQueries([mockInfluxQueryWithTemplateVars([])], {});
expect(query.alias).toBe('var1_value');
expect(query.measurement).toBe('var1_value');
expect(query.policy).toBe('var1_value');
expect(query.limit).toBe('var1_value');
expect(query.slimit).toBe('var1_value');
expect(query.tz).toBe('var1_value');
expect(query.tags![0].value).toBe(`/^\\/etc\\/hosts$/`);
expect(query.groupBy![0].params![0]).toBe('var1_value');
expect(query.select![0][0].params![0]).toBe('var1_value');
});
});
describe('applyTemplateVariables', () => {
it('should apply all template variables with Flux mode', () => {
const ds = getMockInfluxDS(getMockDSInstanceSettings({ version: InfluxVersion.Flux }), mockTemplateService);
const fluxQuery = {
refId: 'x',
query: '$var1',
};
const query = ds.applyTemplateVariables(fluxQuery, {});
expect(query.query).toBe('var1_value');
});
});
describe('variable interpolation with chained variables with frontend mode', () => {
let ds = getMockInfluxDS(getMockDSInstanceSettings(), mockTemplateService);
const fetchMockImpl = () =>
of({
data: {
status: 'success',
results: [
{
series: [
{
name: 'measurement',
columns: ['name'],
values: [['cpu']],
},
],
},
],
},
});
beforeEach(() => {
jest.clearAllMocks();
fetchMock.mockImplementation(fetchMockImpl);
});
it('should render chained regex variables with floating point number', () => {
ds.metricFindQuery(`SELECT sum("piece_count") FROM "rp"."pdata" WHERE diameter <= $maxSED`, {
scopedVars: { maxSED: { text: '8.1', value: '8.1' } },
});
const qe = `SELECT sum("piece_count") FROM "rp"."pdata" WHERE diameter <= 8.1`;
const qData = decodeURIComponent(fetchMock.mock.calls[0][0].data.substring(2));
expect(qData).toBe(qe);
});
it('should render chained regex variables with URL', () => {
ds.metricFindQuery('SHOW TAG VALUES WITH KEY = "agent_url" WHERE agent_url =~ /^$var1$/', {
scopedVars: {
var1: {
text: 'https://aaaa-aa-aaa.bbb.ccc.ddd:8443/ggggg',
value: 'https://aaaa-aa-aaa.bbb.ccc.ddd:8443/ggggg',
},
},
});
const qe = `SHOW TAG VALUES WITH KEY = "agent_url" WHERE agent_url =~ /^https:\\/\\/aaaa-aa-aaa\\.bbb\\.ccc\\.ddd:8443\\/ggggg$/`;
const qData = decodeURIComponent(fetchMock.mock.calls[0][0].data.substring(2));
expect(qData).toBe(qe);
});
it('should render chained regex variables with floating point number and url', () => {
ds.metricFindQuery(
'SELECT sum("piece_count") FROM "rp"."pdata" WHERE diameter <= $maxSED AND agent_url =~ /^$var1$/',
{
scopedVars: {
var1: {
text: 'https://aaaa-aa-aaa.bbb.ccc.ddd:8443/ggggg',
value: 'https://aaaa-aa-aaa.bbb.ccc.ddd:8443/ggggg',
},
maxSED: { text: '8.1', value: '8.1' },
},
}
);
const qe = `SELECT sum("piece_count") FROM "rp"."pdata" WHERE diameter <= 8.1 AND agent_url =~ /^https:\\/\\/aaaa-aa-aaa\\.bbb\\.ccc\\.ddd:8443\\/ggggg$/`;
const qData = decodeURIComponent(fetchMock.mock.calls[0][0].data.substring(2));
expect(qData).toBe(qe);
});
});
});
});
describe('InfluxDataSource Backend Mode [influxdbBackendMigration=true]', () => {
@ -399,171 +264,10 @@ describe('InfluxDataSource Backend Mode [influxdbBackendMigration=true]', () =>
expect(values[0].text).toBe('test-t2-1');
});
});
describe('variable interpolation with chained variables with backend mode', () => {
const variablesMock = [
queryBuilder().withId('var1').withName('var1').withCurrent('var1').build(),
queryBuilder().withId('path').withName('path').withCurrent('/etc/hosts').build(),
queryBuilder()
.withId('field_var')
.withName('field_var')
.withMulti(true)
.withOptions(
{
text: `field_1`,
value: `field_1`,
},
{
text: `field_2`,
value: `field_2`,
},
{
text: `field_3`,
value: `field_3`,
}
)
.withCurrent(['field_1', 'field_3'])
.build(),
];
const mockTemplateService = new TemplateSrv({
getVariables: () => variablesMock,
getVariableWithName: (name: string) => variablesMock.filter((v) => v.name === name)[0],
getFilteredVariables: jest.fn(),
});
mockTemplateService.getAdhocFilters = jest.fn((_: string) => []);
let ds = getMockInfluxDS(getMockDSInstanceSettings(), mockTemplateService);
const fetchMockImpl = () =>
of({
data: {
status: 'success',
results: [
{
series: [
{
name: 'measurement',
columns: ['name'],
values: [['cpu']],
},
],
},
],
},
});
beforeEach(() => {
jest.clearAllMocks();
fetchMock.mockImplementation(fetchMockImpl);
});
it('should render chained regex variables with floating point number', () => {
ds.metricFindQuery(`SELECT sum("piece_count") FROM "rp"."pdata" WHERE diameter <= $maxSED`, {
...queryOptions,
scopedVars: { maxSED: { text: '8.1', value: '8.1' } },
});
const qe = `SELECT sum("piece_count") FROM "rp"."pdata" WHERE diameter <= 8.1`;
const qData = fetchMock.mock.calls[0][0].data.queries[0].query;
expect(qData).toBe(qe);
});
it('should render chained regex variables with URL', () => {
ds.metricFindQuery('SHOW TAG VALUES WITH KEY = "agent_url" WHERE agent_url =~ /^$var1$/', {
...queryOptions,
scopedVars: {
var1: {
text: 'https://aaaa-aa-aaa.bbb.ccc.ddd:8443/ggggg',
value: 'https://aaaa-aa-aaa.bbb.ccc.ddd:8443/ggggg',
},
},
});
const qe = `SHOW TAG VALUES WITH KEY = "agent_url" WHERE agent_url =~ /^https:\\/\\/aaaa-aa-aaa\\.bbb\\.ccc\\.ddd:8443\\/ggggg$/`;
expect(fetchMock).toHaveBeenCalled();
const qData = fetchMock.mock.calls[0][0].data.queries[0].query;
expect(qData).toBe(qe);
});
it('should render chained regex variables with floating point number and url', () => {
ds.metricFindQuery(
'SELECT sum("piece_count") FROM "rp"."pdata" WHERE diameter <= $maxSED AND agent_url =~ /^$var1$/',
{
...queryOptions,
scopedVars: {
var1: {
text: 'https://aaaa-aa-aaa.bbb.ccc.ddd:8443/ggggg',
value: 'https://aaaa-aa-aaa.bbb.ccc.ddd:8443/ggggg',
},
maxSED: { text: '8.1', value: '8.1' },
},
}
);
const qe = `SELECT sum("piece_count") FROM "rp"."pdata" WHERE diameter <= 8.1 AND agent_url =~ /^https:\\/\\/aaaa-aa-aaa\\.bbb\\.ccc\\.ddd:8443\\/ggggg$/`;
const qData = fetchMock.mock.calls[0][0].data.queries[0].query;
expect(qData).toBe(qe);
});
it('should interpolate variable inside a regex pattern', () => {
const query: InfluxQuery = {
refId: 'A',
tags: [
{
key: 'key',
operator: '=~',
value: '/^.*-$var1$/',
},
],
};
const res = ds.applyVariables(query, {});
const expected = `/^.*-var1$/`;
expect(res.tags?.[0].value).toEqual(expected);
});
it('should remove regex wrappers when operator is not a regex operator', () => {
const query: InfluxQuery = {
refId: 'A',
tags: [
{
key: 'key',
operator: '=',
value: '/^$path$/',
},
],
};
const res = ds.applyVariables(query, {});
const expected = `/etc/hosts`;
expect(res.tags?.[0].value).toEqual(expected);
});
it('should interpolate field keys with given scopedVars', () => {
const query: InfluxQuery = {
refId: 'A',
tags: [
{
key: 'key',
operator: '=',
value: 'value',
},
],
select: [
[
{
type: 'field',
params: ['$field_var'],
},
{
type: 'mean',
params: [],
},
],
],
};
const res = ds.applyVariables(query, { field_var: { text: 'field_3', value: 'field_3' } });
const expected = `field_3`;
expect(res.select?.[0][0].params?.[0]).toEqual(expected);
});
});
});
describe('interpolateQueryExpr', () => {
let ds = getMockInfluxDS(getMockDSInstanceSettings(), new TemplateSrv());
let ds = getMockInfluxDS(getMockDSInstanceSettings(), {} as TemplateSrv);
it('should return the value as it is', () => {
const value = 'normalValue';
const variableMock = queryBuilder().withId('tempVar').withName('tempVar').withMulti(false).build();

View File

@ -3,6 +3,7 @@ import { lastValueFrom, merge, Observable, of, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import {
AdHocVariableFilter,
AnnotationEvent,
DataFrame,
DataQueryError,
@ -31,10 +32,11 @@ import {
FetchResponse,
frameToMetricFindValue,
getBackendSrv,
getTemplateSrv,
TemplateSrv,
} from '@grafana/runtime';
import { QueryFormat, SQLQuery } from '@grafana/sql';
import config from 'app/core/config';
import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv';
import { AnnotationEditor } from './components/editor/annotation/AnnotationEditor';
import { FluxQueryEditor } from './components/editor/query/flux/FluxQueryEditor';
@ -170,7 +172,11 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
return true;
}
applyTemplateVariables(query: InfluxQuery, scopedVars: ScopedVars): InfluxQuery & SQLQuery {
applyTemplateVariables(
query: InfluxQuery,
scopedVars: ScopedVars,
filters?: AdHocVariableFilter[]
): InfluxQuery & SQLQuery {
const variables = scopedVars || {};
// We want to interpolate these variables on backend.
@ -190,7 +196,7 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
}
if (this.version === InfluxVersion.SQL || this.isMigrationToggleOnAndIsAccessProxy()) {
query = this.applyVariables(query, variables);
query = this.applyVariables(query, variables, filters);
if (query.adhocFilters?.length) {
const adhocFiltersToTags: InfluxQueryTag[] = (query.adhocFilters ?? []).map((af) => {
const { condition, ...asTag } = af;
@ -238,7 +244,7 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
});
}
applyVariables(query: InfluxQuery & SQLQuery, scopedVars: ScopedVars) {
applyVariables(query: InfluxQuery & SQLQuery, scopedVars: ScopedVars, filters?: AdHocVariableFilter[]) {
const expandedQuery = { ...query };
if (query.groupBy) {
expandedQuery.groupBy = query.groupBy.map((groupBy) => {
@ -282,7 +288,7 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
return {
...expandedQuery,
adhocFilters: this.templateSrv.getAdhocFilters(this.name) ?? [],
adhocFilters: filters ?? [],
query: this.templateSrv.replace(
query.query ?? '',
scopedVars,
@ -651,7 +657,7 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
}
// add global adhoc filters to timeFilter
const adhocFilters = this.templateSrv.getAdhocFilters(this.name);
const adhocFilters = options.filters;
const adhocFiltersFromDashboard = options.targets.flatMap((target: InfluxQuery) => target.adhocFilters ?? []);
if (adhocFilters?.length || adhocFiltersFromDashboard?.length) {
const ahFilters = adhocFilters?.length ? adhocFilters : adhocFiltersFromDashboard;

View File

@ -14,7 +14,7 @@ mockBackendService(mockInfluxSQLFetchResponse);
describe('InfluxDB SQL Support', () => {
const replaceMock = jest.fn();
const templateSrv = mockTemplateSrv(jest.fn(), replaceMock);
const templateSrv = mockTemplateSrv(replaceMock);
let sqlQuery: SQLQuery;

View File

@ -1,4 +1,4 @@
import { TemplateSrv } from 'app/features/templating/template_srv';
import { TemplateSrv } from '@grafana/runtime';
import InfluxQueryModel from './influx_query_model';

View File

@ -1,8 +1,7 @@
import { reduce } from 'lodash';
import { escapeRegex, ScopedVars } from '@grafana/data/src';
import { TemplateSrv } from '../../../features/templating/template_srv';
import { escapeRegex, ScopedVars } from '@grafana/data';
import { TemplateSrv } from '@grafana/runtime';
import { DEFAULT_POLICY, InfluxQueryTag, MetadataQueryType } from './types';