mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Prometheus: Add title to metrics in the metric select with metric help text (#46406)
* Add title to metrics in the select * Add some comments * Fix tests
This commit is contained in:
parent
146745b3bb
commit
b3f8079f4f
@ -7,6 +7,11 @@ export interface SelectableValue<T = any> {
|
||||
value?: T;
|
||||
imgUrl?: string;
|
||||
icon?: string;
|
||||
// Secondary text under the the title of the option.
|
||||
description?: string;
|
||||
// Adds a simple native title attribute to each option.
|
||||
title?: string;
|
||||
// Optional component that will be shown together with other options. Does not get past any props.
|
||||
component?: React.ComponentType<any>;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
@ -61,6 +61,7 @@ export const SelectMenuOptions: FC<SelectMenuOptionProps<any>> = ({
|
||||
)}
|
||||
{...innerProps}
|
||||
aria-label="Select option"
|
||||
title={data.title}
|
||||
>
|
||||
{data.icon && <Icon name={data.icon as IconName} className={styles.optionIcon} />}
|
||||
{data.imgUrl && <img className={styles.optionImage} src={data.imgUrl} alt={data.label || data.value} />}
|
||||
|
@ -12,4 +12,5 @@ export class EmptyLanguageProviderMock {
|
||||
fetchSeries = jest.fn().mockReturnValue([]);
|
||||
fetchSeriesLabels = jest.fn().mockReturnValue([]);
|
||||
fetchLabels = jest.fn();
|
||||
loadMetricsMetadata = jest.fn();
|
||||
}
|
||||
|
@ -63,12 +63,19 @@ export function addHistoryMetadata(item: CompletionItem, history: any[]): Comple
|
||||
function addMetricsMetadata(metric: string, metadata?: PromMetricsMetadata): CompletionItem {
|
||||
const item: CompletionItem = { label: metric };
|
||||
if (metadata && metadata[metric]) {
|
||||
const { type, help } = metadata[metric];
|
||||
item.documentation = `${type.toUpperCase()}: ${help}`;
|
||||
item.documentation = getMetadataString(metric, metadata);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
export function getMetadataString(metric: string, metadata: PromMetricsMetadata): string | undefined {
|
||||
if (!metadata[metric]) {
|
||||
return undefined;
|
||||
}
|
||||
const { type, help } = metadata[metric];
|
||||
return `${type.toUpperCase()}: ${help}`;
|
||||
}
|
||||
|
||||
const PREFIX_DELIMITER_REGEX =
|
||||
/(="|!="|=~"|!~"|\{|\[|\(|\+|-|\/|\*|%|\^|\band\b|\bor\b|\bunless\b|==|>=|!=|<=|>|<|=|~|,)/;
|
||||
|
||||
@ -133,11 +140,15 @@ export default class PromQlLanguageProvider extends LanguageProvider {
|
||||
// TODO #33976: make those requests parallel
|
||||
await this.fetchLabels();
|
||||
this.metrics = (await this.fetchLabelValues('__name__')) || [];
|
||||
this.metricsMetadata = fixSummariesMetadata(await this.request('/api/v1/metadata', {}));
|
||||
await this.loadMetricsMetadata();
|
||||
this.histogramMetrics = processHistogramMetrics(this.metrics).sort();
|
||||
return [];
|
||||
};
|
||||
|
||||
async loadMetricsMetadata() {
|
||||
this.metricsMetadata = fixSummariesMetadata(await this.request('/api/v1/metadata', {}));
|
||||
}
|
||||
|
||||
getLabelKeys(): string[] {
|
||||
return this.labelKeys;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { MetricSelect } from './MetricSelect';
|
||||
import { PromVisualQuery } from '../types';
|
||||
import { LabelFilters } from '../shared/LabelFilters';
|
||||
@ -11,6 +11,7 @@ import { QueryBuilderLabelFilter } from '../shared/types';
|
||||
import { DataSourceApi, PanelData, SelectableValue } from '@grafana/data';
|
||||
import { OperationsEditorRow } from '../shared/OperationsEditorRow';
|
||||
import { PromQueryBuilderHints } from './PromQueryBuilderHints';
|
||||
import { getMetadataString } from '../../language_provider';
|
||||
|
||||
export interface Props {
|
||||
query: PromVisualQuery;
|
||||
@ -26,18 +27,27 @@ export const PromQueryBuilder = React.memo<Props>(({ datasource, query, onChange
|
||||
onChange({ ...query, labels });
|
||||
};
|
||||
|
||||
const withTemplateVariableOptions = async (optionsPromise: Promise<string[]>): Promise<SelectableValue[]> => {
|
||||
const variables = datasource.getVariables();
|
||||
const options = await optionsPromise;
|
||||
return [...variables, ...options].map((value) => ({ label: value, value }));
|
||||
};
|
||||
/**
|
||||
* Map metric metadata to SelectableValue for Select component and also adds defined template variables to the list.
|
||||
*/
|
||||
const withTemplateVariableOptions = useCallback(
|
||||
async (optionsPromise: Promise<Array<{ value: string; description?: string }>>): Promise<SelectableValue[]> => {
|
||||
const variables = datasource.getVariables();
|
||||
const options = await optionsPromise;
|
||||
return [
|
||||
...variables.map((value) => ({ label: value, value })),
|
||||
...options.map((option) => ({ label: option.value, value: option.value, title: option.description })),
|
||||
];
|
||||
},
|
||||
[datasource]
|
||||
);
|
||||
|
||||
const onGetLabelNames = async (forLabel: Partial<QueryBuilderLabelFilter>): Promise<string[]> => {
|
||||
const onGetLabelNames = async (forLabel: Partial<QueryBuilderLabelFilter>): Promise<Array<{ value: string }>> => {
|
||||
// If no metric we need to use a different method
|
||||
if (!query.metric) {
|
||||
// Todo add caching but inside language provider!
|
||||
await datasource.languageProvider.fetchLabels();
|
||||
return datasource.languageProvider.getLabelKeys();
|
||||
return datasource.languageProvider.getLabelKeys().map((k) => ({ value: k }));
|
||||
}
|
||||
|
||||
const labelsToConsider = query.labels.filter((x) => x !== forLabel);
|
||||
@ -46,9 +56,9 @@ export const PromQueryBuilder = React.memo<Props>(({ datasource, query, onChange
|
||||
const labelsIndex = await datasource.languageProvider.fetchSeriesLabels(expr);
|
||||
|
||||
// filter out already used labels
|
||||
return Object.keys(labelsIndex).filter(
|
||||
(labelName) => !labelsToConsider.find((filter) => filter.label === labelName)
|
||||
);
|
||||
return Object.keys(labelsIndex)
|
||||
.filter((labelName) => !labelsToConsider.find((filter) => filter.label === labelName))
|
||||
.map((k) => ({ value: k }));
|
||||
};
|
||||
|
||||
const onGetLabelValues = async (forLabel: Partial<QueryBuilderLabelFilter>) => {
|
||||
@ -58,7 +68,7 @@ export const PromQueryBuilder = React.memo<Props>(({ datasource, query, onChange
|
||||
|
||||
// If no metric we need to use a different method
|
||||
if (!query.metric) {
|
||||
return await datasource.languageProvider.getLabelValues(forLabel.label);
|
||||
return (await datasource.languageProvider.getLabelValues(forLabel.label)).map((v) => ({ value: v }));
|
||||
}
|
||||
|
||||
const labelsToConsider = query.labels.filter((x) => x !== forLabel);
|
||||
@ -66,26 +76,17 @@ export const PromQueryBuilder = React.memo<Props>(({ datasource, query, onChange
|
||||
const expr = promQueryModeller.renderLabels(labelsToConsider);
|
||||
const result = await datasource.languageProvider.fetchSeriesLabels(expr);
|
||||
const forLabelInterpolated = datasource.interpolateString(forLabel.label);
|
||||
return result[forLabelInterpolated] ?? [];
|
||||
return result[forLabelInterpolated].map((v) => ({ value: v })) ?? [];
|
||||
};
|
||||
|
||||
const onGetMetrics = async () => {
|
||||
if (query.labels.length > 0) {
|
||||
const expr = promQueryModeller.renderLabels(query.labels);
|
||||
return (await datasource.languageProvider.getSeries(expr, true))['__name__'] ?? [];
|
||||
} else {
|
||||
return (await datasource.languageProvider.getLabelValues('__name__')) ?? [];
|
||||
}
|
||||
};
|
||||
const onGetMetrics = useCallback(() => {
|
||||
return withTemplateVariableOptions(getMetrics(datasource, query));
|
||||
}, [datasource, query, withTemplateVariableOptions]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EditorRow>
|
||||
<MetricSelect
|
||||
query={query}
|
||||
onChange={onChange}
|
||||
onGetMetrics={() => withTemplateVariableOptions(onGetMetrics())}
|
||||
/>
|
||||
<MetricSelect query={query} onChange={onChange} onGetMetrics={onGetMetrics} />
|
||||
<LabelFilters
|
||||
labelsFilters={query.labels}
|
||||
onChange={onChangeLabels}
|
||||
@ -114,4 +115,34 @@ export const PromQueryBuilder = React.memo<Props>(({ datasource, query, onChange
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns list of metrics, either all or filtered by query param. It also adds description string to each metric if it
|
||||
* exists.
|
||||
* @param datasource
|
||||
* @param query
|
||||
*/
|
||||
async function getMetrics(
|
||||
datasource: PrometheusDatasource,
|
||||
query: PromVisualQuery
|
||||
): Promise<Array<{ value: string; description?: string }>> {
|
||||
// Makes sure we loaded the metadata for metrics. Usually this is done in the start() method of the provider but we
|
||||
// don't use it with the visual builder and there is no need to run all the start() setup anyway.
|
||||
if (!datasource.languageProvider.metricsMetadata) {
|
||||
await datasource.languageProvider.loadMetricsMetadata();
|
||||
}
|
||||
|
||||
let metrics;
|
||||
if (query.labels.length > 0) {
|
||||
const expr = promQueryModeller.renderLabels(query.labels);
|
||||
metrics = (await datasource.languageProvider.getSeries(expr, true))['__name__'] ?? [];
|
||||
} else {
|
||||
metrics = (await datasource.languageProvider.getLabelValues('__name__')) ?? [];
|
||||
}
|
||||
|
||||
return metrics.map((m) => ({
|
||||
value: m,
|
||||
description: getMetadataString(m, datasource.languageProvider.metricsMetadata!),
|
||||
}));
|
||||
}
|
||||
|
||||
PromQueryBuilder.displayName = 'PromQueryBuilder';
|
||||
|
Loading…
Reference in New Issue
Block a user