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:
Kyle Brandt
2024-10-01 07:17:59 -04:00
committed by GitHub
co-authored by Dominik Prokop Bogdan Matei
parent a20ebbc8f8
commit 2a73b89374
7 changed files with 277 additions and 6 deletions
+39 -3
View File
@@ -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 {