Skip to content

Commit

Permalink
✨ Add hover on policy name with details
Browse files Browse the repository at this point in the history
  • Loading branch information
dej611 committed Nov 24, 2023
1 parent bac92d3 commit dd9ebf3
Show file tree
Hide file tree
Showing 8 changed files with 165 additions and 72 deletions.
53 changes: 16 additions & 37 deletions packages/kbn-monaco/src/esql/lib/ast/autocomplete/autocomplete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<AutocompleteCommandDefinition[]>;
type GetFieldsByTypeFn = (
Expand Down Expand Up @@ -196,58 +202,31 @@ export async function suggest(
}

function getFieldsByTypeRetriever(resourceRetriever?: ESQLCallbacks) {
const cacheFields = new Map<string, ESQLRealField>();
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<string, ESQLRealField>();
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()) || []);
};
}

Expand Down
18 changes: 0 additions & 18 deletions packages/kbn-monaco/src/esql/lib/ast/autocomplete/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Options = {}, Result = string> = (ctx?: Options) => Result[] | Promise<Result[]>;

/** @internal **/
export interface UserDefinedVariables {
userDefined: string[];
Expand Down
62 changes: 50 additions & 12 deletions packages/kbn-monaco/src/esql/lib/ast/hover/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [] };
}
69 changes: 69 additions & 0 deletions packages/kbn-monaco/src/esql/lib/ast/shared/resources_helpers.ts
Original file line number Diff line number Diff line change
@@ -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<string, ESQLRealField>();
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<string, ESQLRealField>();
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?.()) || [];
};
}
25 changes: 25 additions & 0 deletions packages/kbn-monaco/src/esql/lib/ast/shared/types.ts
Original file line number Diff line number Diff line change
@@ -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<Options = {}, Result = string> = (ctx?: Options) => Result[] | Promise<Result[]>;

/** @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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion packages/kbn-monaco/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export interface LanguageProvidersModule<Deps = unknown> {
) => 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<Deps = unknown>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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) => {
Expand Down

0 comments on commit dd9ebf3

Please sign in to comment.