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
3 changed files with 58 additions and 9 deletions

View File

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

View File

@ -1,8 +1,27 @@
import type { Monaco, monacoTypes } from '@grafana/ui'; import type { Monaco, monacoTypes } from '@grafana/ui';
import { getIntent } from './intent'; 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( export function getCompletionProvider(
monaco: Monaco, monaco: Monaco,
dataProvider: DataProvider dataProvider: DataProvider
@ -35,10 +54,12 @@ export function getCompletionProvider(
// to stop it, we use a number-as-string sortkey, // to stop it, we use a number-as-string sortkey,
// so that monaco keeps the order we use // so that monaco keeps the order we use
const maxIndexDigits = items.length.toString().length; const maxIndexDigits = items.length.toString().length;
const suggestions = items.map((item, index) => ({ const suggestions: monacoTypes.languages.CompletionItem[] = items.map((item, index) => ({
kind: monaco.languages.CompletionItemKind.Text, kind: getMonacoCompletionItemKind(item.type, monaco),
label: item.label, label: item.label,
insertText: item.insertText, insertText: item.insertText,
detail: item.detail,
documentation: item.documentation,
sortText: index.toString().padStart(maxIndexDigits, '0'), // to force the order we have sortText: index.toString().padStart(maxIndexDigits, '0'), // to force the order we have
range, range,
command: item.triggerOnInsert command: item.triggerOnInsert