Loki: Refactor template variable logic and remove reliance on timeSrv (#78586)

* Loki: Refactor variables support to reuse loic

* Remove redundant methods

* Make parseStringToVariableQuery private
This commit is contained in:
Ivana Huckova 2023-11-24 12:52:29 +01:00 committed by GitHub
parent 8120306fea
commit 8a7eb4b484
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 57 additions and 94 deletions

View File

@ -5947,8 +5947,7 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "2"], [0, 0, 0, "Unexpected any. Specify a different type.", "2"],
[0, 0, 0, "Unexpected any. Specify a different type.", "3"], [0, 0, 0, "Unexpected any. Specify a different type.", "3"],
[0, 0, 0, "Unexpected any. Specify a different type.", "4"], [0, 0, 0, "Unexpected any. Specify a different type.", "4"],
[0, 0, 0, "Unexpected any. Specify a different type.", "5"], [0, 0, 0, "Unexpected any. Specify a different type.", "5"]
[0, 0, 0, "Unexpected any. Specify a different type.", "6"]
], ],
"public/app/plugins/datasource/loki/queryUtils.ts:5381": [ "public/app/plugins/datasource/loki/queryUtils.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"] [0, 0, 0, "Do not use any type assertions.", "0"]

View File

@ -1,6 +1,6 @@
import { LokiVariableSupport } from './LokiVariableSupport';
import { createLokiDatasource, createMetadataRequest } from './mocks'; import { createLokiDatasource, createMetadataRequest } from './mocks';
import { LokiVariableQueryType } from './types'; import { LokiVariableQueryType } from './types';
import { LokiVariableSupport } from './variables';
describe('LokiVariableSupport', () => { describe('LokiVariableSupport', () => {
let lokiVariableSupport: LokiVariableSupport; let lokiVariableSupport: LokiVariableSupport;

View File

@ -26,14 +26,7 @@ describe('LokiVariableQueryEditor', () => {
onChange: () => {}, onChange: () => {},
}; };
jest.spyOn(props.datasource, 'labelNamesQuery').mockResolvedValue([ jest.spyOn(props.datasource.languageProvider, 'fetchLabels').mockResolvedValue(['luna', 'moon']);
{
text: 'moon',
},
{
text: 'luna',
},
]);
}); });
test('Allows to create a Label names variable', async () => { test('Allows to create a Label names variable', async () => {

View File

@ -38,8 +38,8 @@ export const LokiVariableQueryEditor = ({ onChange, query, datasource }: Props)
return; return;
} }
datasource.labelNamesQuery().then((labelNames: Array<{ text: string }>) => { datasource.languageProvider.fetchLabels().then((labelNames: string[]) => {
setLabelOptions(labelNames.map(({ text }) => ({ label: text, value: text }))); setLabelOptions(labelNames.map((labelName) => ({ label: labelName, value: labelName })));
}); });
}, [datasource, type]); }, [datasource, type]);

View File

