Loki: add missing interpolation to metric find query requests (#54979)

* feat(loki-variable-query-support): refactor datasource to support legacy and new variable query format

* Chore: fix comment

* Chore: use internal method to interpolate strings

* Chore: revert removed changes

* Chore: update test
This commit is contained in:
Matias Chomicki
2022-09-12 17:48:04 +02:00
committed by GitHub
parent 18310785a1
commit 445e1b3eae
5 changed files with 96 additions and 38 deletions
@@ -23,7 +23,7 @@ import { CustomVariableModel } from '../../../features/variables/types';
import { LokiDatasource } from './datasource';
import { createMetadataRequest, createLokiDatasource } from './mocks';
import { LokiOptions, LokiQuery, LokiQueryType } from './types';
import { LokiOptions, LokiQuery, LokiQueryType, LokiVariableQueryType } from './types';
import { LokiVariableSupport } from './variables';
const templateSrvStub = {
@@ -464,36 +464,71 @@ describe('LokiDatasource', () => {
return { ds };
};
it(`should return label names for Loki`, async () => {
it('should return label names for Loki', async () => {
const { ds } = getTestContext();
const res = await ds.metricFindQuery('label_names()');
const legacyResult = await ds.metricFindQuery('label_names()');
const result = await ds.metricFindQuery({ refId: 'test', type: LokiVariableQueryType.LabelNames });
expect(res).toEqual([{ text: 'label1' }, { text: 'label2' }]);
expect(legacyResult).toEqual(result);
expect(result).toEqual([{ text: 'label1' }, { text: 'label2' }]);
});
it(`should return label values for Loki when no matcher`, async () => {
it('should return label values for Loki when no matcher', async () => {
const { ds } = getTestContext();
const res = await ds.metricFindQuery('label_values(label1)');
const legacyResult = await ds.metricFindQuery('label_values(label1)');
const result = await ds.metricFindQuery({
refId: 'test',
type: LokiVariableQueryType.LabelValues,
label: 'label1',
});
expect(res).toEqual([{ text: 'value1' }, { text: 'value2' }]);
expect(legacyResult).toEqual(result);
expect(result).toEqual([{ text: 'value1' }, { text: 'value2' }]);
});
it(`should return label values for Loki with matcher`, async () => {
it('should return label values for Loki with matcher', async () => {
const { ds } = getTestContext();
const res = await ds.metricFindQuery('label_values({label1="value1", label2="value2"},label5)');
const legacyResult = await ds.metricFindQuery('label_values({label1="value1", label2="value2"},label5)');
const result = await ds.metricFindQuery({
refId: 'test',
type: LokiVariableQueryType.LabelValues,
stream: '{label1="value1", label2="value2"}',
label: 'label5',
});
expect(res).toEqual([{ text: 'value5' }]);
expect(legacyResult).toEqual(result);
expect(result).toEqual([{ text: 'value5' }]);
});
it(`should return empty array when incorrect query for Loki`, async () => {
it('should return empty array when incorrect query for Loki', async () => {
const { ds } = getTestContext();
const res = await ds.metricFindQuery('incorrect_query');
const result = await ds.metricFindQuery('incorrect_query');
expect(res).toEqual([]);
expect(result).toEqual([]);
});
it('should interpolate strings in the query', async () => {
const { ds } = getTestContext();
await ds.metricFindQuery('label_names()');
await ds.metricFindQuery({
refId: 'test',
type: LokiVariableQueryType.LabelValues,
stream: '{label1="value1", label2="value2"}',
label: 'label5',
});
expect(templateSrvStub.replace).toHaveBeenCalledWith('label_names()', undefined, expect.any(Function));
expect(templateSrvStub.replace).toHaveBeenCalledWith(
'{label1="value1", label2="value2"}',
undefined,
expect.any(Function)
);
expect(templateSrvStub.replace).toHaveBeenCalledWith('label5', undefined, expect.any(Function));
});
});
@@ -62,7 +62,14 @@ import { getQueryHints } from './queryHints';
import { getNormalizedLokiQuery, isLogsQuery, isValidQuery } from './query_utils';
import { sortDataFrameByTime } from './sortDataFrame';
import { doLokiChannelStream } from './streaming';
import { LokiOptions, LokiQuery, LokiQueryDirection, LokiQueryType } from './types';
import {
LokiOptions,
LokiQuery,
LokiQueryDirection,
LokiQueryType,
LokiVariableQuery,
LokiVariableQueryType,
} from './types';
import { LokiVariableSupport } from './variables';
export type RangeQueryOptions = DataQueryRequest<LokiQuery> | AnnotationQueryRequest<LokiQuery>;
@@ -305,16 +312,43 @@ export class LokiDatasource
return res.data || [];
}
async metricFindQuery(query: string) {
async metricFindQuery(query: LokiVariableQuery | string) {
if (!query) {
return Promise.resolve([]);
}
const interpolated = this.templateSrv.replace(query, {}, this.interpolateQueryExpr);
return await this.processMetricFindQuery(interpolated);
if (typeof query === 'string') {
const interpolated = this.interpolateString(query);
return await this.legacyProcessMetricFindQuery(interpolated);
}
const interpolatedQuery = {
...query,
label: this.interpolateString(query.label || ''),
stream: this.interpolateString(query.stream || ''),
};
return await this.processMetricFindQuery(interpolatedQuery);
}
async processMetricFindQuery(query: string) {
async processMetricFindQuery(query: LokiVariableQuery) {
if (query.type === LokiVariableQueryType.LabelNames) {
return this.labelNamesQuery();
}
if (!query.label) {
return [];
}
// If we have stream selector, use /series endpoint
if (query.stream) {
return this.labelValuesSeriesQuery(query.stream, query.label);
}
return this.labelValuesQuery(query.label);
}
async legacyProcessMetricFindQuery(query: string) {
const labelNames = query.match(labelNamesRegex);
if (labelNames) {
return await this.labelNamesQuery();
@@ -322,7 +356,7 @@ export class LokiDatasource
const labelValues = query.match(labelValuesRegex);
if (labelValues) {
// If we have query expr, use /series endpoint
// If we have stream selector, use /series endpoint
if (labelValues[1]) {
return await this.labelValuesSeriesQuery(labelValues[1], labelValues[2]);
}
+5 -1
View File
@@ -25,8 +25,12 @@ const defaultTimeSrvMock = {
}),
};
const defaultTemplateSrvMock = {
replace: (input: string) => input,
};
export function createLokiDatasource(
templateSrvMock: TemplateSrv,
templateSrvMock: Partial<TemplateSrv> = defaultTemplateSrvMock,
settings: Partial<DataSourceInstanceSettings<LokiOptions>> = {},
timeSrvStub = defaultTimeSrvMock
): LokiDatasource {
@@ -1,5 +1,3 @@
import { TemplateSrv } from 'app/features/templating/template_srv';
import { createLokiDatasource, createMetadataRequest } from './mocks';
import { LokiVariableQueryType } from './types';
import { LokiVariableSupport } from './variables';
@@ -8,7 +6,7 @@ describe('LokiVariableSupport', () => {
let lokiVariableSupport: LokiVariableSupport;
beforeEach(() => {
const datasource = createLokiDatasource({} as unknown as TemplateSrv);
const datasource = createLokiDatasource();
jest
.spyOn(datasource, 'metadataRequest')
.mockImplementation(
@@ -5,7 +5,7 @@ import { CustomVariableSupport, DataQueryRequest, DataQueryResponse } from '@gra
import { LokiVariableQueryEditor } from './components/VariableQueryEditor';
import { LokiDatasource } from './datasource';
import { LokiVariableQuery, LokiVariableQueryType } from './types';
import { LokiVariableQuery } from './types';
export class LokiVariableSupport extends CustomVariableSupport<LokiDatasource, LokiVariableQuery> {
editor = LokiVariableQueryEditor;
@@ -16,20 +16,7 @@ export class LokiVariableSupport extends CustomVariableSupport<LokiDatasource, L
}
async execute(query: LokiVariableQuery) {
if (query.type === LokiVariableQueryType.LabelNames) {
return this.datasource.labelNamesQuery();
}
if (!query.label) {
return [];
}
// If we have query expr, use /series endpoint
if (query.stream) {
return this.datasource.labelValuesSeriesQuery(query.stream, query.label);
}
return this.datasource.labelValuesQuery(query.label);
return this.datasource.metricFindQuery(query);
}
query(request: DataQueryRequest<LokiVariableQuery>): Observable<DataQueryResponse> {