Commit 54a3f5fd by David Committed by GitHub

Loki: use series API for stream facetting (#21332)

parent 2cf538a4
...@@ -56,11 +56,43 @@ exports[`LokiExploreQueryEditor should render component 1`] = ` ...@@ -56,11 +56,43 @@ exports[`LokiExploreQueryEditor should render component 1`] = `
"languageProvider": LokiLanguageProvider { "languageProvider": LokiLanguageProvider {
"cleanText": [Function], "cleanText": [Function],
"datasource": [Circular], "datasource": [Circular],
"fetchSeriesLabels": [Function],
"getBeginningCompletionItems": [Function], "getBeginningCompletionItems": [Function],
"getTermCompletionItems": [Function], "getTermCompletionItems": [Function],
"labelKeys": Object {}, "labelKeys": Array [],
"labelValues": Object {}, "labelsCache": LRUCache {
Symbol(max): 10,
Symbol(lengthCalculator): [Function],
Symbol(allowStale): false,
Symbol(maxAge): 0,
Symbol(dispose): undefined,
Symbol(noDisposeOnSet): false,
Symbol(updateAgeOnGet): false,
Symbol(cache): Map {},
Symbol(lruList): Yallist {
"head": null,
"length": 0,
"tail": null,
},
Symbol(length): 0,
},
"request": [Function], "request": [Function],
"seriesCache": LRUCache {
Symbol(max): 10,
Symbol(lengthCalculator): [Function],
Symbol(allowStale): false,
Symbol(maxAge): 0,
Symbol(dispose): undefined,
Symbol(noDisposeOnSet): false,
Symbol(updateAgeOnGet): false,
Symbol(cache): Map {},
Symbol(lruList): Yallist {
"head": null,
"length": 0,
"tail": null,
},
Symbol(length): 0,
},
"start": [Function], "start": [Function],
}, },
"metadataRequest": [Function], "metadataRequest": [Function],
......
...@@ -59,9 +59,9 @@ describe('useLokiSyntax hook', () => { ...@@ -59,9 +59,9 @@ describe('useLokiSyntax hook', () => {
await waitForNextUpdate(); await waitForNextUpdate();
expect(result.current.logLabelOptions).toEqual(logLabelOptionsMock2); expect(result.current.logLabelOptions).toEqual(logLabelOptionsMock2);
languageProvider.fetchLabelValues = (key: string) => { languageProvider.fetchLabelValues = (key: string, absoluteRange: AbsoluteTimeRange) => {
languageProvider.logLabelOptions = logLabelOptionsMock3; languageProvider.logLabelOptions = logLabelOptionsMock3;
return Promise.resolve(); return Promise.resolve([]);
}; };
act(() => result.current.setActiveOption([activeOptionMock])); act(() => result.current.setActiveOption([activeOptionMock]));
......
...@@ -390,9 +390,7 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> { ...@@ -390,9 +390,7 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
async metadataRequest(url: string, params?: Record<string, string>) { async metadataRequest(url: string, params?: Record<string, string>) {
const res = await this._request(url, params, { silent: true }).toPromise(); const res = await this._request(url, params, { silent: true }).toPromise();
return { return res.data.data || res.data.values || [];
data: { data: res.data.data || res.data.values || [] },
};
} }
async metricFindQuery(query: string) { async metricFindQuery(query: string) {
...@@ -423,7 +421,7 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> { ...@@ -423,7 +421,7 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
async labelNamesQuery() { async labelNamesQuery() {
const url = (await this.getVersion()) === 'v0' ? `${LEGACY_LOKI_ENDPOINT}/label` : `${LOKI_ENDPOINT}/label`; const url = (await this.getVersion()) === 'v0' ? `${LEGACY_LOKI_ENDPOINT}/label` : `${LOKI_ENDPOINT}/label`;
const result = await this.metadataRequest(url); const result = await this.metadataRequest(url);
return result.data.data.map((value: string) => ({ text: value })); return result.map((value: string) => ({ text: value }));
} }
async labelValuesQuery(label: string) { async labelValuesQuery(label: string) {
...@@ -432,7 +430,7 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> { ...@@ -432,7 +430,7 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
? `${LEGACY_LOKI_ENDPOINT}/label/${label}/values` ? `${LEGACY_LOKI_ENDPOINT}/label/${label}/values`
: `${LOKI_ENDPOINT}/label/${label}/values`; : `${LOKI_ENDPOINT}/label/${label}/values`;
const result = await this.metadataRequest(url); const result = await this.metadataRequest(url);
return result.data.data.map((value: string) => ({ text: value })); return result.map((value: string) => ({ text: value }));
} }
interpolateQueryExpr(value: any, variable: any) { interpolateQueryExpr(value: any, variable: any) {
......
import Plain from 'slate-plain-serializer'; import Plain from 'slate-plain-serializer';
import { Editor as SlateEditor } from 'slate';
import LanguageProvider, { LABEL_REFRESH_INTERVAL, LokiHistoryItem, rangeToParams } from './language_provider'; import LanguageProvider, { LABEL_REFRESH_INTERVAL, LokiHistoryItem, rangeToParams } from './language_provider';
import { AbsoluteTimeRange } from '@grafana/data'; import { AbsoluteTimeRange } from '@grafana/data';
...@@ -85,34 +84,53 @@ describe('Language completion provider', () => { ...@@ -85,34 +84,53 @@ describe('Language completion provider', () => {
}); });
}); });
describe('label suggestions', () => { describe('label key suggestions', () => {
it('returns default label suggestions on label context', async () => { it('returns all label suggestions on empty selector', async () => {
const instance = new LanguageProvider(datasource); const datasource = makeMockLokiDatasource({ label1: [], label2: [] });
const value = Plain.deserialize('{}'); const provider = await getLanguageProvider(datasource);
const ed = new SlateEditor({ value }); const input = createTypeaheadInput('{}', '', '', 1);
const valueWithSelection = ed.moveForward(1).value; const result = await provider.provideCompletionItems(input, { absoluteRange: rangeMock });
const result = await instance.provideCompletionItems(
{
text: '',
prefix: '',
wrapperClasses: ['context-labels'],
value: valueWithSelection,
},
{ absoluteRange: rangeMock }
);
expect(result.context).toBe('context-labels'); expect(result.context).toBe('context-labels');
expect(result.suggestions).toEqual([{ items: [{ label: 'job' }, { label: 'namespace' }], label: 'Labels' }]); expect(result.suggestions).toEqual([{ items: [{ label: 'label1' }, { label: 'label2' }], label: 'Labels' }]);
}); });
it('returns label suggestions from Loki', async () => { it('returns all label suggestions on selector when starting to type', async () => {
const datasource = makeMockLokiDatasource({ label1: [], label2: [] }); const datasource = makeMockLokiDatasource({ label1: [], label2: [] });
const provider = await getLanguageProvider(datasource); const provider = await getLanguageProvider(datasource);
const input = createTypeaheadInput('{}', ''); const input = createTypeaheadInput('{l}', '', '', 2);
const result = await provider.provideCompletionItems(input, { absoluteRange: rangeMock }); const result = await provider.provideCompletionItems(input, { absoluteRange: rangeMock });
expect(result.context).toBe('context-labels'); expect(result.context).toBe('context-labels');
expect(result.suggestions).toEqual([{ items: [{ label: 'label1' }, { label: 'label2' }], label: 'Labels' }]); expect(result.suggestions).toEqual([{ items: [{ label: 'label1' }, { label: 'label2' }], label: 'Labels' }]);
}); });
});
describe('label suggestions facetted', () => {
it('returns facetted label suggestions based on selector', async () => {
const datasource = makeMockLokiDatasource(
{ label1: [], label2: [] },
{ '{foo="bar"}': [{ label1: 'label_val1' }] }
);
const provider = await getLanguageProvider(datasource);
const input = createTypeaheadInput('{foo="bar",}', '', '', 11);
const result = await provider.provideCompletionItems(input, { absoluteRange: rangeMock });
expect(result.context).toBe('context-labels');
expect(result.suggestions).toEqual([{ items: [{ label: 'label1' }], label: 'Labels' }]);
});
it('returns facetted label suggestions for multipule selectors', async () => {
const datasource = makeMockLokiDatasource(
{ label1: [], label2: [] },
{ '{baz="42",foo="bar"}': [{ label2: 'label_val2' }] }
);
const provider = await getLanguageProvider(datasource);
const input = createTypeaheadInput('{baz="42",foo="bar",}', '', '', 20);
const result = await provider.provideCompletionItems(input, { absoluteRange: rangeMock });
expect(result.context).toBe('context-labels');
expect(result.suggestions).toEqual([{ items: [{ label: 'label2' }], label: 'Labels' }]);
});
});
describe('label suggestions', () => {
it('returns label values suggestions from Loki', async () => { it('returns label values suggestions from Loki', async () => {
const datasource = makeMockLokiDatasource({ label1: ['label1_val1', 'label1_val2'], label2: [] }); const datasource = makeMockLokiDatasource({ label1: ['label1_val1', 'label1_val2'], label2: [] });
const provider = await getLanguageProvider(datasource); const provider = await getLanguageProvider(datasource);
......
// Libraries // Libraries
import _ from 'lodash'; import _ from 'lodash';
import LRU from 'lru-cache';
// Services & Utils // Services & Utils
import { parseSelector, labelRegexp, selectorRegexp } from 'app/plugins/datasource/prometheus/language_utils'; import {
parseSelector,
labelRegexp,
selectorRegexp,
processLabels,
} from 'app/plugins/datasource/prometheus/language_utils';
import syntax, { FUNCTIONS } from './syntax'; import syntax, { FUNCTIONS } from './syntax';
// Types // Types
...@@ -12,7 +18,7 @@ import { PromQuery } from '../prometheus/types'; ...@@ -12,7 +18,7 @@ import { PromQuery } from '../prometheus/types';
import { RATE_RANGES } from '../prometheus/promql'; import { RATE_RANGES } from '../prometheus/promql';
import LokiDatasource from './datasource'; import LokiDatasource from './datasource';
import { CompletionItem, TypeaheadInput, TypeaheadOutput } from '@grafana/ui'; import { CompletionItem, TypeaheadInput, TypeaheadOutput, CompletionItemGroup } from '@grafana/ui';
import { Grammar } from 'prismjs'; import { Grammar } from 'prismjs';
const DEFAULT_KEYS = ['job', 'namespace']; const DEFAULT_KEYS = ['job', 'namespace'];
...@@ -50,20 +56,27 @@ export function addHistoryMetadata(item: CompletionItem, history: LokiHistoryIte ...@@ -50,20 +56,27 @@ export function addHistoryMetadata(item: CompletionItem, history: LokiHistoryIte
} }
export default class LokiLanguageProvider extends LanguageProvider { export default class LokiLanguageProvider extends LanguageProvider {
labelKeys?: { [index: string]: string[] }; // metric -> [labelKey,...] labelKeys?: string[];
labelValues?: { [index: string]: { [index: string]: string[] } }; // metric -> labelKey -> [labelValue,...]
logLabelOptions: any[]; logLabelOptions: any[];
logLabelFetchTs?: number; logLabelFetchTs?: number;
started: boolean; started: boolean;
initialRange: AbsoluteTimeRange; initialRange: AbsoluteTimeRange;
datasource: LokiDatasource; datasource: LokiDatasource;
lookupsDisabled: boolean; // Dynamically set to true for big/slow instances
/**
* Cache for labels of series. This is bit simplistic in the sense that it just counts responses each as a 1 and does
* not account for different size of a response. If that is needed a `length` function can be added in the options.
* 10 as a max size is totally arbitrary right now.
*/
private seriesCache = new LRU<string, Record<string, string[]>>(10);
private labelsCache = new LRU<string, string[]>(10);
constructor(datasource: LokiDatasource, initialValues?: any) { constructor(datasource: LokiDatasource, initialValues?: any) {
super(); super();
this.datasource = datasource; this.datasource = datasource;
this.labelKeys = {}; this.labelKeys = [];
this.labelValues = {};
Object.assign(this, initialValues); Object.assign(this, initialValues);
} }
...@@ -75,8 +88,14 @@ export default class LokiLanguageProvider extends LanguageProvider { ...@@ -75,8 +88,14 @@ export default class LokiLanguageProvider extends LanguageProvider {
return syntax; return syntax;
} }
request = (url: string, params?: any): Promise<{ data: { data: string[] } }> => { request = async (url: string, params?: any): Promise<any> => {
return this.datasource.metadataRequest(url, params); try {
return await this.datasource.metadataRequest(url, params);
} catch (error) {
console.error(error);
}
return undefined;
}; };
/** /**
...@@ -95,12 +114,7 @@ export default class LokiLanguageProvider extends LanguageProvider { ...@@ -95,12 +114,7 @@ export default class LokiLanguageProvider extends LanguageProvider {
}; };
getLabelKeys(): string[] { getLabelKeys(): string[] {
return this.labelKeys[EMPTY_SELECTOR]; return this.labelKeys;
}
async getLabelValues(key: string): Promise<string[]> {
await this.fetchLabelValues(key, this.initialRange);
return this.labelValues[EMPTY_SELECTOR][key];
} }
/** /**
...@@ -219,42 +233,66 @@ export default class LokiLanguageProvider extends LanguageProvider { ...@@ -219,42 +233,66 @@ export default class LokiLanguageProvider extends LanguageProvider {
{ text, wrapperClasses, labelKey, value }: TypeaheadInput, { text, wrapperClasses, labelKey, value }: TypeaheadInput,
{ absoluteRange }: any { absoluteRange }: any
): Promise<TypeaheadOutput> { ): Promise<TypeaheadOutput> {
let context: string; let context = 'context-labels';
const suggestions = []; const suggestions: CompletionItemGroup[] = [];
const line = value.anchorBlock.getText(); const line = value.anchorBlock.getText();
const cursorOffset: number = value.selection.anchor.offset; const cursorOffset = value.selection.anchor.offset;
const isValueStart = text.match(/^(=|=~|!=|!~)/);
// Use EMPTY_SELECTOR until series API is implemented for facetting // Get normalized selector
const selector = EMPTY_SELECTOR; let selector;
let parsedSelector; let parsedSelector;
try { try {
parsedSelector = parseSelector(line, cursorOffset); parsedSelector = parseSelector(line, cursorOffset);
} catch {} selector = parsedSelector.selector;
} catch {
selector = EMPTY_SELECTOR;
}
if (!isValueStart && selector === EMPTY_SELECTOR) {
// start task gets all labels
await this.start();
const allLabels = this.getLabelKeys();
return { context, suggestions: [{ label: `Labels`, items: allLabels.map(wrapLabel) }] };
}
const existingKeys = parsedSelector ? parsedSelector.labelKeys : []; const existingKeys = parsedSelector ? parsedSelector.labelKeys : [];
if ((text && text.match(/^!?=~?/)) || wrapperClasses.includes('attr-value')) { let labelValues;
// Label values // Query labels for selector
if (labelKey && this.labelValues[selector]) { if (selector) {
let labelValues = this.labelValues[selector][labelKey]; if (selector === EMPTY_SELECTOR && labelKey) {
const labelValuesForKey = await this.getLabelValues(labelKey);
labelValues = { [labelKey]: labelValuesForKey };
} else {
labelValues = await this.getSeriesLabels(selector, absoluteRange);
}
}
if (!labelValues) { if (!labelValues) {
await this.fetchLabelValues(labelKey, absoluteRange); console.warn(`Server did not return any values for selector = ${selector}`);
labelValues = this.labelValues[selector][labelKey]; return { context, suggestions };
} }
if ((text && isValueStart) || wrapperClasses.includes('attr-value')) {
// Label values
if (labelKey && labelValues[labelKey]) {
context = 'context-label-values'; context = 'context-label-values';
suggestions.push({ suggestions.push({
label: `Label values for "${labelKey}"`, label: `Label values for "${labelKey}"`,
items: labelValues.map(wrapLabel), items: labelValues[labelKey].map(wrapLabel),
}); });
} }
} else { } else {
// Label keys // Label keys
const labelKeys = this.labelKeys[selector] || DEFAULT_KEYS; const labelKeys = labelValues ? Object.keys(labelValues) : DEFAULT_KEYS;
if (labelKeys) { if (labelKeys) {
const possibleKeys = _.difference(labelKeys, existingKeys); const possibleKeys = _.difference(labelKeys, existingKeys);
if (possibleKeys.length) { if (possibleKeys.length) {
context = 'context-labels'; const newItems = possibleKeys.map(key => ({ label: key }));
suggestions.push({ label: `Labels`, items: possibleKeys.map(wrapLabel) }); const newSuggestion: CompletionItemGroup = { label: `Labels`, items: newItems };
suggestions.push(newSuggestion);
} }
} }
} }
...@@ -302,7 +340,7 @@ export default class LokiLanguageProvider extends LanguageProvider { ...@@ -302,7 +340,7 @@ export default class LokiLanguageProvider extends LanguageProvider {
// Keep only labels that exist on origin and target datasource // Keep only labels that exist on origin and target datasource
await this.start(); // fetches all existing label keys await this.start(); // fetches all existing label keys
const existingKeys = this.labelKeys[EMPTY_SELECTOR]; const existingKeys = this.labelKeys;
let labelsToKeep: { [key: string]: { value: any; operator: any } } = {}; let labelsToKeep: { [key: string]: { value: any; operator: any } } = {};
if (existingKeys && existingKeys.length) { if (existingKeys && existingKeys.length) {
// Check for common labels // Check for common labels
...@@ -325,22 +363,31 @@ export default class LokiLanguageProvider extends LanguageProvider { ...@@ -325,22 +363,31 @@ export default class LokiLanguageProvider extends LanguageProvider {
return ['{', cleanSelector, '}'].join(''); return ['{', cleanSelector, '}'].join('');
} }
async getSeriesLabels(selector: string, absoluteRange: AbsoluteTimeRange) {
if (this.lookupsDisabled) {
return undefined;
}
try {
return await this.fetchSeriesLabels(selector, absoluteRange);
} catch (error) {
// TODO: better error handling
console.error(error);
return undefined;
}
}
/**
* Fetches all label keys
* @param absoluteRange Fetches
*/
async fetchLogLabels(absoluteRange: AbsoluteTimeRange): Promise<any> { async fetchLogLabels(absoluteRange: AbsoluteTimeRange): Promise<any> {
const url = '/api/prom/label'; const url = '/api/prom/label';
try { try {
this.logLabelFetchTs = Date.now(); this.logLabelFetchTs = Date.now();
const rangeParams = absoluteRange ? rangeToParams(absoluteRange) : {}; const rangeParams = absoluteRange ? rangeToParams(absoluteRange) : {};
const res = await this.request(url, rangeParams); const res = await this.request(url, rangeParams);
const labelKeys = res.data.data.slice().sort(); this.labelKeys = res.slice().sort();
this.logLabelOptions = this.labelKeys.map((key: string) => ({ label: key, value: key, isLeaf: false }));
this.labelKeys = {
...this.labelKeys,
[EMPTY_SELECTOR]: labelKeys,
};
this.labelValues = {
[EMPTY_SELECTOR]: {},
};
this.logLabelOptions = labelKeys.map((key: string) => ({ label: key, value: key, isLeaf: false }));
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
...@@ -353,12 +400,64 @@ export default class LokiLanguageProvider extends LanguageProvider { ...@@ -353,12 +400,64 @@ export default class LokiLanguageProvider extends LanguageProvider {
} }
} }
async fetchLabelValues(key: string, absoluteRange: AbsoluteTimeRange) { /**
* Fetch labels for a selector. This is cached by it's args but also by the global timeRange currently selected as
* they can change over requested time.
* @param name
*/
fetchSeriesLabels = async (match: string, absoluteRange: AbsoluteTimeRange): Promise<Record<string, string[]>> => {
const rangeParams: { start?: number; end?: number } = absoluteRange ? rangeToParams(absoluteRange) : {};
const url = '/loki/api/v1/series';
const { start, end } = rangeParams;
const cacheKey = this.generateCacheKey(url, start, end, match);
const params = { match, start, end };
let value = this.seriesCache.get(cacheKey);
if (!value) {
// Clear value when requesting new one. Empty object being truthy also makes sure we don't request twice.
this.seriesCache.set(cacheKey, {});
const data = await this.request(url, params);
const { values } = processLabels(data);
value = values;
this.seriesCache.set(cacheKey, value);
}
return value;
};
// Cache key is a bit different here. We round up to a minute the intervals.
// The rounding may seem strange but makes relative intervals like now-1h less prone to need separate request every
// millisecond while still actually getting all the keys for the correct interval. This still can create problems
// when user does not the newest values for a minute if already cached.
generateCacheKey(url: string, start: number, end: number, param: string): string {
return [url, this.roundTime(start), this.roundTime(end), param].join();
}
// Round nanos epoch to nearest 5 minute interval
roundTime(nanos: number): number {
return nanos ? Math.floor(nanos / NS_IN_MS / 1000 / 60 / 5) : 0;
}
async getLabelValues(key: string): Promise<string[]> {
return await this.fetchLabelValues(key, this.initialRange);
}
async fetchLabelValues(key: string, absoluteRange: AbsoluteTimeRange): Promise<string[]> {
const url = `/api/prom/label/${key}/values`; const url = `/api/prom/label/${key}/values`;
let values: string[] = [];
const rangeParams: { start?: number; end?: number } = absoluteRange ? rangeToParams(absoluteRange) : {};
const { start, end } = rangeParams;
const cacheKey = this.generateCacheKey(url, start, end, key);
const params = { start, end };
let value = this.labelsCache.get(cacheKey);
if (!value) {
try { try {
const rangeParams = absoluteRange ? rangeToParams(absoluteRange) : {}; // Clear value when requesting new one. Empty object being truthy also makes sure we don't request twice.
const res = await this.request(url, rangeParams); this.labelsCache.set(cacheKey, []);
const values = res.data.data.slice().sort(); const res = await this.request(url, params);
values = res.slice().sort();
value = values;
this.labelsCache.set(cacheKey, value);
// Add to label options // Add to label options
this.logLabelOptions = this.logLabelOptions.map(keyOption => { this.logLabelOptions = this.logLabelOptions.map(keyOption => {
...@@ -370,19 +469,10 @@ export default class LokiLanguageProvider extends LanguageProvider { ...@@ -370,19 +469,10 @@ export default class LokiLanguageProvider extends LanguageProvider {
} }
return keyOption; return keyOption;
}); });
// Add to key map
const exisingValues = this.labelValues[EMPTY_SELECTOR];
const nextValues = {
...exisingValues,
[key]: values,
};
this.labelValues = {
...this.labelValues,
[EMPTY_SELECTOR]: nextValues,
};
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
} }
return value;
}
} }
...@@ -3,34 +3,45 @@ import { DataSourceSettings } from '@grafana/data'; ...@@ -3,34 +3,45 @@ import { DataSourceSettings } from '@grafana/data';
import { LokiOptions } from './types'; import { LokiOptions } from './types';
import { createDatasourceSettings } from '../../../features/datasources/mocks'; import { createDatasourceSettings } from '../../../features/datasources/mocks';
export function makeMockLokiDatasource(labelsAndValues: { [label: string]: string[] }): LokiDatasource { interface Labels {
[label: string]: string[];
}
interface Series {
[label: string]: string;
}
interface SeriesForSelector {
[selector: string]: Series[];
}
export function makeMockLokiDatasource(labelsAndValues: Labels, series?: SeriesForSelector): LokiDatasource {
const legacyLokiLabelsAndValuesEndpointRegex = /^\/api\/prom\/label\/(\w*)\/values/; const legacyLokiLabelsAndValuesEndpointRegex = /^\/api\/prom\/label\/(\w*)\/values/;
const lokiLabelsAndValuesEndpointRegex = /^\/loki\/api\/v1\/label\/(\w*)\/values/; const lokiLabelsAndValuesEndpointRegex = /^\/loki\/api\/v1\/label\/(\w*)\/values/;
const lokiSeriesEndpointRegex = /^\/loki\/api\/v1\/series/;
const legacyLokiLabelsEndpoint = `${LEGACY_LOKI_ENDPOINT}/label`; const legacyLokiLabelsEndpoint = `${LEGACY_LOKI_ENDPOINT}/label`;
const lokiLabelsEndpoint = `${LOKI_ENDPOINT}/label`; const lokiLabelsEndpoint = `${LOKI_ENDPOINT}/label`;
const labels = Object.keys(labelsAndValues); const labels = Object.keys(labelsAndValues);
return { return {
metadataRequest: (url: string) => { metadataRequest: (url: string, params?: { [key: string]: string }) => {
let responseData;
if (url === legacyLokiLabelsEndpoint || url === lokiLabelsEndpoint) { if (url === legacyLokiLabelsEndpoint || url === lokiLabelsEndpoint) {
responseData = labels; return labels;
} else { } else {
const match = url.match(legacyLokiLabelsAndValuesEndpointRegex) || url.match(lokiLabelsAndValuesEndpointRegex); const legacyLabelsMatch = url.match(legacyLokiLabelsAndValuesEndpointRegex);
if (match) { const labelsMatch = url.match(lokiLabelsAndValuesEndpointRegex);
responseData = labelsAndValues[match[1]]; const seriesMatch = url.match(lokiSeriesEndpointRegex);
} if (legacyLabelsMatch) {
} return labelsAndValues[legacyLabelsMatch[1]] || [];
if (responseData) { } else if (labelsMatch) {
return { return labelsAndValues[labelsMatch[1]] || [];
data: { } else if (seriesMatch) {
data: responseData, return series[params.match] || [];
},
};
} else { } else {
throw new Error(`Unexpected url error, ${url}`); throw new Error(`Unexpected url error, ${url}`);
} }
}
}, },
} as any; } as any;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment