Loki: Use TemplateSrv from @grafana/runtime (#78196)

Loki: Use TemplateSrv from grafana/runtime
pull/78439/head
Ivana Huckova 2 years ago committed by GitHub
parent 875ea092df
commit 9200e17b18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 152
      public/app/plugins/datasource/loki/datasource.test.ts
  2. 26
      public/app/plugins/datasource/loki/datasource.ts
  3. 1
      public/app/plugins/datasource/loki/mocks.ts
  4. 14
      public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.test.tsx
  5. 19
      public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.test.tsx
  6. 19
      public/app/plugins/datasource/loki/querybuilder/components/UnwrapParamEditor.test.tsx

@ -23,8 +23,8 @@ import {
getBackendSrv, getBackendSrv,
reportInteraction, reportInteraction,
setBackendSrv, setBackendSrv,
TemplateSrv,
} from '@grafana/runtime'; } from '@grafana/runtime';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { initialCustomVariableModelState } from '../../../features/variables/custom/reducer'; import { initialCustomVariableModelState } from '../../../features/variables/custom/reducer';
import { CustomVariableModel } from '../../../features/variables/types'; import { CustomVariableModel } from '../../../features/variables/types';
@ -46,7 +46,6 @@ jest.mock('@grafana/runtime', () => {
jest.mock('./querySplitting'); jest.mock('./querySplitting');
const templateSrvStub = { const templateSrvStub = {
getAdhocFilters: jest.fn(() => [] as unknown[]),
replace: jest.fn((a: string, ...rest: unknown[]) => a), replace: jest.fn((a: string, ...rest: unknown[]) => a),
} as unknown as TemplateSrv; } as unknown as TemplateSrv;
@ -216,7 +215,6 @@ describe('LokiDatasource', () => {
describe('When using adhoc filters', () => { describe('When using adhoc filters', () => {
const DEFAULT_EXPR = 'rate({bar="baz", job="foo"} |= "bar" [5m])'; const DEFAULT_EXPR = 'rate({bar="baz", job="foo"} |= "bar" [5m])';
const query: LokiQuery = { expr: DEFAULT_EXPR, refId: 'A' }; const query: LokiQuery = { expr: DEFAULT_EXPR, refId: 'A' };
const mockedGetAdhocFilters = templateSrvStub.getAdhocFilters as jest.Mock;
const ds = createLokiDatasource(templateSrvStub); const ds = createLokiDatasource(templateSrvStub);
it('should not modify expression with no filters', async () => { it('should not modify expression with no filters', async () => {
@ -224,7 +222,7 @@ describe('LokiDatasource', () => {
}); });
it('should add filters to expression', async () => { it('should add filters to expression', async () => {
mockedGetAdhocFilters.mockReturnValue([ const adhocFilters = [
{ {
key: 'k1', key: 'k1',
operator: '=', operator: '=',
@ -235,15 +233,15 @@ describe('LokiDatasource', () => {
operator: '!=', operator: '!=',
value: 'v2', value: 'v2',
}, },
]); ];
expect(ds.applyTemplateVariables(query, {}).expr).toBe( expect(ds.applyTemplateVariables(query, {}, adhocFilters).expr).toBe(
'rate({bar="baz", job="foo", k1="v1", k2!="v2"} |= "bar" [5m])' 'rate({bar="baz", job="foo", k1="v1", k2!="v2"} |= "bar" [5m])'
); );
}); });
it('should add escaping if needed to regex filter expressions', async () => { it('should add escaping if needed to regex filter expressions', async () => {
mockedGetAdhocFilters.mockReturnValue([ const adhocFilters = [
{ {
key: 'k1', key: 'k1',
operator: '=~', operator: '=~',
@ -254,8 +252,8 @@ describe('LokiDatasource', () => {
operator: '=~', operator: '=~',
value: `v'.*`, value: `v'.*`,
}, },
]); ];
expect(ds.applyTemplateVariables(query, {}).expr).toBe( expect(ds.applyTemplateVariables(query, {}, adhocFilters).expr).toBe(
'rate({bar="baz", job="foo", k1=~"v.*", k2=~"v\\\\\'.*"} |= "bar" [5m])' 'rate({bar="baz", job="foo", k1=~"v.*", k2=~"v\\\\\'.*"} |= "bar" [5m])'
); );
}); });
@ -318,8 +316,8 @@ describe('LokiDatasource', () => {
expr, expr,
}, },
]; ];
ds.interpolateVariablesInQueries(queries, {}); ds.interpolateVariablesInQueries(queries, {}, []);
expect(ds.addAdHocFilters).toHaveBeenCalledWith(expr); expect(ds.addAdHocFilters).toHaveBeenCalledWith(expr, []);
}); });
}); });
@ -896,119 +894,123 @@ describe('LokiDatasource', () => {
describe('addAdHocFilters', () => { describe('addAdHocFilters', () => {
let ds: LokiDatasource; let ds: LokiDatasource;
const createTemplateSrvMock = (options: { adHocFilters: AdHocFilter[] }) => {
return {
getAdhocFilters: (): AdHocFilter[] => options.adHocFilters,
replace: (a: string) => a,
} as unknown as TemplateSrv;
};
describe('when called with "=" operator', () => { describe('when called with "=" operator', () => {
beforeEach(() => { beforeEach(() => {
const defaultAdHocFilters: AdHocFilter[] = [ ds = createLokiDatasource();
{
condition: '',
key: 'job',
operator: '=',
value: 'grafana',
},
];
ds = createLokiDatasource(createTemplateSrvMock({ adHocFilters: defaultAdHocFilters }));
}); });
const defaultAdHocFilters: AdHocFilter[] = [
{
condition: '',
key: 'job',
operator: '=',
value: 'grafana',
},
];
describe('and query has no parser', () => { describe('and query has no parser', () => {
it('then the correct label should be added for logs query', () => { it('then the correct label should be added for logs query', () => {
assertAdHocFilters('{bar="baz"}', '{bar="baz", job="grafana"}', ds); assertAdHocFilters('{bar="baz"}', '{bar="baz", job="grafana"}', ds, defaultAdHocFilters);
}); });
it('then the correct label should be added for metrics query', () => { it('then the correct label should be added for metrics query', () => {
assertAdHocFilters('rate({bar="baz"}[5m])', 'rate({bar="baz", job="grafana"}[5m])', ds); assertAdHocFilters('rate({bar="baz"}[5m])', 'rate({bar="baz", job="grafana"}[5m])', ds, defaultAdHocFilters);
}); });
it('then the correct label should be added for metrics query and variable', () => { it('then the correct label should be added for metrics query and variable', () => {
assertAdHocFilters('rate({bar="baz"}[$__interval])', 'rate({bar="baz", job="grafana"}[$__interval])', ds); assertAdHocFilters(
'rate({bar="baz"}[$__interval])',
'rate({bar="baz", job="grafana"}[$__interval])',
ds,
defaultAdHocFilters
);
}); });
it('then the correct label should be added for logs query with empty selector', () => { it('then the correct label should be added for logs query with empty selector', () => {
assertAdHocFilters('{}', '{job="grafana"}', ds); assertAdHocFilters('{}', '{job="grafana"}', ds, defaultAdHocFilters);
}); });
it('then the correct label should be added for metrics query with empty selector', () => { it('then the correct label should be added for metrics query with empty selector', () => {
assertAdHocFilters('rate({}[5m])', 'rate({job="grafana"}[5m])', ds); assertAdHocFilters('rate({}[5m])', 'rate({job="grafana"}[5m])', ds, defaultAdHocFilters);
}); });
it('then the correct label should be added for metrics query with empty selector and variable', () => { it('then the correct label should be added for metrics query with empty selector and variable', () => {
assertAdHocFilters('rate({}[$__interval])', 'rate({job="grafana"}[$__interval])', ds); assertAdHocFilters('rate({}[$__interval])', 'rate({job="grafana"}[$__interval])', ds, defaultAdHocFilters);
}); });
it('should correctly escape special characters in ad hoc filter', () => { it('should correctly escape special characters in ad hoc filter', () => {
const ds = createLokiDatasource( assertAdHocFilters('{job="grafana"}', '{job="grafana", instance="\\"test\\""}', ds, [
createTemplateSrvMock({ {
adHocFilters: [ condition: '',
{ key: 'instance',
condition: '', operator: '=',
key: 'instance', value: '"test"',
operator: '=', },
value: '"test"', ]);
},
],
})
);
assertAdHocFilters('{job="grafana"}', '{job="grafana", instance="\\"test\\""}', ds);
}); });
}); });
describe('and query has parser', () => { describe('and query has parser', () => {
it('then the correct label should be added for logs query', () => { it('then the correct label should be added for logs query', () => {
assertAdHocFilters('{bar="baz"} | logfmt', '{bar="baz"} | logfmt | job=`grafana`', ds); assertAdHocFilters('{bar="baz"} | logfmt', '{bar="baz"} | logfmt | job=`grafana`', ds, defaultAdHocFilters);
}); });
it('then the correct label should be added for metrics query', () => { it('then the correct label should be added for metrics query', () => {
assertAdHocFilters('rate({bar="baz"} | logfmt [5m])', 'rate({bar="baz"} | logfmt | job=`grafana` [5m])', ds); assertAdHocFilters(
'rate({bar="baz"} | logfmt [5m])',
'rate({bar="baz"} | logfmt | job=`grafana` [5m])',
ds,
defaultAdHocFilters
);
}); });
}); });
}); });
describe('when called with "!=" operator', () => { describe('when called with "!=" operator', () => {
const defaultAdHocFilters: AdHocFilter[] = [
{
condition: '',
key: 'job',
operator: '!=',
value: 'grafana',
},
];
beforeEach(() => { beforeEach(() => {
const defaultAdHocFilters: AdHocFilter[] = [ ds = createLokiDatasource();
{
condition: '',
key: 'job',
operator: '!=',
value: 'grafana',
},
];
ds = createLokiDatasource(createTemplateSrvMock({ adHocFilters: defaultAdHocFilters }));
}); });
describe('and query has no parser', () => { describe('and query has no parser', () => {
it('then the correct label should be added for logs query', () => { it('then the correct label should be added for logs query', () => {
assertAdHocFilters('{bar="baz"}', '{bar="baz", job!="grafana"}', ds); assertAdHocFilters('{bar="baz"}', '{bar="baz", job!="grafana"}', ds, defaultAdHocFilters);
}); });
it('then the correct label should be added for metrics query', () => { it('then the correct label should be added for metrics query', () => {
assertAdHocFilters('rate({bar="baz"}[5m])', 'rate({bar="baz", job!="grafana"}[5m])', ds); assertAdHocFilters('rate({bar="baz"}[5m])', 'rate({bar="baz", job!="grafana"}[5m])', ds, defaultAdHocFilters);
}); });
}); });
describe('and query has parser', () => { describe('and query has parser', () => {
it('then the correct label should be added for logs query', () => { it('then the correct label should be added for logs query', () => {
assertAdHocFilters('{bar="baz"} | logfmt', '{bar="baz"} | logfmt | job!=`grafana`', ds); assertAdHocFilters('{bar="baz"} | logfmt', '{bar="baz"} | logfmt | job!=`grafana`', ds, defaultAdHocFilters);
}); });
it('then the correct label should be added for metrics query', () => { it('then the correct label should be added for metrics query', () => {
assertAdHocFilters('rate({bar="baz"} | logfmt [5m])', 'rate({bar="baz"} | logfmt | job!=`grafana` [5m])', ds); assertAdHocFilters(
'rate({bar="baz"} | logfmt [5m])',
'rate({bar="baz"} | logfmt | job!=`grafana` [5m])',
ds,
defaultAdHocFilters
);
}); });
}); });
}); });
describe('when called with regex operator', () => { describe('when called with regex operator', () => {
const defaultAdHocFilters: AdHocFilter[] = [
{
condition: '',
key: 'instance',
operator: '=~',
value: '.*',
},
];
beforeEach(() => { beforeEach(() => {
const defaultAdHocFilters: AdHocFilter[] = [ ds = createLokiDatasource();
{
condition: '',
key: 'instance',
operator: '=~',
value: '.*',
},
];
ds = createLokiDatasource(createTemplateSrvMock({ adHocFilters: defaultAdHocFilters }));
}); });
it('should not escape special characters in ad hoc filter', () => { it('should not escape special characters in ad hoc filter', () => {
assertAdHocFilters('{job="grafana"}', '{job="grafana", instance=~".*"}', ds); assertAdHocFilters('{job="grafana"}', '{job="grafana", instance=~".*"}', ds, defaultAdHocFilters);
}); });
}); });
}); });
@ -1403,10 +1405,10 @@ describe('LokiDatasource', () => {
describe('applyTemplateVariables', () => { describe('applyTemplateVariables', () => {
it('should add the adhoc filter to the query', () => { it('should add the adhoc filter to the query', () => {
const ds = createLokiDatasource(templateSrvStub); const ds = createLokiDatasource();
const spy = jest.spyOn(ds, 'addAdHocFilters'); const spy = jest.spyOn(ds, 'addAdHocFilters');
ds.applyTemplateVariables({ expr: '{test}', refId: 'A' }, {}); ds.applyTemplateVariables({ expr: '{test}', refId: 'A' }, {}, []);
expect(spy).toHaveBeenCalledWith('{test}'); expect(spy).toHaveBeenCalledWith('{test}', []);
}); });
describe('with template and built-in variables', () => { describe('with template and built-in variables', () => {
@ -1648,9 +1650,9 @@ describe('queryHasFilter()', () => {
}); });
}); });
function assertAdHocFilters(query: string, expectedResults: string, ds: LokiDatasource) { function assertAdHocFilters(query: string, expectedResults: string, ds: LokiDatasource, adhocFilters?: AdHocFilter[]) {
const lokiQuery: LokiQuery = { refId: 'A', expr: query }; const lokiQuery: LokiQuery = { refId: 'A', expr: query };
const result = ds.addAdHocFilters(lokiQuery.expr); const result = ds.addAdHocFilters(lokiQuery.expr, adhocFilters);
expect(result).toEqual(expectedResults); expect(result).toEqual(expectedResults);
} }

@ -35,13 +35,13 @@ import {
QueryFilterOptions, QueryFilterOptions,
renderLegendFormat, renderLegendFormat,
LegacyMetricFindQueryOptions, LegacyMetricFindQueryOptions,
AdHocVariableFilter,
} from '@grafana/data'; } from '@grafana/data';
import { Duration } from '@grafana/lezer-logql'; import { Duration } from '@grafana/lezer-logql';
import { BackendSrvRequest, config, DataSourceWithBackend } from '@grafana/runtime'; import { BackendSrvRequest, config, DataSourceWithBackend, getTemplateSrv, TemplateSrv } from '@grafana/runtime';
import { DataQuery } from '@grafana/schema'; import { DataQuery } from '@grafana/schema';
import { convertToWebSocketUrl } from 'app/core/utils/explore'; import { convertToWebSocketUrl } from 'app/core/utils/explore';
import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv';
import { serializeParams } from '../../../core/utils/fetch'; import { serializeParams } from '../../../core/utils/fetch';
import { queryLogsSample, queryLogsVolume } from '../../../features/logs/logsModel'; import { queryLogsSample, queryLogsVolume } from '../../../features/logs/logsModel';
@ -420,13 +420,20 @@ export class LokiDatasource
* Implemented as a part of DataSourceApi. Interpolates variables and adds ad hoc filters to a list of Loki queries. * Implemented as a part of DataSourceApi. Interpolates variables and adds ad hoc filters to a list of Loki queries.
* @returns An array of expanded Loki queries with interpolated variables and ad hoc filters. * @returns An array of expanded Loki queries with interpolated variables and ad hoc filters.
*/ */
interpolateVariablesInQueries(queries: LokiQuery[], scopedVars: ScopedVars): LokiQuery[] { interpolateVariablesInQueries(
queries: LokiQuery[],
scopedVars: ScopedVars,
adhocFilters?: AdHocVariableFilter[]
): LokiQuery[] {
let expandedQueries = queries; let expandedQueries = queries;
if (queries && queries.length) { if (queries && queries.length) {
expandedQueries = queries.map((query) => ({ expandedQueries = queries.map((query) => ({
...query, ...query,
datasource: this.getRef(), datasource: this.getRef(),
expr: this.addAdHocFilters(this.templateSrv.replace(query.expr, scopedVars, this.interpolateQueryExpr)), expr: this.addAdHocFilters(
this.templateSrv.replace(query.expr, scopedVars, this.interpolateQueryExpr),
adhocFilters
),
})); }));
} }
@ -1046,8 +1053,11 @@ export class LokiDatasource
* @returns The query expression with ad hoc filters and correctly escaped values. * @returns The query expression with ad hoc filters and correctly escaped values.
* @todo this.templateSrv.getAdhocFilters() is deprecated * @todo this.templateSrv.getAdhocFilters() is deprecated
*/ */
addAdHocFilters(queryExpr: string) { addAdHocFilters(queryExpr: string, adhocFilters?: AdHocVariableFilter[]) {
const adhocFilters = this.templateSrv.getAdhocFilters(this.name); if (!adhocFilters) {
return queryExpr;
}
let expr = replaceVariables(queryExpr); let expr = replaceVariables(queryExpr);
expr = adhocFilters.reduce((acc: string, filter: { key: string; operator: string; value: string }) => { expr = adhocFilters.reduce((acc: string, filter: { key: string; operator: string; value: string }) => {
@ -1085,12 +1095,12 @@ export class LokiDatasource
* It is called from DatasourceWithBackend. * It is called from DatasourceWithBackend.
* @returns A modified Loki query with template variables and ad hoc filters applied. * @returns A modified Loki query with template variables and ad hoc filters applied.
*/ */
applyTemplateVariables(target: LokiQuery, scopedVars: ScopedVars): LokiQuery { applyTemplateVariables(target: LokiQuery, scopedVars: ScopedVars, adhocFilters?: AdHocVariableFilter[]): LokiQuery {
// We want to interpolate these variables on backend because we support using them in // We want to interpolate these variables on backend because we support using them in
// alerting/ML queries and we want to have consistent interpolation for all queries // alerting/ML queries and we want to have consistent interpolation for all queries
const { __auto, __interval, __interval_ms, __range, __range_s, __range_ms, ...rest } = scopedVars || {}; const { __auto, __interval, __interval_ms, __range, __range_s, __range_ms, ...rest } = scopedVars || {};
const exprWithAdHoc = this.addAdHocFilters(target.expr); const exprWithAdHoc = this.addAdHocFilters(target.expr, adhocFilters);
return { return {
...target, ...target,

@ -35,6 +35,7 @@ const defaultTimeSrvMock = {
const defaultTemplateSrvMock = { const defaultTemplateSrvMock = {
replace: (input: string) => input, replace: (input: string) => input,
getVariables: () => [],
}; };
export function createLokiDatasource( export function createLokiDatasource(

@ -3,10 +3,8 @@ import userEvent from '@testing-library/user-event';
import React from 'react'; import React from 'react';
import { getSelectParent } from 'test/helpers/selectOptionInTest'; import { getSelectParent } from 'test/helpers/selectOptionInTest';
import { DataSourceInstanceSettings, DataSourcePluginMeta } from '@grafana/data';
import { MISSING_LABEL_FILTER_ERROR_MESSAGE } from '../../../prometheus/querybuilder/shared/LabelFilters'; import { MISSING_LABEL_FILTER_ERROR_MESSAGE } from '../../../prometheus/querybuilder/shared/LabelFilters';
import { LokiDatasource } from '../../datasource'; import { createLokiDatasource } from '../../mocks';
import { LokiOperationId, LokiVisualQuery } from '../types'; import { LokiOperationId, LokiVisualQuery } from '../types';
import { LokiQueryBuilder } from './LokiQueryBuilder'; import { LokiQueryBuilder } from './LokiQueryBuilder';
@ -18,15 +16,7 @@ const defaultQuery: LokiVisualQuery = {
}; };
const createDefaultProps = () => { const createDefaultProps = () => {
const datasource = new LokiDatasource( const datasource = createLokiDatasource();
{
url: '',
jsonData: {},
meta: {} as DataSourcePluginMeta,
} as DataSourceInstanceSettings,
undefined,
undefined
);
const props = { const props = {
datasource, datasource,

@ -1,10 +1,9 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import React from 'react'; import React from 'react';
import { DataSourcePluginMeta } from '@grafana/data';
import { addOperation } from 'app/plugins/datasource/prometheus/querybuilder/shared/OperationList.testUtils'; import { addOperation } from 'app/plugins/datasource/prometheus/querybuilder/shared/OperationList.testUtils';
import { LokiDatasource } from '../../datasource'; import { createLokiDatasource } from '../../mocks';
import { LokiQueryBuilderContainer } from './LokiQueryBuilderContainer'; import { LokiQueryBuilderContainer } from './LokiQueryBuilderContainer';
@ -15,21 +14,7 @@ describe('LokiQueryBuilderContainer', () => {
expr: '{job="testjob"}', expr: '{job="testjob"}',
refId: 'A', refId: 'A',
}, },
datasource: new LokiDatasource( datasource: createLokiDatasource(),
{
id: 1,
uid: '',
type: 'loki',
name: 'loki-test',
access: 'proxy',
url: '',
jsonData: {},
meta: {} as DataSourcePluginMeta,
readOnly: false,
},
undefined,
undefined
),
onChange: jest.fn(), onChange: jest.fn(),
onRunQuery: () => {}, onRunQuery: () => {},
showExplain: false, showExplain: false,

@ -2,13 +2,14 @@ import { screen, render } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import React, { ComponentProps } from 'react'; import React, { ComponentProps } from 'react';
import { DataFrame, DataSourceApi, DataSourcePluginMeta, FieldType, toDataFrame } from '@grafana/data'; import { DataFrame, DataSourceApi, FieldType, toDataFrame } from '@grafana/data';
import { import {
QueryBuilderOperation, QueryBuilderOperation,
QueryBuilderOperationParamDef, QueryBuilderOperationParamDef,
} from 'app/plugins/datasource/prometheus/querybuilder/shared/types'; } from 'app/plugins/datasource/prometheus/querybuilder/shared/types';
import { LokiDatasource } from '../../datasource'; import { LokiDatasource } from '../../datasource';
import { createLokiDatasource } from '../../mocks';
import { LokiOperationId } from '../types'; import { LokiOperationId } from '../types';
import { UnwrapParamEditor } from './UnwrapParamEditor'; import { UnwrapParamEditor } from './UnwrapParamEditor';
@ -75,21 +76,7 @@ const createProps = (
}, },
paramDef: {} as QueryBuilderOperationParamDef, paramDef: {} as QueryBuilderOperationParamDef,
operation: {} as QueryBuilderOperation, operation: {} as QueryBuilderOperation,
datasource: new LokiDatasource( datasource: createLokiDatasource() as DataSourceApi,
{
id: 1,
uid: '',
type: 'loki',
name: 'loki-test',
access: 'proxy',
url: '',
jsonData: {},
meta: {} as DataSourcePluginMeta,
readOnly: false,
},
undefined,
undefined
) as DataSourceApi,
}; };
const props = { ...propsDefault, ...propsOverrides }; const props = { ...propsDefault, ...propsOverrides };

Loading…
Cancel
Save