mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
5ac2a7cca6
commit
97a59a4855
@ -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
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user