diff --git a/public/app/plugins/datasource/loki/datasource.test.ts b/public/app/plugins/datasource/loki/datasource.test.ts index 3324be98108..c7c4ce8530e 100644 --- a/public/app/plugins/datasource/loki/datasource.test.ts +++ b/public/app/plugins/datasource/loki/datasource.test.ts @@ -4,6 +4,7 @@ import { getQueryOptions } from 'test/helpers/getQueryOptions'; import { AnnotationQueryRequest, DataSourceApi, DataFrame, dateTime } from '@grafana/data'; import { BackendSrv } from 'app/core/services/backend_srv'; import { TemplateSrv } from 'app/features/templating/template_srv'; +import { CustomVariable } from 'app/features/templating/custom_variable'; describe('LokiDatasource', () => { const instanceSettings: any = { @@ -80,6 +81,54 @@ describe('LokiDatasource', () => { }); }); + describe('When interpolating variables', () => { + let ds: any = {}; + let variable: any = {}; + + beforeEach(() => { + const customData = { ...(instanceSettings.jsonData || {}), maxLines: 20 }; + const customSettings = { ...instanceSettings, jsonData: customData }; + ds = new LokiDatasource(customSettings, backendSrv, templateSrvMock); + variable = new CustomVariable({}, {} as any); + }); + + it('should only escape single quotes', () => { + expect(ds.interpolateQueryExpr("abc'$^*{}[]+?.()|", variable)).toEqual("abc\\\\'$^*{}[]+?.()|"); + }); + + it('should return a number', () => { + expect(ds.interpolateQueryExpr(1000, variable)).toEqual(1000); + }); + + describe('and variable allows multi-value', () => { + beforeEach(() => { + variable.multi = true; + }); + + it('should regex escape values if the value is a string', () => { + expect(ds.interpolateQueryExpr('looking*glass', variable)).toEqual('looking\\\\*glass'); + }); + + it('should return pipe separated values if the value is an array of strings', () => { + expect(ds.interpolateQueryExpr(['a|bc', 'de|f'], variable)).toEqual('a\\\\|bc|de\\\\|f'); + }); + }); + + describe('and variable allows all', () => { + beforeEach(() => { + variable.includeAll = true; + }); + + it('should regex escape values if the array is a string', () => { + expect(ds.interpolateQueryExpr('looking*glass', variable)).toEqual('looking\\\\*glass'); + }); + + it('should return pipe separated values if the value is an array of strings', () => { + expect(ds.interpolateQueryExpr(['a|bc', 'de|f'], variable)).toEqual('a\\\\|bc|de\\\\|f'); + }); + }); + }); + describe('when performing testDataSource', () => { let ds: DataSourceApi; let result: any; diff --git a/public/app/plugins/datasource/loki/datasource.ts b/public/app/plugins/datasource/loki/datasource.ts index 6a9f62d26d9..f3b771a628b 100644 --- a/public/app/plugins/datasource/loki/datasource.ts +++ b/public/app/plugins/datasource/loki/datasource.ts @@ -1,5 +1,5 @@ // Libraries -import { isEmpty, isString, fromPairs } from 'lodash'; +import { isEmpty, isString, fromPairs, map as lodashMap } from 'lodash'; // Services & Utils import { dateMath, @@ -88,7 +88,7 @@ export class LokiDatasource extends DataSourceApi { } prepareLiveTarget(target: LokiQuery, options: DataQueryRequest): LiveTarget { - const interpolated = this.templateSrv.replace(target.expr); + const interpolated = this.templateSrv.replace(target.expr, {}, this.interpolateQueryExpr); const { query, regexp } = parseQuery(interpolated); const refId = target.refId; const baseUrl = this.instanceSettings.url; @@ -105,7 +105,7 @@ export class LokiDatasource extends DataSourceApi { } prepareQueryTarget(target: LokiQuery, options: DataQueryRequest) { - const interpolated = this.templateSrv.replace(target.expr); + const interpolated = this.templateSrv.replace(target.expr, {}, this.interpolateQueryExpr); const { query, regexp } = parseQuery(interpolated); const start = this.getTime(options.range.from, false); const end = this.getTime(options.range.to, true); @@ -126,7 +126,6 @@ export class LokiDatasource extends DataSourceApi { message: (err && err.statusText) || 'Unknown error during query transaction. Please check JS console logs.', refId: target.refId, }; - if (err.data) { if (typeof err.data === 'string') { error.message = err.data; @@ -239,7 +238,7 @@ export class LokiDatasource extends DataSourceApi { const expandedQuery = { ...query, datasource: this.name, - expr: this.templateSrv.replace(query.expr), + expr: this.templateSrv.replace(query.expr, {}, this.interpolateQueryExpr), }; return expandedQuery; }); @@ -260,6 +259,20 @@ export class LokiDatasource extends DataSourceApi { }); } + interpolateQueryExpr(value: any, variable: any) { + // if no multi or include all do not regexEscape + if (!variable.multi && !variable.includeAll) { + return lokiRegularEscape(value); + } + + if (typeof value === 'string') { + return lokiSpecialRegexEscape(value); + } + + const escapedValues = lodashMap(value, lokiSpecialRegexEscape); + return escapedValues.join('|'); + } + modifyQuery(query: LokiQuery, action: any): LokiQuery { const parsed = parseQuery(query.expr || ''); let { query: selector } = parsed; @@ -481,4 +494,18 @@ function queryRequestFromAnnotationOptions(options: AnnotationQueryRequest