From c8689fd591d66599ad492a217c49fae93490b6b7 Mon Sep 17 00:00:00 2001 From: Matias Chomicki Date: Wed, 26 Oct 2022 15:44:44 +0200 Subject: [PATCH] Loki Monaco Editor: grab operator documentation from the operations module (#57525) * feat(loki-operations): add method to resolve operation docs * feat(loki-operations): read operation and strip markdown links * feat(loki-monaco-editor): read parser docs from operations * feat(loki-monaco-editor): grab docs for more operations * Chore: update completions test * Chore: add tests for the operations module * Chore: fix typo Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> * Chore: fix typo Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> --- .../completions.test.ts | 25 ++++++---- .../monaco-completion-provider/completions.ts | 24 ++++++--- .../loki/querybuilder/operations.test.ts | 50 +++++++++++++++++++ .../loki/querybuilder/operations.ts | 16 ++++++ .../prometheus/querybuilder/shared/types.ts | 2 +- 5 files changed, 99 insertions(+), 18 deletions(-) create mode 100644 public/app/plugins/datasource/loki/querybuilder/operations.test.ts diff --git a/public/app/plugins/datasource/loki/components/monaco-query-field/monaco-completion-provider/completions.test.ts b/public/app/plugins/datasource/loki/components/monaco-query-field/monaco-completion-provider/completions.test.ts index 8e5a93df983..4fde82901b2 100644 --- a/public/app/plugins/datasource/loki/components/monaco-query-field/monaco-completion-provider/completions.test.ts +++ b/public/app/plugins/datasource/loki/components/monaco-query-field/monaco-completion-provider/completions.test.ts @@ -6,6 +6,10 @@ import { CompletionDataProvider } from './CompletionDataProvider'; import { getCompletions } from './completions'; import { Label, Situation } from './situation'; +jest.mock('../../../querybuilder/operations', () => ({ + explainOperator: () => 'Operator docs', +})); + const history = [ { ts: 12345678, @@ -35,59 +39,59 @@ const otherLabels: Label[] = [ ]; const afterSelectorCompletions = [ { - documentation: 'Log line contains string', + documentation: 'Operator docs', insertText: '|= "$0"', isSnippet: true, label: '|= ""', type: 'LINE_FILTER', }, { - documentation: 'Log line does not contain string', + documentation: 'Operator docs', insertText: '!= "$0"', isSnippet: true, label: '!= ""', type: 'LINE_FILTER', }, { - documentation: 'Log line contains a match to the regular expression', + documentation: 'Operator docs', insertText: '|~ "$0"', isSnippet: true, label: '|~ ""', type: 'LINE_FILTER', }, { - documentation: 'Log line does not contain a match to the regular expression', + documentation: 'Operator docs', insertText: '!~ "$0"', isSnippet: true, label: '!~ ""', type: 'LINE_FILTER', }, { - documentation: 'Parse and extract labels from the log content.', + documentation: 'Operator docs', insertText: '', label: '// Placeholder for the detected parser', type: 'DETECTED_PARSER_PLACEHOLDER', }, { - documentation: 'Parse and extract labels from the log content.', + documentation: 'Operator docs', insertText: '', label: '// Placeholder for logfmt or json', type: 'OPPOSITE_PARSER_PLACEHOLDER', }, { - documentation: 'Parse and extract labels from the log content.', + documentation: 'Operator docs', insertText: '| pattern', label: 'pattern', type: 'PARSER', }, { - documentation: 'Parse and extract labels from the log content.', + documentation: 'Operator docs', insertText: '| regexp', label: 'regexp', type: 'PARSER', }, { - documentation: 'Parse and extract labels from the log content.', + documentation: 'Operator docs', insertText: '| unpack', label: 'unpack', type: 'PARSER', @@ -106,18 +110,21 @@ const afterSelectorCompletions = [ insertText: '| unwrap', label: 'unwrap', type: 'PIPE_OPERATION', + documentation: 'Operator docs', }, { insertText: '| line_format "{{.$0}}"', isSnippet: true, label: 'line_format', type: 'PIPE_OPERATION', + documentation: 'Operator docs', }, { insertText: '| label_format', isSnippet: true, label: 'label_format', type: 'PIPE_OPERATION', + documentation: 'Operator docs', }, ]; diff --git a/public/app/plugins/datasource/loki/components/monaco-query-field/monaco-completion-provider/completions.ts b/public/app/plugins/datasource/loki/components/monaco-query-field/monaco-completion-provider/completions.ts index 291e9bb1f68..77e36d7e95e 100644 --- a/public/app/plugins/datasource/loki/components/monaco-query-field/monaco-completion-provider/completions.ts +++ b/public/app/plugins/datasource/loki/components/monaco-query-field/monaco-completion-provider/completions.ts @@ -1,4 +1,6 @@ import { escapeLabelValueInExactSelector } from '../../../languageUtils'; +import { explainOperator } from '../../../querybuilder/operations'; +import { LokiOperationId } from '../../../querybuilder/types'; import { AGGREGATION_OPERATORS, RANGE_VEC_FUNCTIONS } from '../../../syntax'; import { CompletionDataProvider } from './CompletionDataProvider'; @@ -67,21 +69,21 @@ const DURATION_COMPLETIONS: Completion[] = ['$__interval', '$__range', '1m', '5m const LINE_FILTER_COMPLETIONS = [ { operator: '|=', - documentation: 'Log line contains string', + documentation: explainOperator(LokiOperationId.LineContains), afterPipe: true, }, { operator: '!=', - documentation: 'Log line does not contain string', + documentation: explainOperator(LokiOperationId.LineContainsNot), }, { operator: '|~', - documentation: 'Log line contains a match to the regular expression', + documentation: explainOperator(LokiOperationId.LineMatchesRegex), afterPipe: true, }, { operator: '!~', - documentation: 'Log line does not contain a match to the regular expression', + documentation: explainOperator(LokiOperationId.LineMatchesRegexNot), }, ]; @@ -152,7 +154,6 @@ async function getInGroupingCompletions( } const PARSERS = ['json', 'logfmt', 'pattern', 'regexp', 'unpack']; -const PARSER_DOCUMENTATION = 'Parse and extract labels from the log content.'; async function getAfterSelectorCompletions( labels: Label[], @@ -171,7 +172,9 @@ async function getAfterSelectorCompletions( type: 'PARSER', label: `json${extra}`, insertText: `${prefix}json`, - documentation: hasLevelInExtractedLabels ? 'Use it to get log-levels in the histogram' : PARSER_DOCUMENTATION, + documentation: hasLevelInExtractedLabels + ? 'Use it to get log-levels in the histogram' + : explainOperator(LokiOperationId.Json), }); } @@ -182,7 +185,9 @@ async function getAfterSelectorCompletions( type: 'DURATION', label: `logfmt${extra}`, insertText: `${prefix}logfmt`, - documentation: hasLevelInExtractedLabels ? 'Get detected levels in the histogram' : PARSER_DOCUMENTATION, + documentation: hasLevelInExtractedLabels + ? 'Get detected levels in the histogram' + : explainOperator(LokiOperationId.Logfmt), }); } @@ -192,7 +197,7 @@ async function getAfterSelectorCompletions( type: 'PARSER', label: parser, insertText: `${prefix}${parser}`, - documentation: PARSER_DOCUMENTATION, + documentation: explainOperator(parser), }); }); @@ -208,6 +213,7 @@ async function getAfterSelectorCompletions( type: 'PIPE_OPERATION', label: 'unwrap', insertText: `${prefix}unwrap`, + documentation: explainOperator(LokiOperationId.Unwrap), }); completions.push({ @@ -215,6 +221,7 @@ async function getAfterSelectorCompletions( label: 'line_format', insertText: `${prefix}line_format "{{.$0}}"`, isSnippet: true, + documentation: explainOperator(LokiOperationId.LineFormat), }); completions.push({ @@ -222,6 +229,7 @@ async function getAfterSelectorCompletions( label: 'label_format', insertText: `${prefix}label_format`, isSnippet: true, + documentation: explainOperator(LokiOperationId.LabelFormat), }); return [...getLineFilterCompletions(afterPipe), ...completions]; diff --git a/public/app/plugins/datasource/loki/querybuilder/operations.test.ts b/public/app/plugins/datasource/loki/querybuilder/operations.test.ts new file mode 100644 index 00000000000..ccd1ee1254e --- /dev/null +++ b/public/app/plugins/datasource/loki/querybuilder/operations.test.ts @@ -0,0 +1,50 @@ +import { explainOperator, getOperationDefinitions } from './operations'; +import { LokiOperationId } from './types'; + +const undocumentedOperationsIds: string[] = [ + LokiOperationId.Addition, + LokiOperationId.Subtraction, + LokiOperationId.MultiplyBy, + LokiOperationId.DivideBy, + LokiOperationId.Modulo, + LokiOperationId.Exponent, + LokiOperationId.NestedQuery, + LokiOperationId.EqualTo, + LokiOperationId.NotEqualTo, + LokiOperationId.GreaterThan, + LokiOperationId.LessThan, + LokiOperationId.GreaterOrEqual, + LokiOperationId.LessOrEqual, +]; + +describe('explainOperator', () => { + let operations = []; + let undocumentedOperations = []; + + const definitions = getOperationDefinitions(); + for (const definition of definitions) { + if (!undocumentedOperationsIds.includes(definition.id)) { + operations.push(definition.id); + } else { + undocumentedOperations.push(definition.id); + } + } + + test('Resolves operation definitions', () => { + expect(definitions.length).toBeGreaterThan(0); + }); + + test.each(operations)('Returns docs for the %s operation', (operation) => { + const explain = explainOperator(operation); + + expect(explain).toBeDefined(); + expect(explain).not.toBe(''); + }); + + test.each(undocumentedOperations)('Returns empty docs for the %s operation', (operation) => { + const explain = explainOperator(operation); + + expect(explain).toBeDefined(); + expect(explain).toBe(''); + }); +}); diff --git a/public/app/plugins/datasource/loki/querybuilder/operations.ts b/public/app/plugins/datasource/loki/querybuilder/operations.ts index a37023caf73..f18f99be13d 100644 --- a/public/app/plugins/datasource/loki/querybuilder/operations.ts +++ b/public/app/plugins/datasource/loki/querybuilder/operations.ts @@ -420,3 +420,19 @@ export function getOperationDefinitions(): QueryBuilderOperationDef[] { return list; } + +// Keeping a local copy as an optimization measure. +const definitions = getOperationDefinitions(); + +/** + * Given an operator, return the corresponding explain. + * For usage within the Query Editor. + */ +export function explainOperator(id: LokiOperationId | string): string { + const definition = definitions.find((operation) => operation.id === id); + + const explain = definition?.explainHandler?.({ id: '', params: [''] }) || ''; + + // Strip markdown links + return explain.replace(/\[(.*)\]\(.*\)/g, '$1'); +} diff --git a/public/app/plugins/datasource/prometheus/querybuilder/shared/types.ts b/public/app/plugins/datasource/prometheus/querybuilder/shared/types.ts index 191c5004de3..5f3a3f57b2f 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/shared/types.ts +++ b/public/app/plugins/datasource/prometheus/querybuilder/shared/types.ts @@ -43,7 +43,7 @@ export type QueryBuilderAddOperationHandler = ( modeller: VisualQueryModeller ) => T; -export type QueryBuilderExplainOperationHandler = (op: QueryBuilderOperation, def: QueryBuilderOperationDef) => string; +export type QueryBuilderExplainOperationHandler = (op: QueryBuilderOperation, def?: QueryBuilderOperationDef) => string; export type QueryBuilderOnParamChangedHandler = ( index: number,