Loki: Add optional stream selector to fetchLabelValues API (#77207)

* Loki: Add optional stream selector to fetchLabelValues API

* Update public/app/plugins/datasource/loki/docs/app_plugin_developer_documentation.md

Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com>

* Update tests

---------

Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com>
This commit is contained in:
Ivana Huckova 2023-10-30 10:16:35 +01:00 committed by GitHub
parent dae49fbb34
commit f5d04a067e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 86 additions and 9 deletions

View File

@ -233,7 +233,23 @@ describe('Language completion provider', () => {
const provider = await getLanguageProvider(datasource);
const requestSpy = jest.spyOn(provider, 'request');
const labelValues = await provider.fetchLabelValues('testkey');
expect(requestSpy).toHaveBeenCalled();
expect(requestSpy).toHaveBeenCalledWith('label/testkey/values', {
end: 1560163909000,
start: 1560153109000,
});
expect(labelValues).toEqual(['label1_val1', 'label1_val2']);
});
it('fetch label when options.streamSelector provided and values is not cached', async () => {
const datasource = setup({ testkey: ['label1_val1', 'label1_val2'], label2: [] });
const provider = await getLanguageProvider(datasource);
const requestSpy = jest.spyOn(provider, 'request');
const labelValues = await provider.fetchLabelValues('testkey', { streamSelector: '{foo="bar"}' });
expect(requestSpy).toHaveBeenCalledWith('label/testkey/values', {
end: 1560163909000,
query: '%7Bfoo%3D%22bar%22%7D',
start: 1560153109000,
});
expect(labelValues).toEqual(['label1_val1', 'label1_val2']);
});
@ -247,6 +263,28 @@ describe('Language completion provider', () => {
const nextLabelValues = await provider.fetchLabelValues('testkey');
expect(requestSpy).toHaveBeenCalledTimes(1);
expect(requestSpy).toHaveBeenCalledWith('label/testkey/values', {
end: 1560163909000,
start: 1560153109000,
});
expect(nextLabelValues).toEqual(['label1_val1', 'label1_val2']);
});
it('should return cached values when options.streamSelector provided', async () => {
const datasource = setup({ testkey: ['label1_val1', 'label1_val2'], label2: [] });
const provider = await getLanguageProvider(datasource);
const requestSpy = jest.spyOn(provider, 'request');
const labelValues = await provider.fetchLabelValues('testkey', { streamSelector: '{foo="bar"}' });
expect(requestSpy).toHaveBeenCalledTimes(1);
expect(requestSpy).toHaveBeenCalledWith('label/testkey/values', {
end: 1560163909000,
query: '%7Bfoo%3D%22bar%22%7D',
start: 1560153109000,
});
expect(labelValues).toEqual(['label1_val1', 'label1_val2']);
const nextLabelValues = await provider.fetchLabelValues('testkey', { streamSelector: '{foo="bar"}' });
expect(requestSpy).toHaveBeenCalledTimes(1);
expect(nextLabelValues).toEqual(['label1_val1', 'label1_val2']);
});
@ -258,6 +296,19 @@ describe('Language completion provider', () => {
expect(requestSpy).toHaveBeenCalledWith('label/%60%5C%22testkey/values', expect.any(Object));
});
it('should encode special characters in options.streamSelector', async () => {
const datasource = setup({ '`\\"testkey': ['label1_val1', 'label1_val2'], label2: [] });
const provider = await getLanguageProvider(datasource);
const requestSpy = jest.spyOn(provider, 'request');
await provider.fetchLabelValues('`\\"testkey', { streamSelector: '{foo="\\bar"}' });
expect(requestSpy).toHaveBeenCalledWith(expect.any(String), {
query: '%7Bfoo%3D%22%5Cbar%22%7D',
start: expect.any(Number),
end: expect.any(Number),
});
});
});
});

View File

@ -2,7 +2,7 @@ import { chain, difference } from 'lodash';
import { LRUCache } from 'lru-cache';
import Prism, { Grammar } from 'prismjs';
import { dateTime, AbsoluteTimeRange, LanguageProvider, HistoryItem, AbstractQuery } from '@grafana/data';
import { dateTime, AbsoluteTimeRange, LanguageProvider, HistoryItem, AbstractQuery, KeyValue } from '@grafana/data';
import { CompletionItem, TypeaheadInput, TypeaheadOutput, CompletionItemGroup } from '@grafana/ui';
import {
extractLabelMatchers,
@ -452,18 +452,29 @@ export default class LokiLanguageProvider extends LanguageProvider {
* It returns a promise that resolves to an array of strings containing the label values.
*
* @param labelName - The name of the label for which you want to retrieve values.
* @param options - (Optional) An object containing additional options - currently only stream selector.
* @param options.streamSelector - (Optional) The stream selector to filter label values. If not provided, all label values are fetched.
* @returns A promise containing an array of label values.
* @throws An error if the fetch operation fails.
*/
async fetchLabelValues(labelName: string): Promise<string[]> {
const interpolatedKey = encodeURIComponent(this.datasource.interpolateString(labelName));
async fetchLabelValues(labelName: string, options?: { streamSelector?: string }): Promise<string[]> {
const label = encodeURIComponent(this.datasource.interpolateString(labelName));
const streamParam = options?.streamSelector
? encodeURIComponent(this.datasource.interpolateString(options.streamSelector))
: undefined;
const url = `label/${interpolatedKey}/values`;
const url = `label/${label}/values`;
const rangeParams = this.datasource.getTimeRangeParams();
const { start, end } = rangeParams;
const params: KeyValue<string | number> = { start, end };
let paramCacheKey = label;
const cacheKey = this.generateCacheKey(url, start, end, interpolatedKey);
const params = { start, end };
if (streamParam) {
params.query = streamParam;
paramCacheKey += streamParam;
}
const cacheKey = this.generateCacheKey(url, start, end, paramCacheKey);
let labelValues = this.labelsCache.get(cacheKey);
if (!labelValues) {

View File

@ -58,13 +58,15 @@ The `datasource.languageProvider.fetchLabelValues()` method is designed for fetc
* It returns a promise that resolves to an array of strings containing the label values.
*
* @param labelName - The name of the label for which you want to retrieve values.
* @param options - (Optional) An object containing additional options - currently only stream selector.
* @param options.streamSelector - (Optional) The stream selector to filter label values. If not provided, all label values are fetched.
* @returns A promise containing an array of label values.
* @throws An error if the fetch operation fails.
*/
async function fetchLabelValues(labelName: string): Promise<string[]>;
async function fetchLabelValues(labelName: string, options?: { streamSelector?: string }): Promise<string[]>;
/**
* Example usage:
* Example usage without stream selector:
*/
const labelName = 'job';
@ -74,6 +76,19 @@ try {
} catch (error) {
console.error(`Error fetching label values: ${error.message}`);
}
/**
* Example usage with stream selector:
*/
const labelName = 'job';
const streamSelector = '{app="grafana"}';
try {
const values = await fetchLabelValues(labelName, { streamSelector });
console.log(values);
} catch (error) {
console.error(`Error fetching label values: ${error.message}`);
}
```
### Fetching Loki labels for a specified selector