From d573925e37846ab2b506bbf536fdd288cba08b97 Mon Sep 17 00:00:00 2001 From: Ivana Huckova Date: Wed, 15 Nov 2023 14:14:11 +0100 Subject: [PATCH] Loki: Use TemplateSrv from grafana/runtime --- .../datasource/loki/datasource.test.ts | 152 +++++++++--------- .../app/plugins/datasource/loki/datasource.ts | 26 ++- public/app/plugins/datasource/loki/mocks.ts | 1 + .../components/LokiQueryBuilder.test.tsx | 14 +- .../LokiQueryBuilderContainer.test.tsx | 19 +-- .../components/UnwrapParamEditor.test.tsx | 19 +-- 6 files changed, 103 insertions(+), 128 deletions(-) diff --git a/public/app/plugins/datasource/loki/datasource.test.ts b/public/app/plugins/datasource/loki/datasource.test.ts index 6c6ba6657f6..ad9ffdade56 100644 --- a/public/app/plugins/datasource/loki/datasource.test.ts +++ b/public/app/plugins/datasource/loki/datasource.test.ts @@ -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); } diff --git a/public/app/plugins/datasource/loki/datasource.ts b/public/app/plugins/datasource/loki/datasource.ts index 6ca38333b26..e664a996df0 100644 --- a/public/app/plugins/datasource/loki/datasource.ts +++ b/public/app/plugins/datasource/loki/datasource.ts @@ -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, diff --git a/public/app/plugins/datasource/loki/mocks.ts b/public/app/plugins/datasource/loki/mocks.ts index 1507eb05f55..cac99b2c227 100644 --- a/public/app/plugins/datasource/loki/mocks.ts +++ b/public/app/plugins/datasource/loki/mocks.ts @@ -35,6 +35,7 @@ const defaultTimeSrvMock = { const defaultTemplateSrvMock = { replace: (input: string) => input, + getVariables: () => [], }; export function createLokiDatasource( diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.test.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.test.tsx index 94a6e4bf195..c0f9f7811e6 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.test.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.test.tsx @@ -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, diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.test.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.test.tsx index 5c01d8908bc..db9c78d41d5 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.test.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.test.tsx @@ -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, diff --git a/public/app/plugins/datasource/loki/querybuilder/components/UnwrapParamEditor.test.tsx b/public/app/plugins/datasource/loki/querybuilder/components/UnwrapParamEditor.test.tsx index a0b7803a500..c10e27603b4 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/UnwrapParamEditor.test.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/UnwrapParamEditor.test.tsx @@ -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 };