mirror of
				https://github.com/grafana/grafana.git
				synced 2025-02-25 18:55:37 -06:00 
			
		
		
		
	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>
This commit is contained in:
		| @@ -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', | ||||
|   }, | ||||
| ]; | ||||
|  | ||||
|   | ||||
| @@ -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]; | ||||
|   | ||||
| @@ -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(''); | ||||
|   }); | ||||
| }); | ||||
| @@ -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: ['<value>'] }) || ''; | ||||
|  | ||||
|   // Strip markdown links | ||||
|   return explain.replace(/\[(.*)\]\(.*\)/g, '$1'); | ||||
| } | ||||
|   | ||||
| @@ -43,7 +43,7 @@ export type QueryBuilderAddOperationHandler<T> = ( | ||||
|   modeller: VisualQueryModeller | ||||
| ) => T; | ||||
|  | ||||
| export type QueryBuilderExplainOperationHandler = (op: QueryBuilderOperation, def: QueryBuilderOperationDef) => string; | ||||
| export type QueryBuilderExplainOperationHandler = (op: QueryBuilderOperation, def?: QueryBuilderOperationDef) => string; | ||||
|  | ||||
| export type QueryBuilderOnParamChangedHandler = ( | ||||
|   index: number, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user