import { getTemplateSrv, TemplateSrv } from '@grafana/runtime'; import { Monaco, monacoTypes } from '@grafana/ui'; import { type ResourcesAPI } from '../../../resources/ResourcesAPI'; import { LogGroup } from '../../../types'; import { CompletionItemProvider } from '../../monarch/CompletionItemProvider'; import { LinkedToken } from '../../monarch/LinkedToken'; import { TRIGGER_SUGGEST } from '../../monarch/commands'; import { CompletionItem, CompletionItemPriority, StatementPosition, SuggestionKind } from '../../monarch/types'; import { LOGS_COMMANDS, LOGS_FUNCTION_OPERATORS, SORT_DIRECTION_KEYWORDS } from '../language'; import { getStatementPosition } from './statementPosition'; import { getSuggestionKinds } from './suggestionKinds'; import { LogsTokenTypes } from './types'; export type queryContext = { logGroups?: LogGroup[]; region: string; }; export function LogsCompletionItemProviderFunc(resources: ResourcesAPI, templateSrv: TemplateSrv = getTemplateSrv()) { return (queryContext: queryContext) => { return new LogsCompletionItemProvider(resources, templateSrv, queryContext); }; } export class LogsCompletionItemProvider extends CompletionItemProvider { queryContext: queryContext; constructor(resources: ResourcesAPI, templateSrv: TemplateSrv = getTemplateSrv(), queryContext: queryContext) { super(resources, templateSrv); this.getStatementPosition = getStatementPosition; this.getSuggestionKinds = getSuggestionKinds; this.tokenTypes = LogsTokenTypes; this.queryContext = queryContext; } async getSuggestions( monaco: Monaco, currentToken: LinkedToken | null, suggestionKinds: SuggestionKind[], statementPosition: StatementPosition, position: monacoTypes.IPosition ): Promise { const suggestions: CompletionItem[] = []; const invalidRangeToken = currentToken?.isWhiteSpace() || currentToken?.isParenthesis(); const range = invalidRangeToken || !currentToken?.range ? monaco.Range.fromPositions(position) : currentToken?.range; function toCompletionItem(value: string, rest: Partial = {}) { const item: monacoTypes.languages.CompletionItem = { label: value, insertText: value, kind: monaco.languages.CompletionItemKind.Field, range, sortText: CompletionItemPriority.Medium, ...rest, }; return item; } function addSuggestion(value: string, rest: Partial = {}) { suggestions.push(toCompletionItem(value, rest)); } for (const kind of suggestionKinds) { switch (kind) { case SuggestionKind.Command: LOGS_COMMANDS.forEach((command) => { addSuggestion(command, { insertText: `${command} $0`, insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, command: TRIGGER_SUGGEST, kind: monaco.languages.CompletionItemKind.Method, }); }); break; case SuggestionKind.Function: LOGS_FUNCTION_OPERATORS.forEach((f) => { addSuggestion(f, { insertText: `${f}($0)`, insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, command: TRIGGER_SUGGEST, kind: monaco.languages.CompletionItemKind.Function, }); }); if (this.queryContext.logGroups && this.queryContext.logGroups.length > 0) { let fields = await this.fetchFields(this.queryContext.logGroups, this.queryContext.region); fields.push('@log'); fields.forEach((field) => { if (field !== '') { addSuggestion(field, { range, label: field, insertText: field, kind: monaco.languages.CompletionItemKind.Field, sortText: CompletionItemPriority.High, }); } }); } break; case SuggestionKind.SortOrderDirectionKeyword: SORT_DIRECTION_KEYWORDS.forEach((direction) => { addSuggestion(direction, { sortText: CompletionItemPriority.High, kind: monaco.languages.CompletionItemKind.Operator, }); }); break; case SuggestionKind.InKeyword: addSuggestion('in []', { insertText: 'in ["$0"]', insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, kind: monaco.languages.CompletionItemKind.Snippet, sortText: CompletionItemPriority.High, }); break; } } this.templateSrv.getVariables().map((v) => { const variable = `$${v.name}`; addSuggestion(variable, { range, label: variable, insertText: variable, kind: monaco.languages.CompletionItemKind.Variable, sortText: CompletionItemPriority.Low, }); }); return suggestions; } private fetchFields = async (logGroups: LogGroup[], region: string): Promise => { const results = await Promise.all( logGroups.map((logGroup) => this.resources .getLogGroupFields({ logGroupName: logGroup.name, arn: logGroup.arn, region }) .then((fields) => fields.filter((f) => f).map((f) => f.value.name ?? '')) ) ); // Deduplicate fields return [...new Set(results.flat())]; }; }