Files
grafana/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilder.tsx
Brendan O'Handley e51187a474 Prometheus datasource: query builder freezes when metrics metadata is undefined (#51929)
* add error handling for prom query builder returning undefined metrics metadata

* remove reference to escalation
2022-07-08 11:59:48 -04:00

164 lines
6.3 KiB
TypeScript

import React, { useCallback } from 'react';
import { DataSourceApi, PanelData, SelectableValue } from '@grafana/data';
import { EditorRow } from '@grafana/experimental';
import { PrometheusDatasource } from '../../datasource';
import { getMetadataString } from '../../language_provider';
import { promQueryModeller } from '../PromQueryModeller';
import { buildVisualQueryFromString } from '../parsing';
import { LabelFilters } from '../shared/LabelFilters';
import { OperationList } from '../shared/OperationList';
import { OperationsEditorRow } from '../shared/OperationsEditorRow';
import { QueryBuilderHints } from '../shared/QueryBuilderHints';
import { QueryBuilderLabelFilter } from '../shared/types';
import { PromVisualQuery } from '../types';
import { MetricSelect } from './MetricSelect';
import { NestedQueryList } from './NestedQueryList';
export interface Props {
query: PromVisualQuery;
datasource: PrometheusDatasource;
onChange: (update: PromVisualQuery) => void;
onRunQuery: () => void;
data?: PanelData;
}
export const PromQueryBuilder = React.memo<Props>(({ datasource, query, onChange, onRunQuery, data }) => {
const onChangeLabels = (labels: QueryBuilderLabelFilter[]) => {
onChange({ ...query, labels });
};
/**
* 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<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().map((k) => ({ value: k }));
}
const labelsToConsider = query.labels.filter((x) => x !== forLabel);
labelsToConsider.push({ label: '__name__', op: '=', value: query.metric });
const expr = promQueryModeller.renderLabels(labelsToConsider);
const labelsIndex = await datasource.languageProvider.fetchSeriesLabels(expr);
// filter out already used labels
return Object.keys(labelsIndex)
.filter((labelName) => !labelsToConsider.find((filter) => filter.label === labelName))
.map((k) => ({ value: k }));
};
const onGetLabelValues = async (forLabel: Partial<QueryBuilderLabelFilter>) => {
if (!forLabel.label) {
return [];
}
// If no metric we need to use a different method
if (!query.metric) {
return (await datasource.languageProvider.getLabelValues(forLabel.label)).map((v) => ({ value: v }));
}
const labelsToConsider = query.labels.filter((x) => x !== forLabel);
labelsToConsider.push({ label: '__name__', op: '=', value: query.metric });
const expr = promQueryModeller.renderLabels(labelsToConsider);
const result = await datasource.languageProvider.fetchSeriesLabels(expr);
const forLabelInterpolated = datasource.interpolateString(forLabel.label);
return result[forLabelInterpolated].map((v) => ({ value: v })) ?? [];
};
const onGetMetrics = useCallback(() => {
return withTemplateVariableOptions(getMetrics(datasource, query));
}, [datasource, query, withTemplateVariableOptions]);
return (
<>
<EditorRow>
<MetricSelect query={query} onChange={onChange} onGetMetrics={onGetMetrics} />
<LabelFilters
labelsFilters={query.labels}
onChange={onChangeLabels}
onGetLabelNames={(forLabel: Partial<QueryBuilderLabelFilter>) =>
withTemplateVariableOptions(onGetLabelNames(forLabel))
}
onGetLabelValues={(forLabel: Partial<QueryBuilderLabelFilter>) =>
withTemplateVariableOptions(onGetLabelValues(forLabel))
}
/>
</EditorRow>
<OperationsEditorRow>
<OperationList<PromVisualQuery>
queryModeller={promQueryModeller}
datasource={datasource as DataSourceApi}
query={query}
onChange={onChange}
onRunQuery={onRunQuery}
/>
<QueryBuilderHints<PromVisualQuery>
datasource={datasource}
query={query}
onChange={onChange}
data={data}
queryModeller={promQueryModeller}
buildVisualQueryFromString={buildVisualQueryFromString}
/>
</OperationsEditorRow>
{query.binaryQueries && query.binaryQueries.length > 0 && (
<NestedQueryList query={query} datasource={datasource} onChange={onChange} onRunQuery={onRunQuery} />
)}
</>
);
});
/**
* 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();
}
// Error handling for when metrics metadata returns as undefined
if (!datasource.languageProvider.metricsMetadata) {
datasource.languageProvider.metricsMetadata = {};
}
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';