From dd9ebf32302d965ca6cf640ef87b3992bed79b1d Mon Sep 17 00:00:00 2001 From: dej611 Date: Fri, 24 Nov 2023 18:03:40 +0100 Subject: [PATCH] :sparkles: Add hover on policy name with details --- .../esql/lib/ast/autocomplete/autocomplete.ts | 53 +++++--------- .../src/esql/lib/ast/autocomplete/types.ts | 18 ----- .../src/esql/lib/ast/hover/index.ts | 62 +++++++++++++---- .../esql/lib/ast/shared/resources_helpers.ts | 69 +++++++++++++++++++ .../src/esql/lib/ast/shared/types.ts | 25 +++++++ .../src/esql/lib/monaco/esql_ast_provider.ts | 2 +- packages/kbn-monaco/src/types.ts | 2 +- .../src/text_based_languages_editor.tsx | 6 +- 8 files changed, 165 insertions(+), 72 deletions(-) create mode 100644 packages/kbn-monaco/src/esql/lib/ast/shared/resources_helpers.ts create mode 100644 packages/kbn-monaco/src/esql/lib/ast/shared/types.ts diff --git a/packages/kbn-monaco/src/esql/lib/ast/autocomplete/autocomplete.ts b/packages/kbn-monaco/src/esql/lib/ast/autocomplete/autocomplete.ts index ca39bd81bb4ea..28ad85d239a84 100644 --- a/packages/kbn-monaco/src/esql/lib/ast/autocomplete/autocomplete.ts +++ b/packages/kbn-monaco/src/esql/lib/ast/autocomplete/autocomplete.ts @@ -8,7 +8,7 @@ import uniqBy from 'lodash/uniqBy'; import type { monaco } from '../../../../monaco_imports'; -import type { AutocompleteCommandDefinition, ESQLCallbacks } from './types'; +import type { AutocompleteCommandDefinition } from './types'; import { nonNullable } from '../ast_helpers'; import { getColumnHit, @@ -61,6 +61,12 @@ import { } from './factories'; import { EDITOR_MARKER } from '../shared/constants'; import { getAstContext, removeMarkerArgFromArgsList } from '../shared/context'; +import { + getFieldsByTypeHelper, + getPolicyHelper, + getSourcesHelper, +} from '../shared/resources_helpers'; +import { ESQLCallbacks } from '../shared/types'; type GetSourceFn = () => Promise; type GetFieldsByTypeFn = ( @@ -196,58 +202,31 @@ export async function suggest( } function getFieldsByTypeRetriever(resourceRetriever?: ESQLCallbacks) { - const cacheFields = new Map(); - const getFields = async () => { - if (!cacheFields.size) { - const fieldsOfType = await resourceRetriever?.getFieldsFor?.(); - for (const field of fieldsOfType || []) { - cacheFields.set(field.name, field); - } - } - }; + const helpers = getFieldsByTypeHelper(resourceRetriever); return { getFieldsByType: async (expectedType: string | string[] = 'any', ignored: string[] = []) => { - const types = Array.isArray(expectedType) ? expectedType : [expectedType]; - await getFields(); - return buildFieldsDefinitions( - Array.from(cacheFields.values()) - ?.filter(({ name, type }) => { - const ts = Array.isArray(type) ? type : [type]; - return ( - !ignored.includes(name) && ts.some((t) => types[0] === 'any' || types.includes(t)) - ); - }) - .map(({ name }) => name) || [] - ); - }, - getFieldsMap: async () => { - await getFields(); - const cacheCopy = new Map(); - cacheFields.forEach((value, key) => cacheCopy.set(key, value)); - return cacheCopy; + const fields = await helpers.getFieldsByType(expectedType, ignored); + return buildFieldsDefinitions(fields); }, + getFieldsMap: helpers.getFieldsMap, }; } function getPolicyRetriever(resourceRetriever?: ESQLCallbacks) { - const getPolicies = async () => { - return (await resourceRetriever?.getPolicies?.()) || []; - }; + const helpers = getPolicyHelper(resourceRetriever); return { getPolicies: async () => { - const policies = await getPolicies(); + const policies = await helpers.getPolicies(); return buildPoliciesDefinitions(policies); }, - getPolicyMetadata: async (policyName: string) => { - const policies = await getPolicies(); - return policies.find(({ name }) => name === policyName); - }, + getPolicyMetadata: helpers.getPolicyMetadata, }; } function getSourcesRetriever(resourceRetriever?: ESQLCallbacks) { + const helper = getSourcesHelper(resourceRetriever); return async () => { - return buildSourcesDefinitions((await resourceRetriever?.getSources?.()) || []); + return buildSourcesDefinitions((await helper()) || []); }; } diff --git a/packages/kbn-monaco/src/esql/lib/ast/autocomplete/types.ts b/packages/kbn-monaco/src/esql/lib/ast/autocomplete/types.ts index aa63d61e58e92..ad6428fcbc771 100644 --- a/packages/kbn-monaco/src/esql/lib/ast/autocomplete/types.ts +++ b/packages/kbn-monaco/src/esql/lib/ast/autocomplete/types.ts @@ -8,24 +8,6 @@ import { monaco } from '../../../../..'; -/** @public **/ -export interface ESQLCallbacks { - getSources?: CallbackFn; - getFieldsFor?: CallbackFn< - { sourcesOnly?: boolean } | { customQuery?: string }, - { name: string; type: string } - >; - getPolicies?: CallbackFn< - {}, - { name: string; sourceIndices: string[]; matchField: string; enrichFields: string[] } - >; - getPolicyFields?: CallbackFn; - getPolicyMatchingField?: CallbackFn; -} - -/** @internal **/ -type CallbackFn = (ctx?: Options) => Result[] | Promise; - /** @internal **/ export interface UserDefinedVariables { userDefined: string[]; diff --git a/packages/kbn-monaco/src/esql/lib/ast/hover/index.ts b/packages/kbn-monaco/src/esql/lib/ast/hover/index.ts index cd91be8c66a3c..b118a7dec5489 100644 --- a/packages/kbn-monaco/src/esql/lib/ast/hover/index.ts +++ b/packages/kbn-monaco/src/esql/lib/ast/hover/index.ts @@ -6,38 +6,76 @@ * Side Public License, v 1. */ +import { i18n } from '@kbn/i18n'; import type { monaco } from '../../../../monaco_imports'; import { getFunctionSignatures } from '../definitions/helpers'; import { getAstContext } from '../shared/context'; -import { monacoPositionToOffset, getFunctionDefinition } from '../shared/helpers'; +import { monacoPositionToOffset, getFunctionDefinition, isSourceItem } from '../shared/helpers'; +import { getPolicyHelper } from '../shared/resources_helpers'; +import { ESQLCallbacks } from '../shared/types'; import type { AstProviderFn } from '../types'; export async function getHoverItem( model: monaco.editor.ITextModel, position: monaco.Position, token: monaco.CancellationToken, - astProvider: AstProviderFn + astProvider: AstProviderFn, + resourceRetriever?: ESQLCallbacks ) { const innerText = model.getValue(); const offset = monacoPositionToOffset(innerText, position); const { ast } = await astProvider(innerText); const astContext = getAstContext(innerText, ast, offset); + const { getPolicyMetadata } = getPolicyHelper(resourceRetriever); - if (astContext.type !== 'function') { + if (['newCommand', 'list'].includes(astContext.type)) { return { contents: [] }; } - const fnDefinition = getFunctionDefinition(astContext.node.name); + if (astContext.type === 'function') { + const fnDefinition = getFunctionDefinition(astContext.node.name); - if (!fnDefinition) { - return { contents: [] }; + if (fnDefinition) { + return { + contents: [ + { value: getFunctionSignatures(fnDefinition)[0].declaration }, + { value: fnDefinition.description }, + ], + }; + } + } + + if (astContext.type === 'expression') { + if ( + astContext.node && + isSourceItem(astContext.node) && + astContext.node.sourceType === 'policy' + ) { + const policyMetadata = await getPolicyMetadata(astContext.node.name); + if (policyMetadata) { + return { + contents: [ + { + value: `${i18n.translate('monaco.esql.hover.policyIndexes', { + defaultMessage: '**Indexes**', + })}: ${policyMetadata.sourceIndices}`, + }, + { + value: `${i18n.translate('monaco.esql.hover.policyMatchingField', { + defaultMessage: '**Matching field**', + })}: ${policyMetadata.matchField}`, + }, + { + value: `${i18n.translate('monaco.esql.hover.policyEnrichedFields', { + defaultMessage: '**Fields**', + })}: ${policyMetadata.enrichFields}`, + }, + ], + }; + } + } } - return { - contents: [ - { value: getFunctionSignatures(fnDefinition)[0].declaration }, - { value: fnDefinition.description }, - ], - }; + return { contents: [] }; } diff --git a/packages/kbn-monaco/src/esql/lib/ast/shared/resources_helpers.ts b/packages/kbn-monaco/src/esql/lib/ast/shared/resources_helpers.ts new file mode 100644 index 0000000000000..f0d59703bb4c3 --- /dev/null +++ b/packages/kbn-monaco/src/esql/lib/ast/shared/resources_helpers.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { ESQLCallbacks } from './types'; +import type { ESQLRealField } from '../validation/types'; + +// These are method useful for any non-validation use cases that can be re-used. +// Validation has its own logic so DO NOT USE THESE there. + +export function getFieldsByTypeHelper(resourceRetriever?: ESQLCallbacks) { + const cacheFields = new Map(); + const getFields = async () => { + if (!cacheFields.size) { + const fieldsOfType = await resourceRetriever?.getFieldsFor?.(); + for (const field of fieldsOfType || []) { + cacheFields.set(field.name, field); + } + } + }; + return { + getFieldsByType: async (expectedType: string | string[] = 'any', ignored: string[] = []) => { + const types = Array.isArray(expectedType) ? expectedType : [expectedType]; + await getFields(); + return ( + Array.from(cacheFields.values()) + ?.filter(({ name, type }) => { + const ts = Array.isArray(type) ? type : [type]; + return ( + !ignored.includes(name) && ts.some((t) => types[0] === 'any' || types.includes(t)) + ); + }) + .map(({ name }) => name) || [] + ); + }, + getFieldsMap: async () => { + await getFields(); + const cacheCopy = new Map(); + cacheFields.forEach((value, key) => cacheCopy.set(key, value)); + return cacheCopy; + }, + }; +} + +export function getPolicyHelper(resourceRetriever?: ESQLCallbacks) { + const getPolicies = async () => { + return (await resourceRetriever?.getPolicies?.()) || []; + }; + return { + getPolicies: async () => { + const policies = await getPolicies(); + return policies; + }, + getPolicyMetadata: async (policyName: string) => { + const policies = await getPolicies(); + return policies.find(({ name }) => name === policyName); + }, + }; +} + +export function getSourcesHelper(resourceRetriever?: ESQLCallbacks) { + return async () => { + return (await resourceRetriever?.getSources?.()) || []; + }; +} diff --git a/packages/kbn-monaco/src/esql/lib/ast/shared/types.ts b/packages/kbn-monaco/src/esql/lib/ast/shared/types.ts new file mode 100644 index 0000000000000..a72b35a7dffc6 --- /dev/null +++ b/packages/kbn-monaco/src/esql/lib/ast/shared/types.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** @internal **/ +type CallbackFn = (ctx?: Options) => Result[] | Promise; + +/** @public **/ +export interface ESQLCallbacks { + getSources?: CallbackFn; + getFieldsFor?: CallbackFn< + { sourcesOnly?: boolean } | { customQuery?: string }, + { name: string; type: string } + >; + getPolicies?: CallbackFn< + {}, + { name: string; sourceIndices: string[]; matchField: string; enrichFields: string[] } + >; + getPolicyFields?: CallbackFn; + getPolicyMatchingField?: CallbackFn; +} diff --git a/packages/kbn-monaco/src/esql/lib/monaco/esql_ast_provider.ts b/packages/kbn-monaco/src/esql/lib/monaco/esql_ast_provider.ts index 7ac5857fc8a33..6b7cddbdbcada 100644 --- a/packages/kbn-monaco/src/esql/lib/monaco/esql_ast_provider.ts +++ b/packages/kbn-monaco/src/esql/lib/monaco/esql_ast_provider.ts @@ -50,7 +50,7 @@ export class ESQLAstAdapter { token: monaco.CancellationToken ) { const getAstFn = await this.getAstWorker(model); - return getHoverItem(model, position, token, getAstFn); + return getHoverItem(model, position, token, getAstFn, this.callbacks); } async autocomplete( diff --git a/packages/kbn-monaco/src/types.ts b/packages/kbn-monaco/src/types.ts index d0d3da7897700..f58dde1f4ee21 100644 --- a/packages/kbn-monaco/src/types.ts +++ b/packages/kbn-monaco/src/types.ts @@ -31,7 +31,7 @@ export interface LanguageProvidersModule { ) => Promise<{ errors: monaco.editor.IMarkerData[]; warnings: monaco.editor.IMarkerData[] }>; getSuggestionProvider: (callbacks?: Deps) => monaco.languages.CompletionItemProvider; getSignatureProvider?: (callbacks?: Deps) => monaco.languages.SignatureHelpProvider; - getHoverProvider?: () => monaco.languages.HoverProvider; + getHoverProvider?: (callbacks?: Deps) => monaco.languages.HoverProvider; } export interface CustomLangModuleType diff --git a/packages/kbn-text-based-editor/src/text_based_languages_editor.tsx b/packages/kbn-text-based-editor/src/text_based_languages_editor.tsx index 48642d0a584a1..e328e407f4317 100644 --- a/packages/kbn-text-based-editor/src/text_based_languages_editor.tsx +++ b/packages/kbn-text-based-editor/src/text_based_languages_editor.tsx @@ -338,7 +338,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ } return []; }, - getPolicies: async (ctx) => { + getPolicies: async () => { const { data: policies, error } = (await indexManagementApiService?.getAllEnrichPolicies()) || {}; if (error || !policies) { @@ -413,8 +413,8 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ ); const hoverProvider = useMemo( - () => (language === 'esql' ? ESQLLang.getHoverProvider?.() : undefined), - [language] + () => (language === 'esql' ? ESQLLang.getHoverProvider?.(esqlCallbacks) : undefined), + [language, esqlCallbacks] ); const onErrorClick = useCallback(({ startLineNumber, startColumn }: MonacoMessage) => {