@ -28,12 +28,12 @@ import {
TemplateSrv, TemplateSrv,
} from '@grafana/runtime'; } from '@grafana/runtime';
import { LokiVariableSupport } from './LokiVariableSupport';
import { LokiDatasource, REF_ID_DATA_SAMPLES } from './datasource'; import { LokiDatasource, REF_ID_DATA_SAMPLES } from './datasource';
import { createLokiDatasource, createMetadataRequest } from './mocks'; import { createLokiDatasource, createMetadataRequest } from './mocks';
import { runSplitQuery } from './querySplitting'; import { runSplitQuery } from './querySplitting';
import { parseToNodeNamesArray } from './queryUtils'; import { parseToNodeNamesArray } from './queryUtils';
import { LokiOptions, LokiQuery, LokiQueryType, LokiVariableQueryType, SupportingQueryType } from './types'; import { LokiOptions, LokiQuery, LokiQueryType, LokiVariableQueryType, SupportingQueryType } from './types';
import { LokiVariableSupport } from './variables';
jest.mock('@grafana/runtime', () => { jest.mock('@grafana/runtime', () => {
return { return {

View File

@ -37,6 +37,9 @@ import {
LegacyMetricFindQueryOptions, LegacyMetricFindQueryOptions,
AdHocVariableFilter, AdHocVariableFilter,
urlUtil, urlUtil,
MetricFindValue,
DataSourceGetTagValuesOptions,
DataSourceGetTagKeysOptions,
DataSourceWithQueryModificationSupport, DataSourceWithQueryModificationSupport,
} from '@grafana/data'; } from '@grafana/data';
import { Duration } from '@grafana/lezer-logql'; import { Duration } from '@grafana/lezer-logql';
@ -51,6 +54,7 @@ import { replaceVariables, returnVariables } from '../prometheus/querybuilder/sh
import LanguageProvider from './LanguageProvider'; import LanguageProvider from './LanguageProvider';
import { LiveStreams, LokiLiveTarget } from './LiveStreams'; import { LiveStreams, LokiLiveTarget } from './LiveStreams';
import { LogContextProvider } from './LogContextProvider'; import { LogContextProvider } from './LogContextProvider';
import { LokiVariableSupport } from './LokiVariableSupport';
import { transformBackendResult } from './backendResultTransformer'; import { transformBackendResult } from './backendResultTransformer';
import { LokiAnnotationsQueryEditor } from './components/AnnotationsQueryEditor'; import { LokiAnnotationsQueryEditor } from './components/AnnotationsQueryEditor';
import { placeHolderScopedVars } from './components/monaco-query-field/monaco-completion-provider/validation'; import { placeHolderScopedVars } from './components/monaco-query-field/monaco-completion-provider/validation';
@ -94,7 +98,6 @@ import {
QueryStats, QueryStats,
SupportingQueryType, SupportingQueryType,
} from './types'; } from './types';
import { LokiVariableSupport } from './variables';
export type RangeQueryOptions = DataQueryRequest<LokiQuery> | AnnotationQueryRequest<LokiQuery>; export type RangeQueryOptions = DataQueryRequest<LokiQuery> | AnnotationQueryRequest<LokiQuery>;
export const DEFAULT_MAX_LINES = 1000; export const DEFAULT_MAX_LINES = 1000;
@ -646,23 +649,31 @@ export class LokiDatasource
* Implemented as part of DataSourceAPI and used for template variable queries. * Implemented as part of DataSourceAPI and used for template variable queries.
* @returns A Promise that resolves to an array of results from the metric find query. * @returns A Promise that resolves to an array of results from the metric find query.
*/ */
async metricFindQuery(query: LokiVariableQuery | string, options?: LegacyMetricFindQueryOptions) { async metricFindQuery(
query: LokiVariableQuery | string,
options?: LegacyMetricFindQueryOptions
): Promise<MetricFindValue[]> {
if (!query) { if (!query) {
return Promise.resolve([]); return Promise.resolve([]);
} }
let interpolatedVariableQuery: LokiVariableQuery | undefined;
if (typeof query === 'string') { if (typeof query === 'string') {
const interpolated = this.interpolateString(query, options?.scopedVars); interpolatedVariableQuery = this.parseStringToVariableQuery(this.interpolateString(query, options?.scopedVars));
return await this.legacyProcessMetricFindQuery(interpolated); } else {
interpolatedVariableQuery = {
...query,
label: this.interpolateString(query.label || '', options?.scopedVars),
stream: this.interpolateString(query.stream || '', options?.scopedVars),
};
} }
const interpolatedQuery = { if (interpolatedVariableQuery) {
...query, return await this.processMetricFindQuery(interpolatedVariableQuery, options?.range);
label: this.interpolateString(query.label || '', options?.scopedVars), }
stream: this.interpolateString(query.stream || '', options?.scopedVars),
};
return await this.processMetricFindQuery(interpolatedQuery); return Promise.resolve([]);
} }
/** /**
@ -670,9 +681,10 @@ export class LokiDatasource
* @returns A Promise that resolves to an array of variable results based on the query type and parameters. * @returns A Promise that resolves to an array of variable results based on the query type and parameters.
*/ */
private async processMetricFindQuery(query: LokiVariableQuery) { private async processMetricFindQuery(query: LokiVariableQuery, timeRange?: TimeRange): Promise<MetricFindValue[]> {
if (query.type === LokiVariableQueryType.LabelNames) { if (query.type === LokiVariableQueryType.LabelNames) {
return this.labelNamesQuery(); const result = await this.languageProvider.fetchLabels({ timeRange });
return result.map((value: string) => ({ text: value }));
} }
if (!query.label) { if (!query.label) {
@ -681,81 +693,38 @@ export class LokiDatasource
// If we have stream selector, use /series endpoint // If we have stream selector, use /series endpoint
if (query.stream) { if (query.stream) {
return this.labelValuesSeriesQuery(query.stream, query.label); const result = await this.languageProvider.fetchSeriesLabels(query.stream, { timeRange });
return result[query.label].map((value: string) => ({ text: value }));
} }
return this.labelValuesQuery(query.label); const result = await this.languageProvider.fetchLabelValues(query.label, { timeRange });
return result.map((value: string) => ({ text: value }));
} }
/** /**
* Used in `metricFindQuery` to process legacy query strings (label_name() and label_values()) and return variable results. * Used in `metricFindQuery` to process legacy query strings (label_name() and label_values()) to variable query objects.
* @returns A Promise that resolves to an array of variables based on the legacy query string. * @returns LokiVariableQuery object based on the provided query string, or undefined if string can't be parsed.
* @todo It can be refactored in the future to return a LokiVariableQuery and be used in `processMetricFindQuery`
* to not duplicate querying logic.
*/ */
async legacyProcessMetricFindQuery(query: string) { private parseStringToVariableQuery(query: string): LokiVariableQuery | undefined {
const refId = 'LokiVariableQueryEditor-VariableQuery';
const labelNames = query.match(labelNamesRegex); const labelNames = query.match(labelNamesRegex);
if (labelNames) { if (labelNames) {
return await this.labelNamesQuery(); return {
type: LokiVariableQueryType.LabelNames,
refId,
};
} }
const labelValues = query.match(labelValuesRegex); const labelValues = query.match(labelValuesRegex);
if (labelValues) { if (labelValues) {
// If we have stream selector, use /series endpoint return {
if (labelValues[1]) { type: LokiVariableQueryType.LabelValues,
return await this.labelValuesSeriesQuery(labelValues[1], labelValues[2]); label: labelValues[2],
} stream: labelValues[1],
return await this.labelValuesQuery(labelValues[2]); refId,
};
} }
return undefined;
return Promise.resolve([]);
}
/**
* Private method used in `processMetricFindQuery`, `legacyProcessMetricFindQuery` and `getTagKeys` to fetch label names.
* @returns A Promise that resolves to an array of label names as text values.
* @todo Future exploration may involve using the `languageProvider.fetchLabels()` to avoid duplicating logic.
*/
async labelNamesQuery() {
const url = 'labels';
const params = this.getTimeRangeParams();
const result = await this.metadataRequest(url, params);
return result.map((value: string) => ({ text: value }));
}
/**
* Private method used in `processMetricFindQuery`, `legacyProcessMetricFindQuery` `getTagValues` to fetch label values.
* @returns A Promise that resolves to an array of label values as text values.
* @todo Future exploration may involve using the `languageProvider.fetchLabelValues()` method to avoid duplicating logic.
*/
private async labelValuesQuery(label: string) {
const params = this.getTimeRangeParams();
const url = `label/${label}/values`;
const result = await this.metadataRequest(url, params);
return result.map((value: string) => ({ text: value }));
}
/**
* Private method used in `processMetricFindQuery` and `legacyProcessMetricFindQuery` to fetch label values for specified stream.
* @returns A Promise that resolves to an array of label values as text values.
* @todo Future exploration may involve using the `languageProvider.fetchLabelValues()` or `languageProvider.fetchSeriesLabels()` method to avoid duplicating logic.
*/
private async labelValuesSeriesQuery(expr: string, label: string) {
const timeParams = this.getTimeRangeParams();
const params = {
...timeParams,
'match[]': expr,
};
const url = 'series';
const streams = new Set();
const result = await this.metadataRequest(url, params);
result.forEach((stream: { [key: string]: string }) => {
if (stream[label]) {
streams.add({ text: stream[label] });
}
});
return Array.from(streams);
} }
/** /**
@ -784,18 +753,20 @@ export class LokiDatasource
/** /**
* Implemented as part of the DataSourceAPI. Retrieves tag keys that can be used for ad-hoc filtering. * Implemented as part of the DataSourceAPI. Retrieves tag keys that can be used for ad-hoc filtering.
* @returns A Promise that resolves to an array of label names. * @returns A Promise that resolves to an array of label names represented as MetricFindValue objects.
*/ */
async getTagKeys() { async getTagKeys(options?: DataSourceGetTagKeysOptions): Promise<MetricFindValue[]> {
return await this.labelNamesQuery(); const result = await this.languageProvider.fetchLabels({ timeRange: options?.timeRange });
return result.map((value: string) => ({ text: value }));
} }
/** /**
* Implemented as part of the DataSourceAPI. Retrieves tag values that can be used for ad-hoc filtering. * Implemented as part of the DataSourceAPI. Retrieves tag values that can be used for ad-hoc filtering.
* @returns A Promise that resolves to an array of label values. * @returns A Promise that resolves to an array of label values represented as MetricFindValue objects
*/ */
async getTagValues(options: any = {}) { async getTagValues(options: DataSourceGetTagValuesOptions): Promise<MetricFindValue[]> {
return await this.labelValuesQuery(options.key); const result = await this.languageProvider.fetchLabelValues(options.key, { timeRange: options.timeRange });
return result.map((value: string) => ({ text: value }));
} }
/** /**