Loki: Use TemplateSrv from grafana/runtime

pull/78265/head
Ivana Huckova 2 years ago
parent b0448b92e5
commit d573925e37
  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,
reportInteraction,
setBackendSrv,
TemplateSrv,
} from '@grafana/runtime';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { initialCustomVariableModelState } from '../../../features/variables/custom/reducer';
import { CustomVariableModel } from '../../../features/variables/types';
@ -46,7 +46,6 @@ jest.mock('@grafana/runtime', () => {
jest.mock('./querySplitting');
const templateSrvStub = {
getAdhocFilters: jest.fn(() => [] as unknown[]),
replace: jest.fn((a: string, ...rest: unknown[]) => a),
} as unknown as TemplateSrv;
@ -216,7 +215,6 @@ describe('LokiDatasource', () => {
describe('When using adhoc filters', () => {
const DEFAULT_EXPR = 'rate({bar="baz", job="foo"} |= "bar" [5m])';
const query: LokiQuery = { expr: DEFAULT_EXPR, refId: 'A' };
const mockedGetAdhocFilters = templateSrvStub.getAdhocFilters as jest.Mock;
const ds = createLokiDatasource(templateSrvStub);
it('should not modify expression with no filters', async () => {
@ -224,7 +222,7 @@ describe('LokiDatasource', () => {
});
it('should add filters to expression', async () => {
mockedGetAdhocFilters.mockReturnValue([
const adhocFilters = [
{
key: 'k1',
operator: '=',
@ -235,15 +233,15 @@ describe('LokiDatasource', () => {
operator: '!=',
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])'
);
});
it('should add escaping if needed to regex filter expressions', async () => {
mockedGetAdhocFilters.mockReturnValue([
const adhocFilters = [
{
key: 'k1',
operator: '=~',
@ -254,8 +252,8 @@ describe('LokiDatasource', () => {
operator: '=~',
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])'
);
});
@ -318,8 +316,8 @@ describe('LokiDatasource', () => {
expr,
},
];
ds.interpolateVariablesInQueries(queries, {});
expect(ds.addAdHocFilters).toHaveBeenCalledWith(expr);
ds.interpolateVariablesInQueries(queries, {}, []);
expect(ds.addAdHocFilters).toHaveBeenCalledWith(expr, []);
});
});
@ -896,119 +894,123 @@ describe('LokiDatasource', () => {
describe('addAdHocFilters', () => {
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', () => {
beforeEach(() => {
const defaultAdHocFilters: AdHocFilter[] = [
{
condition: '',
key: 'job',
operator: '=',
value: 'grafana',
},
];
ds = createLokiDatasource(createTemplateSrvMock({ adHocFilters: defaultAdHocFilters }));
ds = createLokiDatasource();
});
const defaultAdHocFilters: AdHocFilter[] = [
{
condition: '',
key: 'job',
operator: '=',
value: 'grafana',
},
];
describe('and query has no parser', () => {
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', () => {
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', () => {
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', () => {
assertAdHocFilters('{}', '{job="grafana"}', ds);
assertAdHocFilters('{}', '{job="grafana"}', ds, defaultAdHocFilters);
});
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', () => {
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', () => {
const ds = createLokiDatasource(
createTemplateSrvMock({
adHocFilters: [
{
condition: '',
key: 'instance',
operator: '=',
value: '"test"',
},
],
})
);
assertAdHocFilters('{job="grafana"}', '{job="grafana", instance="\\"test\\""}', ds);
assertAdHocFilters('{job="grafana"}', '{job="grafana", instance="\\"test\\""}', ds, [
{
condition: '',
key: 'instance',
operator: '=',
value: '"test"',
},
]);
});
});
describe('and query has parser', () => {
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', () => {
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', () => {
const defaultAdHocFilters: AdHocFilter[] = [
{
condition: '',
key: 'job',
operator: '!=',
value: 'grafana',
},
];
beforeEach(() => {
const defaultAdHocFilters: AdHocFilter[] = [
{
condition: '',
key: 'job',
operator: '!=',
value: 'grafana',
},
];
ds = createLokiDatasource(createTemplateSrvMock({ adHocFilters: defaultAdHocFilters }));
ds = createLokiDatasource();
});
describe('and query has no parser', () => {
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', () => {
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', () => {
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', () => {
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', () => {
const defaultAdHocFilters: AdHocFilter[] = [
{
condition: '',
key: 'instance',
operator: '=~',
value: '.*',
},
];
beforeEach(() => {
const defaultAdHocFilters: AdHocFilter[] = [
{
condition: '',
key: 'instance',
operator: '=~',
value: '.*',
},
];
ds = createLokiDatasource(createTemplateSrvMock({ adHocFilters: defaultAdHocFilters }));
ds = createLokiDatasource();
});
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', () => {
it('should add the adhoc filter to the query', () => {
const ds = createLokiDatasource(templateSrvStub);
const ds = createLokiDatasource();
const spy = jest.spyOn(ds, 'addAdHocFilters');
ds.applyTemplateVariables({ expr: '{test}', refId: 'A' }, {});
expect(spy).toHaveBeenCalledWith('{test}');
ds.applyTemplateVariables({ expr: '{test}', refId: 'A' }, {}, []);
expect(spy).toHaveBeenCalledWith('{test}', []);
});
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 result = ds.addAdHocFilters(lokiQuery.expr);
const result = ds.addAdHocFilters(lokiQuery.expr, adhocFilters);
expect(result).toEqual(expectedResults);
}

@ -35,14 +35,14 @@ import {
QueryFilterOptions,
renderLegendFormat,
LegacyMetricFindQueryOptions,
AdHocVariableFilter,
} from '@grafana/data';
import { intervalToMs } from '@grafana/data/src/datetime/rangeutil';
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 { convertToWebSocketUrl } from 'app/core/utils/explore';
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 { queryLogsSample, queryLogsVolume } from '../../../features/logs/logsModel';
@ -421,13 +421,20 @@ export class LokiDatasource
* 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.
*/
interpolateVariablesInQueries(queries: LokiQuery[], scopedVars: ScopedVars): LokiQuery[] {
interpolateVariablesInQueries(
queries: LokiQuery[],
scopedVars: ScopedVars,
adhocFilters?: AdHocVariableFilter[]
): LokiQuery[] {
let expandedQueries = queries;
if (queries && queries.length) {
expandedQueries = queries.map((query) => ({
...query,
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
),
}));
}
@ -1047,8 +1054,11 @@ export class LokiDatasource
* @returns The query expression with ad hoc filters and correctly escaped values.
* @todo this.templateSrv.getAdhocFilters() is deprecated
*/
addAdHocFilters(queryExpr: string) {
const adhocFilters = this.templateSrv.getAdhocFilters(this.name);
addAdHocFilters(queryExpr: string, adhocFilters?: AdHocVariableFilter[]) {
if (!adhocFilters) {
return queryExpr;
}
let expr = replaceVariables(queryExpr);
expr = adhocFilters.reduce((acc: string, filter: { key: string; operator: string; value: string }) => {
@ -1086,12 +1096,12 @@ export class LokiDatasource
* It is called from DatasourceWithBackend.
* @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
// 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 exprWithAdHoc = this.addAdHocFilters(target.expr);
const exprWithAdHoc = this.addAdHocFilters(target.expr, adhocFilters);
return {
...target,

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

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

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

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

Loading…
Cancel
Save