prometheus: monaco: improved suggestion-displaying (icons, help-text) (#38977)

* prometheus: monaco: better icons in popup

* prometheus: monaco: add help-text to functions

* prometheus: monaco: add help-text to metrics

* better help-text
This commit is contained in:
Gábor Farkas 2021-09-13 12:36:18 +02:00 committed by GitHub
parent 88ad9aad42
commit e055ba3525
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 58 additions and 9 deletions

View File

@ -56,7 +56,14 @@ const MonacoQueryField = (props: Props) => {
const getAllMetricNames = () => {
const { metricsMetadata } = lpRef.current;
const result = metricsMetadata == null ? [] : Object.keys(metricsMetadata);
const result =
metricsMetadata == null
? []
: Object.entries(metricsMetadata).map(([k, v]) => ({
name: k,
help: v[0].help,
type: v[0].type,
}));
return Promise.resolve(result);
};

View File

@ -3,38 +3,56 @@ import { NeverCaseError } from './util';
// FIXME: we should not load this from the "outside", but we cannot do that while we have the "old" query-field too
import { FUNCTIONS } from '../../../promql';
export type CompletionType = 'HISTORY' | 'FUNCTION' | 'METRIC_NAME' | 'DURATION' | 'LABEL_NAME' | 'LABEL_VALUE';
type Completion = {
type: CompletionType;
label: string;
insertText: string;
detail?: string;
documentation?: string;
triggerOnInsert?: boolean;
};
type Metric = {
name: string;
help: string;
type: string;
};
export type DataProvider = {
getHistory: () => Promise<string[]>;
getAllMetricNames: () => Promise<string[]>;
getAllMetricNames: () => Promise<Metric[]>;
getSeries: (selector: string) => Promise<Record<string, string[]>>;
};
// we order items like: history, functions, metrics
async function getAllMetricNamesCompletions(dataProvider: DataProvider): Promise<Completion[]> {
const names = await dataProvider.getAllMetricNames();
return names.map((text) => ({
label: text,
insertText: text,
const metrics = await dataProvider.getAllMetricNames();
return metrics.map((metric) => ({
type: 'METRIC_NAME',
label: metric.name,
insertText: metric.name,
detail: `${metric.name} : ${metric.type}`,
documentation: metric.help,
}));
}
function getAllFunctionsCompletions(): Completion[] {
return FUNCTIONS.map((f) => ({
type: 'FUNCTION',
label: f.label,
insertText: f.insertText ?? '', // i don't know what to do when this is nullish. it should not be.
detail: f.detail,
documentation: f.documentation,
}));
}
function getAllDurationsCompletions(): Completion[] {
// FIXME: get a better list
return ['5m', '1m', '30s', '15s'].map((text) => ({
type: 'DURATION',
label: text,
insertText: text,
}));
@ -46,6 +64,7 @@ async function getAllHistoryCompletions(dataProvider: DataProvider): Promise<Com
const allHistory = await dataProvider.getHistory();
// FIXME: find a better history-limit
return allHistory.slice(0, 10).map((expr) => ({
type: 'HISTORY',
label: expr,
insertText: expr,
}));
@ -77,6 +96,7 @@ async function getLabelNamesForCompletions(
const usedLabelNames = new Set(otherLabels.map((l) => l.name)); // names used in the query
const labelNames = possibleLabelNames.filter((l) => !usedLabelNames.has(l));
return labelNames.map((text) => ({
type: 'LABEL_NAME',
label: text,
insertText: `${text}${suffix}`,
triggerOnInsert,
@ -108,6 +128,7 @@ async function getLabelValuesForMetricCompletions(
const data = await dataProvider.getSeries(selector);
const values = data[labelName] ?? [];
return values.map((text) => ({
type: 'LABEL_VALUE',
label: text,
insertText: `"${text}"`, // FIXME: escaping strange characters?
}));

View File

@ -1,8 +1,27 @@
import type { Monaco, monacoTypes } from '@grafana/ui';
import { getIntent } from './intent';
import { getCompletions, DataProvider } from './completions';
import { getCompletions, DataProvider, CompletionType } from './completions';
import { NeverCaseError } from './util';
function getMonacoCompletionItemKind(type: CompletionType, monaco: Monaco): monacoTypes.languages.CompletionItemKind {
switch (type) {
case 'DURATION':
return monaco.languages.CompletionItemKind.Unit;
case 'FUNCTION':
return monaco.languages.CompletionItemKind.Variable;
case 'HISTORY':
return monaco.languages.CompletionItemKind.Snippet;
case 'LABEL_NAME':
return monaco.languages.CompletionItemKind.Enum;
case 'LABEL_VALUE':
return monaco.languages.CompletionItemKind.EnumMember;
case 'METRIC_NAME':
return monaco.languages.CompletionItemKind.Constructor;
default:
throw new NeverCaseError(type);
}
}
export function getCompletionProvider(
monaco: Monaco,
dataProvider: DataProvider
@ -35,10 +54,12 @@ export function getCompletionProvider(
// to stop it, we use a number-as-string sortkey,
// so that monaco keeps the order we use
const maxIndexDigits = items.length.toString().length;
const suggestions = items.map((item, index) => ({
kind: monaco.languages.CompletionItemKind.Text,
const suggestions: monacoTypes.languages.CompletionItem[] = items.map((item, index) => ({
kind: getMonacoCompletionItemKind(item.type, monaco),
label: item.label,
insertText: item.insertText,
detail: item.detail,
documentation: item.documentation,
sortText: index.toString().padStart(maxIndexDigits, '0'), // to force the order we have
range,
command: item.triggerOnInsert