Prometheus: Add resource for suggestions that include scopes/adhoc filters (#94001)
Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com> Co-authored-by: Bogdan Matei <bogdan.matei@grafana.com>
This commit is contained in:
co-authored by
Dominik Prokop
Bogdan Matei
parent
a20ebbc8f8
commit
2a73b89374
@@ -74,7 +74,13 @@ import {
|
||||
import { PrometheusVariableSupport } from './variables';
|
||||
|
||||
const ANNOTATION_QUERY_STEP_DEFAULT = '60s';
|
||||
const GET_AND_POST_METADATA_ENDPOINTS = ['api/v1/query', 'api/v1/query_range', 'api/v1/series', 'api/v1/labels'];
|
||||
const GET_AND_POST_METADATA_ENDPOINTS = [
|
||||
'api/v1/query',
|
||||
'api/v1/query_range',
|
||||
'api/v1/series',
|
||||
'api/v1/labels',
|
||||
'suggestions',
|
||||
];
|
||||
|
||||
export const InstantQueryRefIdIndex = '-Instant';
|
||||
|
||||
@@ -263,7 +269,9 @@ export class PrometheusDatasource
|
||||
.join('&');
|
||||
}
|
||||
} else {
|
||||
options.headers!['Content-Type'] = 'application/x-www-form-urlencoded';
|
||||
if (!options.headers!['Content-Type']) {
|
||||
options.headers!['Content-Type'] = 'application/x-www-form-urlencoded';
|
||||
}
|
||||
options.data = data;
|
||||
}
|
||||
|
||||
@@ -599,6 +607,20 @@ export class PrometheusDatasource
|
||||
// it is used in metric_find_query.ts
|
||||
// and in Tempo here grafana/public/app/plugins/datasource/tempo/QueryEditor/ServiceGraphSection.tsx
|
||||
async getTagKeys(options: DataSourceGetTagKeysOptions<PromQuery>): Promise<MetricFindValue[]> {
|
||||
if (config.featureToggles.promQLScope && !!options) {
|
||||
const suggestions = await this.languageProvider.fetchSuggestions(
|
||||
options.timeRange,
|
||||
options.queries,
|
||||
options.scopes,
|
||||
options.filters
|
||||
);
|
||||
|
||||
// filter out already used labels and empty labels
|
||||
return suggestions
|
||||
.filter((labelName) => !!labelName && !options.filters.find((filter) => filter.key === labelName))
|
||||
.map((k) => ({ value: k, text: k }));
|
||||
}
|
||||
|
||||
if (!options || options.filters.length === 0) {
|
||||
await this.languageProvider.fetchLabels(options.timeRange, options.queries);
|
||||
return this.languageProvider.getLabelKeys().map((k) => ({ value: k, text: k }));
|
||||
@@ -621,6 +643,21 @@ export class PrometheusDatasource
|
||||
|
||||
// By implementing getTagKeys and getTagValues we add ad-hoc filters functionality
|
||||
async getTagValues(options: DataSourceGetTagValuesOptions<PromQuery>) {
|
||||
const requestId = `[${this.uid}][${options.key}]`;
|
||||
if (config.featureToggles.promQLScope) {
|
||||
return (
|
||||
await this.languageProvider.fetchSuggestions(
|
||||
options.timeRange,
|
||||
options.queries,
|
||||
options.scopes,
|
||||
options.filters,
|
||||
options.key,
|
||||
undefined,
|
||||
requestId
|
||||
)
|
||||
).map((v) => ({ value: v, text: v }));
|
||||
}
|
||||
|
||||
const labelFilters: QueryBuilderLabelFilter[] = options.filters.map((f) => ({
|
||||
label: f.key,
|
||||
value: f.value,
|
||||
@@ -630,7 +667,6 @@ export class PrometheusDatasource
|
||||
const expr = promQueryModeller.renderLabels(labelFilters);
|
||||
|
||||
if (this.hasLabelsMatchAPISupport()) {
|
||||
const requestId = `[${this.uid}][${options.key}]`;
|
||||
return (
|
||||
await this.languageProvider.fetchSeriesValuesWithMatch(options.key, expr, requestId, options.timeRange)
|
||||
).map((v) => ({
|
||||
|
||||
@@ -6,8 +6,12 @@ import {
|
||||
AbstractLabelMatcher,
|
||||
AbstractLabelOperator,
|
||||
AbstractQuery,
|
||||
AdHocVariableFilter,
|
||||
getDefaultTimeRange,
|
||||
LanguageProvider,
|
||||
Scope,
|
||||
scopeFilterOperatorMap,
|
||||
ScopeSpecFilter,
|
||||
TimeRange,
|
||||
} from '@grafana/data';
|
||||
import { BackendSrvRequest } from '@grafana/runtime';
|
||||
@@ -407,6 +411,64 @@ export default class PromQlLanguageProvider extends LanguageProvider {
|
||||
const values = await Promise.all(DEFAULT_KEYS.map((key) => this.fetchLabelValues(key)));
|
||||
return DEFAULT_KEYS.reduce((acc, key, i) => ({ ...acc, [key]: values[i] }), {});
|
||||
});
|
||||
|
||||
/**
|
||||
* Fetch labels or values for a label based on the queries, scopes, filters and time range
|
||||
* @param timeRange
|
||||
* @param queries
|
||||
* @param scopes
|
||||
* @param adhocFilters
|
||||
* @param labelName
|
||||
* @param limit
|
||||
* @param requestId
|
||||
*/
|
||||
fetchSuggestions = async (
|
||||
timeRange?: TimeRange,
|
||||
queries?: PromQuery[],
|
||||
scopes?: Scope[],
|
||||
adhocFilters?: AdHocVariableFilter[],
|
||||
labelName?: string,
|
||||
limit?: number,
|
||||
requestId?: string
|
||||
): Promise<string[]> => {
|
||||
if (timeRange) {
|
||||
this.timeRange = timeRange;
|
||||
}
|
||||
|
||||
const url = '/suggestions';
|
||||
const timeParams = this.datasource.getAdjustedInterval(this.timeRange);
|
||||
const value = await this.request(
|
||||
url,
|
||||
[],
|
||||
{
|
||||
labelName,
|
||||
queries: queries?.map((q) => q.expr),
|
||||
scopes: scopes?.reduce<ScopeSpecFilter[]>((acc, scope) => {
|
||||
acc.push(...scope.spec.filters);
|
||||
|
||||
return acc;
|
||||
}, []),
|
||||
adhocFilters: adhocFilters?.map((filter) => ({
|
||||
key: filter.key,
|
||||
operator: scopeFilterOperatorMap[filter.operator],
|
||||
value: filter.value,
|
||||
values: filter.values,
|
||||
})),
|
||||
limit,
|
||||
...timeParams,
|
||||
},
|
||||
{
|
||||
...(requestId && { requestId }),
|
||||
headers: {
|
||||
...this.getDefaultCacheHeaders()?.headers,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'POST',
|
||||
}
|
||||
);
|
||||
|
||||
return value ?? [];
|
||||
};
|
||||
}
|
||||
|
||||
function getNameLabelValue(promQuery: string, tokens: Array<string | Prism.Token>): string {
|
||||
|
||||
Reference in New Issue
Block a user