Loki: Add support for "label_values(log stream selector, label)" in templating (#35488)

* Use series endpoint if we have queries expr

* Update documentation
This commit is contained in:
Ivana Huckova 2021-06-11 04:57:40 -04:00 committed by GitHub
parent 5ac2a7cca6
commit 97a59a4855
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 57 additions and 39 deletions

View File

@ -148,10 +148,11 @@ Check out the [Templating]({{< relref "../variables/_index.md" >}}) documentatio
Variable of the type _Query_ allows you to query Loki for a list labels or label values. The Loki data source plugin
provides the following functions you can use in the `Query` input field.
| Name | Description |
| --------------------- | --------------------------------------------------------------- |
| `label_names()` | Returns a list of label names. |
| `label_values(label)` | Returns a list of label values for the `label` in every metric. |
| Name | Description |
| -------------------------------------------| -------------------------------------------------------------------------------------|
| `label_names()` | Returns a list of label names. |
| `label_values(label)` | Returns a list of label values for the `label`. |
| `label_values(log stream selector, label)` | Returns a list of label values for the `label` in the specified `log stream selector`.|
## Annotations

View File

@ -548,42 +548,46 @@ describe('LokiDatasource', () => {
describe('metricFindQuery', () => {
const getTestContext = (mock: LokiDatasource) => {
const ds = createLokiDSForTests();
ds.getVersion = mock.getVersion;
ds.metadataRequest = mock.metadataRequest;
return { ds };
};
const mocks = makeMetadataAndVersionsMocks();
const mock = makeMockLokiDatasource(
{ label1: ['value1', 'value2'], label2: ['value3', 'value4'] },
{ '{label1="value1", label2="value2"}': [{ label5: 'value5' }] }
);
mocks.forEach((mock, index) => {
it(`should return label names for Loki v${index}`, async () => {
const { ds } = getTestContext(mock);
it(`should return label names for Loki`, async () => {
const { ds } = getTestContext(mock);
const res = await ds.metricFindQuery('label_names()');
const res = await ds.metricFindQuery('label_names()');
expect(res).toEqual([{ text: 'label1' }, { text: 'label2' }]);
});
expect(res).toEqual([{ text: 'label1' }, { text: 'label2' }]);
});
mocks.forEach((mock, index) => {
it(`should return label values for Loki v${index}`, async () => {
const { ds } = getTestContext(mock);
it(`should return label values for Loki when no matcher`, async () => {
const { ds } = getTestContext(mock);
const res = await ds.metricFindQuery('label_values(label1)');
const res = await ds.metricFindQuery('label_values(label1)');
expect(res).toEqual([{ text: 'value1' }, { text: 'value2' }]);
});
expect(res).toEqual([{ text: 'value1' }, { text: 'value2' }]);
});
mocks.forEach((mock, index) => {
it(`should return empty array when incorrect query for Loki v${index}`, async () => {
const { ds } = getTestContext(mock);
it(`should return label values for Loki with matcher`, async () => {
const { ds } = getTestContext(mock);
const res = await ds.metricFindQuery('incorrect_query');
const res = await ds.metricFindQuery('label_values({label1="value1", label2="value2"},label5)');
expect(res).toEqual([]);
});
expect(res).toEqual([{ text: 'value5' }]);
});
it(`should return empty array when incorrect query for Loki`, async () => {
const { ds } = getTestContext(mock);
const res = await ds.metricFindQuery('incorrect_query');
expect(res).toEqual([]);
});
});
});
@ -629,13 +633,3 @@ function makeAnnotationQueryRequest(options: any): AnnotationQueryRequest<LokiQu
rangeRaw: timeRange,
};
}
function makeMetadataAndVersionsMocks() {
const mocks = [];
for (let i = 0; i <= 1; i++) {
const mock: LokiDatasource = makeMockLokiDatasource({ label1: ['value1', 'value2'], label2: ['value3', 'value4'] });
mock.getVersion = jest.fn().mockReturnValue(`v${i}`);
mocks.push(mock);
}
return mocks;
}

View File

@ -313,32 +313,55 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
const labelNamesRegex = /^label_names\(\)\s*$/;
const labelValuesRegex = /^label_values\((?:(.+),\s*)?([a-zA-Z_][a-zA-Z0-9_]*)\)\s*$/;
const params = this.getTimeRangeParams();
const labelNames = query.match(labelNamesRegex);
if (labelNames) {
return await this.labelNamesQuery(params);
return await this.labelNamesQuery();
}
const labelValues = query.match(labelValuesRegex);
if (labelValues) {
return await this.labelValuesQuery(labelValues[2], params);
// If we have query expr, use /series endpoint
if (labelValues[1]) {
return await this.labelValuesSeriesQuery(labelValues[1], labelValues[2]);
}
return await this.labelValuesQuery(labelValues[2]);
}
return Promise.resolve([]);
}
async labelNamesQuery(params?: Record<string, string | number>) {
async labelNamesQuery() {
const url = `${LOKI_ENDPOINT}/label`;
const params = this.getTimeRangeParams();
const result = await this.metadataRequest(url, params);
return result.map((value: string) => ({ text: value }));
}
async labelValuesQuery(label: string, params?: Record<string, string | number>) {
async labelValuesQuery(label: string) {
const params = this.getTimeRangeParams();
const url = `${LOKI_ENDPOINT}/label/${label}/values`;
const result = await this.metadataRequest(url, params);
return result.map((value: string) => ({ text: value }));
}
async labelValuesSeriesQuery(expr: string, label: string) {
const timeParams = this.getTimeRangeParams();
const params = {
...timeParams,
match: expr,
};
const url = `${LOKI_ENDPOINT}/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);
}
interpolateQueryExpr(value: any, variable: any) {
// if no multi or include all do not regexEscape
if (!variable.multi && !variable.includeAll) {