From e25413e3c572f506566c25f93dbaeb65aa385fc2 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Tue, 25 Jul 2023 15:32:42 -0700 Subject: [PATCH] [Console] Converted all ```/lib/autocomplete/**/*.js``` files to typescript (#4148) (#4620) Major changes are: * Convert autocomplete part to TS * reafactor and improve typing * clean comments for compileBodyDescription * refactor getTemplate --------- Signed-off-by: Sirazh Gabdullin Signed-off-by: Josh Romero Co-authored-by: Josh Romero (cherry picked from commit d94d7cb683aa76878bba5120cb8130add147d7ad) Signed-off-by: github-actions[bot] # Conflicts: # CHANGELOG.md Co-authored-by: github-actions[bot] --- ...plete.test.js => url_autocomplete.test.ts} | 50 +++--- ...{url_params.test.js => url_params.test.ts} | 68 ++++---- .../{body_completer.js => body_completer.ts} | 146 +++++++++++------- ...ponent.js => accept_endpoint_component.ts} | 20 ++- ...component.js => autocomplete_component.ts} | 27 +++- ...ditional_proxy.js => conditional_proxy.ts} | 15 +- ...ant_component.js => constant_component.ts} | 14 +- ...ent.js => field_autocomplete_component.ts} | 7 +- .../components/full_request_component.ts | 6 +- ..._component.js => global_only_component.ts} | 5 +- ...ponent.js => id_autocomplete_component.ts} | 16 +- .../components/{index.js => index.ts} | 1 + ...ent.js => index_autocomplete_component.ts} | 6 +- .../{list_component.js => list_component.ts} | 32 +++- ...bject_component.js => object_component.ts} | 42 +++-- .../components/param_component.ts | 26 ++++ ...hared_component.js => shared_component.ts} | 12 +- ...component.js => simple_param_component.ts} | 12 +- ....js => template_autocomplete_component.ts} | 2 +- ...nent.js => type_autocomplete_component.ts} | 10 +- ...tern_matcher.js => url_pattern_matcher.ts} | 62 +++++--- ....js => username_autocomplete_component.ts} | 6 +- .../lib/autocomplete/{engine.js => engine.ts} | 124 +++++++++------ .../console/public/lib/autocomplete/types.ts | 77 +++++++++ .../{url_params.js => url_params.ts} | 37 ++--- 25 files changed, 557 insertions(+), 266 deletions(-) rename src/plugins/console/public/lib/autocomplete/__jest__/{url_autocomplete.test.js => url_autocomplete.test.ts} (90%) rename src/plugins/console/public/lib/autocomplete/__jest__/{url_params.test.js => url_params.test.ts} (60%) rename src/plugins/console/public/lib/autocomplete/{body_completer.js => body_completer.ts} (68%) rename src/plugins/console/public/lib/autocomplete/components/{accept_endpoint_component.js => accept_endpoint_component.ts} (72%) rename src/plugins/console/public/lib/autocomplete/components/{autocomplete_component.js => autocomplete_component.ts} (69%) rename src/plugins/console/public/lib/autocomplete/components/{conditional_proxy.js => conditional_proxy.ts} (74%) rename src/plugins/console/public/lib/autocomplete/components/{constant_component.js => constant_component.ts} (78%) rename src/plugins/console/public/lib/autocomplete/components/{field_autocomplete_component.js => field_autocomplete_component.ts} (89%) rename src/plugins/console/public/lib/autocomplete/components/{global_only_component.js => global_only_component.ts} (91%) rename src/plugins/console/public/lib/autocomplete/components/{id_autocomplete_component.js => id_autocomplete_component.ts} (81%) rename src/plugins/console/public/lib/autocomplete/components/{index.js => index.ts} (97%) rename src/plugins/console/public/lib/autocomplete/components/{index_autocomplete_component.js => index_autocomplete_component.ts} (91%) rename src/plugins/console/public/lib/autocomplete/components/{list_component.js => list_component.ts} (76%) rename src/plugins/console/public/lib/autocomplete/components/{object_component.js => object_component.ts} (66%) create mode 100644 src/plugins/console/public/lib/autocomplete/components/param_component.ts rename src/plugins/console/public/lib/autocomplete/components/{shared_component.js => shared_component.ts} (82%) rename src/plugins/console/public/lib/autocomplete/components/{simple_param_component.js => simple_param_component.ts} (78%) rename src/plugins/console/public/lib/autocomplete/components/{template_autocomplete_component.js => template_autocomplete_component.ts} (96%) rename src/plugins/console/public/lib/autocomplete/components/{type_autocomplete_component.js => type_autocomplete_component.ts} (86%) rename src/plugins/console/public/lib/autocomplete/components/{url_pattern_matcher.js => url_pattern_matcher.ts} (70%) rename src/plugins/console/public/lib/autocomplete/components/{username_autocomplete_component.js => username_autocomplete_component.ts} (90%) rename src/plugins/console/public/lib/autocomplete/{engine.js => engine.ts} (57%) create mode 100644 src/plugins/console/public/lib/autocomplete/types.ts rename src/plugins/console/public/lib/autocomplete/{url_params.js => url_params.ts} (68%) diff --git a/src/plugins/console/public/lib/autocomplete/__jest__/url_autocomplete.test.js b/src/plugins/console/public/lib/autocomplete/__jest__/url_autocomplete.test.ts similarity index 90% rename from src/plugins/console/public/lib/autocomplete/__jest__/url_autocomplete.test.js rename to src/plugins/console/public/lib/autocomplete/__jest__/url_autocomplete.test.ts index 111954917140..c8e4221f234d 100644 --- a/src/plugins/console/public/lib/autocomplete/__jest__/url_autocomplete.test.js +++ b/src/plugins/console/public/lib/autocomplete/__jest__/url_autocomplete.test.ts @@ -33,16 +33,25 @@ import { URL_PATH_END_MARKER, UrlPatternMatcher, ListComponent, + SharedComponent, } from '../../autocomplete/components'; import { populateContext } from '../../autocomplete/engine'; +import { PartialAutoCompleteContext } from '../components/autocomplete_component'; +import { ComponentFactory, ParametrizedComponentFactories } from '../../osd'; describe('Url autocomplete', () => { - function patternsTest(name, endpoints, tokenPath, expectedContext, globalUrlComponentFactories) { + function patternsTest( + name: string, + endpoints: Record, + tokenPath: string | Array, + expectedContext: PartialAutoCompleteContext, + globalUrlComponentFactories?: ParametrizedComponentFactories + ) { test(name, function () { const patternMatcher = new UrlPatternMatcher(globalUrlComponentFactories); _.each(endpoints, function (e, id) { - e.id = id; + e.id = id.toString(); _.each(e.patterns, function (p) { patternMatcher.addEndpoint(p, e); }); @@ -52,39 +61,40 @@ describe('Url autocomplete', () => { tokenPath = tokenPath.substr(0, tokenPath.length - 1) + '/' + URL_PATH_END_MARKER; } tokenPath = _.map(tokenPath.split('/'), function (p) { - p = p.split(','); - if (p.length === 1) { - return p[0]; + const pSplit = p.split(','); + if (pSplit.length === 1) { + return pSplit[0]; } - return p; + return pSplit; }); } if (expectedContext.autoCompleteSet) { - expectedContext.autoCompleteSet = _.map(expectedContext.autoCompleteSet, function (t) { - if (_.isString(t)) { - t = { name: t }; + expectedContext.autoCompleteSet = _.map(expectedContext.autoCompleteSet, function (term) { + if (_.isString(term)) { + term = { name: term }; } - return t; + return term; }); expectedContext.autoCompleteSet = _.sortBy(expectedContext.autoCompleteSet, 'name'); } - const context = {}; + const context: PartialAutoCompleteContext = {}; if (expectedContext.method) { context.method = expectedContext.method; } + populateContext( tokenPath, context, null, - expectedContext.autoCompleteSet, - patternMatcher.getTopLevelComponents(context.method) + expectedContext.autoCompleteSet ? true : false, + patternMatcher.getTopLevelComponents(context.method!) ); // override context to just check on id if (context.endpoint) { - context.endpoint = context.endpoint.id; + context.endpoint = (context as any).endpoint.id; } if (context.autoCompleteSet) { @@ -95,9 +105,9 @@ describe('Url autocomplete', () => { }); } - function t(name, meta) { + function t(name: string, meta: string) { if (meta) { - return { name: name, meta: meta }; + return { name, meta }; } return name; } @@ -265,11 +275,11 @@ describe('Url autocomplete', () => { }, }; const globalFactories = { - p: function (name, parent) { + p(name: string, parent: SharedComponent) { return new ListComponent(name, ['g1', 'g2'], parent); }, - getComponent(name) { - return this[name]; + getComponent(name: string): ComponentFactory { + return (this as any)[name]; }, }; @@ -355,7 +365,7 @@ describe('Url autocomplete', () => { })(); (function () { - const endpoints = { + const endpoints: Record = { '1_param': { patterns: ['a/{p}'], methods: ['GET'], diff --git a/src/plugins/console/public/lib/autocomplete/__jest__/url_params.test.js b/src/plugins/console/public/lib/autocomplete/__jest__/url_params.test.ts similarity index 60% rename from src/plugins/console/public/lib/autocomplete/__jest__/url_params.test.js rename to src/plugins/console/public/lib/autocomplete/__jest__/url_params.test.ts index d7ddc622a9b1..74713979cadf 100644 --- a/src/plugins/console/public/lib/autocomplete/__jest__/url_params.test.js +++ b/src/plugins/console/public/lib/autocomplete/__jest__/url_params.test.ts @@ -31,64 +31,72 @@ import _ from 'lodash'; import { UrlParams } from '../../autocomplete/url_params'; import { populateContext } from '../../autocomplete/engine'; +import { Term } from '../types'; +import { PartialAutoCompleteContext } from '../components/autocomplete_component'; +import { SharedComponent } from '../components'; describe('Url params', () => { - function paramTest(name, description, tokenPath, expectedContext, globalParams) { + function paramTest( + name: string, + description: Record, + tokenPath: string | Array, + expectedContext: PartialAutoCompleteContext, + globalParams?: Record + ) { test(name, function () { const urlParams = new UrlParams(description, globalParams || {}); if (typeof tokenPath === 'string') { tokenPath = _.map(tokenPath.split('/'), function (p) { - p = p.split(','); - if (p.length === 1) { - return p[0]; + const pSplit = p.split(','); + if (pSplit.length === 1) { + return pSplit[0]; } - return p; + return pSplit; }); } if (expectedContext.autoCompleteSet) { - expectedContext.autoCompleteSet = _.map(expectedContext.autoCompleteSet, function (t) { - if (_.isString(t)) { - t = { name: t }; + expectedContext.autoCompleteSet = _.map(expectedContext.autoCompleteSet, function (term) { + if (_.isString(term)) { + term = { name: term }; } - return t; + return term; }); expectedContext.autoCompleteSet = _.sortBy(expectedContext.autoCompleteSet, 'name'); } - const context = {}; + const context: PartialAutoCompleteContext = {}; populateContext( tokenPath, context, null, - expectedContext.autoCompleteSet, - urlParams.getTopLevelComponents() + expectedContext.autoCompleteSet ? true : false, + urlParams.getTopLevelComponents() as SharedComponent[] ); - if (context.autoCompleteSet) { - context.autoCompleteSet = _.sortBy(context.autoCompleteSet, 'name'); + const populatedContext = context; + + if (populatedContext.autoCompleteSet) { + populatedContext.autoCompleteSet = _.sortBy(populatedContext.autoCompleteSet, 'name'); } - expect(context).toEqual(expectedContext); + expect(populatedContext).toEqual(expectedContext); }); } - function t(name, meta, insertValue) { - let r = name; + function createTerm(name: string, meta?: string, insertValue?: string) { + const term: Term = { name }; if (meta) { - r = { name: name, meta: meta }; + term.meta = meta; if (meta === 'param' && !insertValue) { insertValue = name + '='; } } if (insertValue) { - if (_.isString(r)) { - r = { name: name }; - } - r.insertValue = insertValue; + term.insertValue = insertValue; } - return r; + return term; } (function () { @@ -99,25 +107,31 @@ describe('Url params', () => { paramTest('settings params', params, 'a/1', { a: ['1'] }); paramTest('autocomplete top level', params, [], { - autoCompleteSet: [t('a', 'param'), t('b', 'flag')], + autoCompleteSet: [createTerm('a', 'param'), createTerm('b', 'flag')], }); paramTest( 'autocomplete top level, with defaults', params, [], - { autoCompleteSet: [t('a', 'param'), t('b', 'flag'), t('c', 'param')] }, + { + autoCompleteSet: [ + createTerm('a', 'param'), + createTerm('b', 'flag'), + createTerm('c', 'param'), + ], + }, { c: [2], } ); paramTest('autocomplete values', params, 'a', { - autoCompleteSet: [t('1', 'a'), t('2', 'a')], + autoCompleteSet: [createTerm('1', 'a'), createTerm('2', 'a')], }); paramTest('autocomplete values flag', params, 'b', { - autoCompleteSet: [t('true', 'b'), t('false', 'b')], + autoCompleteSet: [createTerm('true', 'b'), createTerm('false', 'b')], }); })(); }); diff --git a/src/plugins/console/public/lib/autocomplete/body_completer.js b/src/plugins/console/public/lib/autocomplete/body_completer.ts similarity index 68% rename from src/plugins/console/public/lib/autocomplete/body_completer.js rename to src/plugins/console/public/lib/autocomplete/body_completer.ts index 1fc899033d4c..62a473034613 100644 --- a/src/plugins/console/public/lib/autocomplete/body_completer.js +++ b/src/plugins/console/public/lib/autocomplete/body_completer.ts @@ -28,6 +28,7 @@ * under the License. */ +// eslint-disable-next-line max-classes-per-file import _ from 'lodash'; import { WalkingState, walkTokenPath, wrapComponentWithDefaults } from './engine'; import { @@ -37,12 +38,25 @@ import { ConditionalProxy, GlobalOnlyComponent, } from './components'; +import { ParametrizedComponentFactories } from '../osd/osd'; +import { AutoCompleteContext, Template, Term } from './types'; +import { CoreEditor, Token } from '../../types'; +import { MatchResult } from './components/autocomplete_component'; -function CompilingContext(endpointId, parametrizedComponentFactories) { - this.parametrizedComponentFactories = parametrizedComponentFactories; - this.endpointId = endpointId; +export class CompilingContext { + parametrizedComponentFactories?: ParametrizedComponentFactories; + endpointId: string; + + constructor(endpointId: string, parametrizedComponentFactories?: ParametrizedComponentFactories) { + this.parametrizedComponentFactories = parametrizedComponentFactories; + this.endpointId = endpointId; + } } +export type Description = Record | string | Description[]; + +type LinkType = string | ((context: AutoCompleteContext, editor: CoreEditor) => any); + /** * An object to resolve scope links (syntax endpoint.path1.path2) * @param link the link either string (endpoint.path1.path2, or .path1.path2) or a function (context,editor) @@ -55,19 +69,29 @@ function CompilingContext(endpointId, parametrizedComponentFactories) { * which should return the top level components for the given endpoint */ -function resolvePathToComponents(tokenPath, context, editor, components) { +function resolvePathToComponents( + tokenPath: string[], + context: AutoCompleteContext, + editor: CoreEditor, + components: SharedComponent[] +) { const walkStates = walkTokenPath( tokenPath, [new WalkingState('ROOT', components, [])], context, editor ); - const result = [].concat.apply([], _.map(walkStates, 'components')); + const result: SharedComponent[] = ([] as SharedComponent[]).concat.apply( + [], + _.map(walkStates, 'components') + ); return result; } class ScopeResolver extends SharedComponent { - constructor(link, compilingContext) { + link: LinkType; + compilingContext: CompilingContext; + constructor(link: LinkType, compilingContext: CompilingContext) { super('__scope_link'); if (_.isString(link) && link[0] === '.') { // relative link, inject current endpoint @@ -81,7 +105,7 @@ class ScopeResolver extends SharedComponent { this.compilingContext = compilingContext; } - resolveLinkToComponents(context, editor) { + resolveLinkToComponents(context: AutoCompleteContext, editor: CoreEditor): SharedComponent[] { if (_.isFunction(this.link)) { const desc = this.link(context, editor); return compileDescription(desc, this.compilingContext); @@ -92,7 +116,7 @@ class ScopeResolver extends SharedComponent { let path = this.link.replace(/\./g, '{').split(/(\{)/); const endpoint = path[0]; - let components; + let components: SharedComponent[]; try { if (endpoint === 'GLOBAL') { // global rules need an extra indirection @@ -112,32 +136,47 @@ class ScopeResolver extends SharedComponent { return resolvePathToComponents(path, context, editor, components); } - getTerms(context, editor) { - const options = []; + getTerms(context: AutoCompleteContext, editor: CoreEditor) { + const options: Term[] = []; const components = this.resolveLinkToComponents(context, editor); _.each(components, function (component) { - options.push.apply(options, component.getTerms(context, editor)); + const option = component.getTerms(context, editor); + if (option) { + options.push.apply(options, option); + } }); return options; } - match(token, context, editor) { - const result = { + match(token: string | string[] | Token, context: AutoCompleteContext, editor: CoreEditor) { + const result: MatchResult = { next: [], }; const components = this.resolveLinkToComponents(context, editor); _.each(components, function (component) { const componentResult = component.match(token, context, editor); if (componentResult && componentResult.next) { - result.next.push.apply(result.next, componentResult.next); + result.next?.push.apply(result.next, componentResult.next); } }); return result; } } -function getTemplate(description) { - if (description.__template) { +function getTemplate(description: Description): Description | Template { + if (Array.isArray(description)) { + if (description.length === 1) { + if (_.isObject(description[0])) { + // shortcut to save typing + const innerTemplate = getTemplate(description[0]); + + return innerTemplate != null ? [innerTemplate] : []; + } + } + return []; + } else if (_.isString(description)) { + return description; + } else if (description.__template) { if (description.__raw && _.isString(description.__template)) { return { // This is a special secret attribute that gets passed through to indicate that @@ -158,28 +197,16 @@ function getTemplate(description) { } else if (description.__scope_link) { // assume an object for now. return {}; - } else if (Array.isArray(description)) { - if (description.length === 1) { - if (_.isObject(description[0])) { - // shortcut to save typing - const innerTemplate = getTemplate(description[0]); - - return innerTemplate != null ? [innerTemplate] : []; - } - } - return []; } else if (_.isObject(description)) { return {}; - } else if (_.isString(description) && !/^\{.*\}$/.test(description)) { - return description; } else { return description; } } -function getOptions(description) { - const options = {}; - const template = getTemplate(description); +function getOptions(description: Record) { + const options: Term = {}; + const template = getTemplate(description) as Template; if (!_.isUndefined(template)) { options.template = template; @@ -191,7 +218,10 @@ function getOptions(description) { * @param description a json dict describing the endpoint * @param compilingContext */ -function compileDescription(description, compilingContext) { +function compileDescription( + description: Description, + compilingContext: CompilingContext +): SharedComponent[] { if (Array.isArray(description)) { return [compileList(description, compilingContext)]; } else if (_.isObject(description)) { @@ -211,7 +241,7 @@ function compileDescription(description, compilingContext) { } const obj = compileObject(description, compilingContext); if (description.__condition) { - return [compileCondition(description.__condition, obj, compilingContext)]; + return [compileCondition(description.__condition, obj)]; } else { return [obj]; } @@ -222,23 +252,30 @@ function compileDescription(description, compilingContext) { } } -function compileParametrizedValue(value, compilingContext, template) { +function compileParametrizedValue( + value: string, + compilingContext: CompilingContext, + template?: Template +) { value = value.substr(1, value.length - 2).toLowerCase(); - let component = compilingContext.parametrizedComponentFactories.getComponent(value, true); - if (!component) { + const componentFactory = compilingContext.parametrizedComponentFactories?.getComponent( + value, + true + ); + if (!componentFactory) { throw new Error("no factory found for '" + value + "'"); } - component = component(value, null, template); + let component = componentFactory(value, null, template); if (!_.isUndefined(template)) { - component = wrapComponentWithDefaults(component, { template: template }); + component = wrapComponentWithDefaults(component, { template }); } return component; } -function compileObject(objDescription, compilingContext) { +function compileObject(objDescription: Record, compilingContext: CompilingContext) { const objectC = new ConstantComponent('{'); - const constants = []; - const patterns = []; + const constants: ConstantComponent[] = []; + const patterns: SharedComponent[] = []; _.each(objDescription, function (desc, key) { if (key.indexOf('__') === 0) { // meta key @@ -246,7 +283,7 @@ function compileObject(objDescription, compilingContext) { } const options = getOptions(desc); - let component; + let component: SharedComponent | ConstantComponent; if (/^\{.*\}$/.test(key)) { component = compileParametrizedValue(key, compilingContext, options.template); patterns.push(component); @@ -256,7 +293,7 @@ function compileObject(objDescription, compilingContext) { } else { options.name = key; component = new ConstantComponent(key, null, [options]); - constants.push(component); + constants.push(component as ConstantComponent); } _.map(compileDescription(desc, compilingContext), function (subComponent) { component.addComponent(subComponent); @@ -266,7 +303,7 @@ function compileObject(objDescription, compilingContext) { return objectC; } -function compileList(listRule, compilingContext) { +function compileList(listRule: Description[], compilingContext: CompilingContext) { const listC = new ConstantComponent('['); _.each(listRule, function (desc) { _.each(compileDescription(desc, compilingContext), function (component) { @@ -277,7 +314,7 @@ function compileList(listRule, compilingContext) { } /** takes a compiled object and wraps in a {@link ConditionalProxy }*/ -function compileCondition(description, compiledObject) { +function compileCondition(description: Record, compiledObject: ConstantComponent) { if (description.lines_regex) { return new ConditionalProxy(function (context, editor) { const lines = editor @@ -286,7 +323,7 @@ function compileCondition(description, compiledObject) { return new RegExp(description.lines_regex, 'm').test(lines); }, compiledObject); } else { - throw 'unknown condition type - got: ' + JSON.stringify(description); + throw new Error('unknown condition type - got: ' + JSON.stringify(description)); } } @@ -298,17 +335,18 @@ export function globalsOnlyAutocompleteComponents() { /** * @param endpointId id of the endpoint being compiled. * @param description a json dict describing the endpoint - * @param endpointComponentResolver a function (endpoint,context,editor) which should resolve an endpoint - * to it's list of compiled components. - * @param parametrizedComponentFactories a dict of the following structure - * that will be used as a fall back for pattern keys (i.e.: {type} ,resolved without the $s) + * @param parametrizedComponentFactories an object containing factories for different types of autocomplete components. + * It is used as a fallback for pattern keys (e.g., `{type}`, resolved without the `$s`) + * and has the following structure: * { - * TYPE: function (part, parent, endpoint) { - * return new SharedComponent(part, parent) - * } + * TYPE: (part, parent) => new SharedComponent(part, parent) * } */ -export function compileBodyDescription(endpointId, description, parametrizedComponentFactories) { +export function compileBodyDescription( + endpointId: string, + description: Description, + parametrizedComponentFactories: ParametrizedComponentFactories +) { return compileDescription( description, new CompilingContext(endpointId, parametrizedComponentFactories) diff --git a/src/plugins/console/public/lib/autocomplete/components/accept_endpoint_component.js b/src/plugins/console/public/lib/autocomplete/components/accept_endpoint_component.ts similarity index 72% rename from src/plugins/console/public/lib/autocomplete/components/accept_endpoint_component.js rename to src/plugins/console/public/lib/autocomplete/components/accept_endpoint_component.ts index 1584e2e3ce00..1605230613a7 100644 --- a/src/plugins/console/public/lib/autocomplete/components/accept_endpoint_component.js +++ b/src/plugins/console/public/lib/autocomplete/components/accept_endpoint_component.ts @@ -28,27 +28,31 @@ * under the License. */ -import _ from 'lodash'; import { SharedComponent } from './shared_component'; +import { AutoCompleteContext, Endpoint } from '../types'; +import { CoreEditor } from '../../../types'; export const URL_PATH_END_MARKER = '__url_path_end__'; export class AcceptEndpointComponent extends SharedComponent { - constructor(endpoint, parent) { + endpoint: Endpoint; + constructor(endpoint: Endpoint, parent?: SharedComponent) { super(endpoint.id, parent); this.endpoint = endpoint; } - match(token, context, editor) { + match(token: string, context: AutoCompleteContext, editor: CoreEditor) { if (token !== URL_PATH_END_MARKER) { return null; } - if (this.endpoint.methods && -1 === _.indexOf(this.endpoint.methods, context.method)) { + if (this.endpoint.methods && !this.endpoint.methods.includes(context.method)) { return null; } const r = super.match(token, context, editor); - r.context_values = r.context_values || {}; - r.context_values.endpoint = this.endpoint; - if (_.isNumber(this.endpoint.priority)) { - r.priority = this.endpoint.priority; + if (r) { + r.context_values = r.context_values || {}; + r.context_values.endpoint = this.endpoint; + if (typeof this.endpoint.priority === 'number') { + r.priority = this.endpoint.priority; + } } return r; } diff --git a/src/plugins/console/public/lib/autocomplete/components/autocomplete_component.js b/src/plugins/console/public/lib/autocomplete/components/autocomplete_component.ts similarity index 69% rename from src/plugins/console/public/lib/autocomplete/components/autocomplete_component.js rename to src/plugins/console/public/lib/autocomplete/components/autocomplete_component.ts index 8febcc9f6b5c..718cc372ff8d 100644 --- a/src/plugins/console/public/lib/autocomplete/components/autocomplete_component.js +++ b/src/plugins/console/public/lib/autocomplete/components/autocomplete_component.ts @@ -28,14 +28,31 @@ * under the License. */ +import { Token, CoreEditor } from '../../../types'; +import { AutoCompleteContext, Term } from '../types'; +import { SharedComponent } from './shared_component'; + +// A partial context object that can be merged into an AutoCompleteContext +export type PartialAutoCompleteContext = Partial; + +export interface MatchResult { + context_values?: PartialAutoCompleteContext; + priority?: number; + next: SharedComponent[]; +} + export class AutocompleteComponent { - constructor(name) { + name: string; + next: SharedComponent[]; + + constructor(name: string) { this.name = name; + this.next = []; } /** called to get the possible suggestions for tokens, when this object is at the end of * the resolving chain (and thus can suggest possible continuation paths) */ - getTerms() { + getTerms(context: AutoCompleteContext, editor: CoreEditor | null): Array | null { return []; } /* @@ -48,7 +65,11 @@ export class AutocompleteComponent { priority: optional priority to solve collisions between multiple paths. Min value is used across entire chain } */ - match() { + match( + token: string | string[] | Token, + context: AutoCompleteContext, + editor: CoreEditor | null + ): MatchResult | null | false { return { next: this.next, }; diff --git a/src/plugins/console/public/lib/autocomplete/components/conditional_proxy.js b/src/plugins/console/public/lib/autocomplete/components/conditional_proxy.ts similarity index 74% rename from src/plugins/console/public/lib/autocomplete/components/conditional_proxy.js rename to src/plugins/console/public/lib/autocomplete/components/conditional_proxy.ts index ca5202c1b96f..2a6a8d0ed432 100644 --- a/src/plugins/console/public/lib/autocomplete/components/conditional_proxy.js +++ b/src/plugins/console/public/lib/autocomplete/components/conditional_proxy.ts @@ -28,15 +28,24 @@ * under the License. */ +import { CoreEditor } from '../../../types'; +import { AutoCompleteContext } from '../types'; +import { AutocompleteComponent } from './autocomplete_component'; import { SharedComponent } from './shared_component'; + +type PredicateFunction = (context: AutoCompleteContext, editor: CoreEditor) => boolean; + export class ConditionalProxy extends SharedComponent { - constructor(predicate, delegate) { + predicate: PredicateFunction; + delegate: AutocompleteComponent; + + constructor(predicate: PredicateFunction, delegate: AutocompleteComponent) { super('__condition'); this.predicate = predicate; this.delegate = delegate; } - getTerms(context, editor) { + getTerms(context: AutoCompleteContext, editor: CoreEditor) { if (this.predicate(context, editor)) { return this.delegate.getTerms(context, editor); } else { @@ -44,7 +53,7 @@ export class ConditionalProxy extends SharedComponent { } } - match(token, context, editor) { + match(token: string, context: AutoCompleteContext, editor: CoreEditor) { if (this.predicate(context, editor)) { return this.delegate.match(token, context, editor); } else { diff --git a/src/plugins/console/public/lib/autocomplete/components/constant_component.js b/src/plugins/console/public/lib/autocomplete/components/constant_component.ts similarity index 78% rename from src/plugins/console/public/lib/autocomplete/components/constant_component.js rename to src/plugins/console/public/lib/autocomplete/components/constant_component.ts index 54be1a91ae3d..10e451daf546 100644 --- a/src/plugins/console/public/lib/autocomplete/components/constant_component.js +++ b/src/plugins/console/public/lib/autocomplete/components/constant_component.ts @@ -30,27 +30,31 @@ import _ from 'lodash'; import { SharedComponent } from './shared_component'; +import { CoreEditor } from '../../../types'; +import { AutoCompleteContext, Term } from '../types'; export class ConstantComponent extends SharedComponent { - constructor(name, parent, options) { + options: Term[]; + + constructor(name: string, parent?: SharedComponent | null, options?: string | Term[]) { super(name, parent); if (_.isString(options)) { options = [options]; } this.options = options || [name]; } - getTerms() { + getTerms(context?: AutoCompleteContext, editor?: CoreEditor | null): string[] | Term[] { return this.options; } - addOption(options) { + addOption(options: Term | Term[]) { if (!Array.isArray(options)) { options = [options]; } - [].push.apply(this.options, options); + this.options.push(...options); this.options = _.uniq(this.options); } - match(token, context, editor) { + match(token: string, context: AutoCompleteContext, editor: CoreEditor) { if (token !== this.name) { return null; } diff --git a/src/plugins/console/public/lib/autocomplete/components/field_autocomplete_component.js b/src/plugins/console/public/lib/autocomplete/components/field_autocomplete_component.ts similarity index 89% rename from src/plugins/console/public/lib/autocomplete/components/field_autocomplete_component.js rename to src/plugins/console/public/lib/autocomplete/components/field_autocomplete_component.ts index f29229aae7a3..a6f08150532c 100644 --- a/src/plugins/console/public/lib/autocomplete/components/field_autocomplete_component.js +++ b/src/plugins/console/public/lib/autocomplete/components/field_autocomplete_component.ts @@ -31,18 +31,19 @@ import _ from 'lodash'; import { getFields } from '../../mappings/mappings'; import { ListComponent } from './list_component'; +import { AutoCompleteContext } from '../types'; -function FieldGenerator(context) { +function FieldGenerator(context: AutoCompleteContext) { return _.map(getFields(context.indices, context.types), function (field) { return { name: field.name, meta: field.type }; }); } export class FieldAutocompleteComponent extends ListComponent { - constructor(name, parent, multiValued) { + constructor(name: string, parent: ListComponent, multiValued: boolean) { super(name, FieldGenerator, parent, multiValued); } - validateTokens(tokens) { + validateTokens(tokens: string[]) { if (!this.multiValued && tokens.length > 1) { return false; } diff --git a/src/plugins/console/public/lib/autocomplete/components/full_request_component.ts b/src/plugins/console/public/lib/autocomplete/components/full_request_component.ts index cc44fc3360b7..45a1c79a9aca 100644 --- a/src/plugins/console/public/lib/autocomplete/components/full_request_component.ts +++ b/src/plugins/console/public/lib/autocomplete/components/full_request_component.ts @@ -28,12 +28,12 @@ * under the License. */ -// @ts-ignore import { ConstantComponent } from './constant_component'; +import { SharedComponent } from './shared_component'; export class FullRequestComponent extends ConstantComponent { - private readonly name: string; - constructor(name: string, parent: any, private readonly template: string) { + readonly name: string; + constructor(name: string, parent: SharedComponent, private readonly template: string) { super(name, parent); this.name = name; } diff --git a/src/plugins/console/public/lib/autocomplete/components/global_only_component.js b/src/plugins/console/public/lib/autocomplete/components/global_only_component.ts similarity index 91% rename from src/plugins/console/public/lib/autocomplete/components/global_only_component.js rename to src/plugins/console/public/lib/autocomplete/components/global_only_component.ts index b65cbfaa699a..f38374b6852b 100644 --- a/src/plugins/console/public/lib/autocomplete/components/global_only_component.js +++ b/src/plugins/console/public/lib/autocomplete/components/global_only_component.ts @@ -29,14 +29,15 @@ */ import { SharedComponent } from './shared_component'; +import { AutoCompleteContext } from '../types'; export class GlobalOnlyComponent extends SharedComponent { getTerms() { return null; } - match(token, context) { + match(token: string, context: AutoCompleteContext) { const result = { - next: [], + next: [] as SharedComponent[], }; // try to link to GLOBAL rules diff --git a/src/plugins/console/public/lib/autocomplete/components/id_autocomplete_component.js b/src/plugins/console/public/lib/autocomplete/components/id_autocomplete_component.ts similarity index 81% rename from src/plugins/console/public/lib/autocomplete/components/id_autocomplete_component.js rename to src/plugins/console/public/lib/autocomplete/components/id_autocomplete_component.ts index 18527d89dde4..8d29528c106a 100644 --- a/src/plugins/console/public/lib/autocomplete/components/id_autocomplete_component.js +++ b/src/plugins/console/public/lib/autocomplete/components/id_autocomplete_component.ts @@ -30,12 +30,17 @@ import _ from 'lodash'; import { SharedComponent } from './shared_component'; +import { AutoCompleteContext } from '../types'; +import { CoreEditor } from '../../../types'; + export class IdAutocompleteComponent extends SharedComponent { - constructor(name, parent, multi) { + multi_match: boolean; + + constructor(name: string, parent: SharedComponent, multi = false) { super(name, parent); this.multi_match = multi; } - match(token, context, editor) { + match(token: string | string[], context: AutoCompleteContext, editor: CoreEditor) { if (!token) { return null; } @@ -51,8 +56,11 @@ export class IdAutocompleteComponent extends SharedComponent { return null; } const r = super.match(token, context, editor); - r.context_values = r.context_values || {}; - r.context_values.id = token; + if (r) { + r.context_values = r.context_values || {}; + r.context_values.id = token; + } + return r; } } diff --git a/src/plugins/console/public/lib/autocomplete/components/index.js b/src/plugins/console/public/lib/autocomplete/components/index.ts similarity index 97% rename from src/plugins/console/public/lib/autocomplete/components/index.js rename to src/plugins/console/public/lib/autocomplete/components/index.ts index 55d70609eea8..5e249ba78cde 100644 --- a/src/plugins/console/public/lib/autocomplete/components/index.js +++ b/src/plugins/console/public/lib/autocomplete/components/index.ts @@ -44,3 +44,4 @@ export { TypeAutocompleteComponent } from './type_autocomplete_component'; export { IdAutocompleteComponent } from './id_autocomplete_component'; export { TemplateAutocompleteComponent } from './template_autocomplete_component'; export { UsernameAutocompleteComponent } from './username_autocomplete_component'; +export { ParamComponent } from './param_component'; diff --git a/src/plugins/console/public/lib/autocomplete/components/index_autocomplete_component.js b/src/plugins/console/public/lib/autocomplete/components/index_autocomplete_component.ts similarity index 91% rename from src/plugins/console/public/lib/autocomplete/components/index_autocomplete_component.js rename to src/plugins/console/public/lib/autocomplete/components/index_autocomplete_component.ts index dffa5003f11d..c8729abd5c18 100644 --- a/src/plugins/console/public/lib/autocomplete/components/index_autocomplete_component.js +++ b/src/plugins/console/public/lib/autocomplete/components/index_autocomplete_component.ts @@ -31,14 +31,14 @@ import _ from 'lodash'; import { getIndices } from '../../mappings/mappings'; import { ListComponent } from './list_component'; -function nonValidIndexType(token) { +function nonValidIndexType(token: string) { return !(token === '_all' || token[0] !== '_'); } export class IndexAutocompleteComponent extends ListComponent { - constructor(name, parent, multiValued) { + constructor(name: string, parent: ListComponent, multiValued: boolean) { super(name, getIndices, parent, multiValued); } - validateTokens(tokens) { + validateTokens(tokens: string[]) { if (!this.multiValued && tokens.length > 1) { return false; } diff --git a/src/plugins/console/public/lib/autocomplete/components/list_component.js b/src/plugins/console/public/lib/autocomplete/components/list_component.ts similarity index 76% rename from src/plugins/console/public/lib/autocomplete/components/list_component.js rename to src/plugins/console/public/lib/autocomplete/components/list_component.ts index c3eb08226ee3..ff54ebb64803 100644 --- a/src/plugins/console/public/lib/autocomplete/components/list_component.js +++ b/src/plugins/console/public/lib/autocomplete/components/list_component.ts @@ -30,9 +30,23 @@ import _ from 'lodash'; import { SharedComponent } from './shared_component'; +import { CoreEditor } from '../../../types'; +import { AutoCompleteContext, Term } from '../types'; + +type ListGenerator = (...args: any[]) => Term[]; /** A component that suggests one of the give options, but accepts anything */ export class ListComponent extends SharedComponent { - constructor(name, list, parent, multiValued, allowNonValidValues) { + listGenerator: ListGenerator; + multiValued: boolean; + allowNonValidValues: boolean; + + constructor( + name: string, + list: string[] | ListGenerator, + parent: SharedComponent, + multiValued?: boolean, + allowNonValidValues?: boolean + ) { super(name, parent); this.listGenerator = Array.isArray(list) ? function () { @@ -42,7 +56,7 @@ export class ListComponent extends SharedComponent { this.multiValued = _.isUndefined(multiValued) ? true : multiValued; this.allowNonValidValues = _.isUndefined(allowNonValidValues) ? false : allowNonValidValues; } - getTerms(context, editor) { + getTerms(context: AutoCompleteContext, editor: CoreEditor) { if (!this.multiValued && context.otherTokenValues) { // already have a value -> no suggestions return []; @@ -59,14 +73,14 @@ export class ListComponent extends SharedComponent { if (_.isString(term)) { term = { name: term }; } - return _.defaults(term, { meta: meta }); + return _.defaults(term, { meta }); }); } return ret; } - validateTokens(tokens) { + validateTokens(tokens: string[]): boolean { if (!this.multiValued && tokens.length > 1) { return false; } @@ -91,17 +105,19 @@ export class ListComponent extends SharedComponent { return this.name; } - match(token, context, editor) { + match(token: string | string[], context: AutoCompleteContext, editor: CoreEditor) { if (!Array.isArray(token)) { token = [token]; } - if (!this.allowNonValidValues && !this.validateTokens(token, context, editor)) { + if (!this.allowNonValidValues && !this.validateTokens(token)) { return null; } const result = super.match(token, context, editor); - result.context_values = result.context_values || {}; - result.context_values[this.getContextKey()] = token; + if (result) { + result.context_values = result.context_values || {}; + result.context_values[this.getContextKey()] = token; + } return result; } } diff --git a/src/plugins/console/public/lib/autocomplete/components/object_component.js b/src/plugins/console/public/lib/autocomplete/components/object_component.ts similarity index 66% rename from src/plugins/console/public/lib/autocomplete/components/object_component.js rename to src/plugins/console/public/lib/autocomplete/components/object_component.ts index f979767ab244..9e2a0484ca33 100644 --- a/src/plugins/console/public/lib/autocomplete/components/object_component.js +++ b/src/plugins/console/public/lib/autocomplete/components/object_component.ts @@ -28,38 +28,52 @@ * under the License. */ -import _ from 'lodash'; import { SharedComponent } from './index'; +import { ConstantComponent } from './constant_component'; +import { CoreEditor } from '../../../types'; +import { AutoCompleteContext, Term } from '../types'; /** * @param constants list of components that represent constant keys * @param patternsAndWildCards list of components that represent patterns and should be matched only if * there is no constant matches */ export class ObjectComponent extends SharedComponent { - constructor(name, constants, patternsAndWildCards) { + constants: ConstantComponent[]; + patternsAndWildCards: SharedComponent[]; + + constructor( + name: string, + constants: ConstantComponent[], + patternsAndWildCards: SharedComponent[] + ) { super(name); this.constants = constants; this.patternsAndWildCards = patternsAndWildCards; } - getTerms(context, editor) { - const options = []; - _.each(this.constants, function (component) { - options.push.apply(options, component.getTerms(context, editor)); + getTerms(context: AutoCompleteContext, editor: CoreEditor) { + const options: Term[] = []; + this.constants.forEach((component) => { + options.push(...component.getTerms(context, editor)); }); - _.each(this.patternsAndWildCards, function (component) { - options.push.apply(options, component.getTerms(context, editor)); + this.patternsAndWildCards.forEach((component) => { + const option = component.getTerms(context, editor); + if (option) { + options.push(...option); + } }); return options; } - match(token, context, editor) { - const result = { + match(token: string, context: AutoCompleteContext, editor: CoreEditor) { + const result: { + next: SharedComponent[]; + } = { next: [], }; - _.each(this.constants, function (component) { + this.constants.forEach((component) => { const componentResult = component.match(token, context, editor); if (componentResult && componentResult.next) { - result.next.push.apply(result.next, componentResult.next); + result.next.push(...componentResult.next); } }); @@ -72,10 +86,10 @@ export class ObjectComponent extends SharedComponent { if (result.next.length) { return result; } - _.each(this.patternsAndWildCards, function (component) { + this.patternsAndWildCards.forEach((component) => { const componentResult = component.match(token, context, editor); if (componentResult && componentResult.next) { - result.next.push.apply(result.next, componentResult.next); + result.next.push(...componentResult.next); } }); diff --git a/src/plugins/console/public/lib/autocomplete/components/param_component.ts b/src/plugins/console/public/lib/autocomplete/components/param_component.ts new file mode 100644 index 000000000000..9f8a1a38576a --- /dev/null +++ b/src/plugins/console/public/lib/autocomplete/components/param_component.ts @@ -0,0 +1,26 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Term } from '../types'; +import { ConstantComponent } from './constant_component'; + +export class ParamComponent extends ConstantComponent { + description: string | unknown; + + constructor(name: string, parent: ConstantComponent, description: string | unknown) { + super(name, parent); + this.description = description; + } + getTerms() { + const t: Term = { name: this.name }; + if (this.description === '__flag__') { + t.meta = 'flag'; + } else { + t.meta = 'param'; + t.insertValue = this.name + '='; + } + return [t]; + } +} diff --git a/src/plugins/console/public/lib/autocomplete/components/shared_component.js b/src/plugins/console/public/lib/autocomplete/components/shared_component.ts similarity index 82% rename from src/plugins/console/public/lib/autocomplete/components/shared_component.js rename to src/plugins/console/public/lib/autocomplete/components/shared_component.ts index f974e0d84ecb..e337b8dc5b7d 100644 --- a/src/plugins/console/public/lib/autocomplete/components/shared_component.js +++ b/src/plugins/console/public/lib/autocomplete/components/shared_component.ts @@ -28,10 +28,12 @@ * under the License. */ -import _ from 'lodash'; import { AutocompleteComponent } from './autocomplete_component'; export class SharedComponent extends AutocompleteComponent { - constructor(name, parent) { + _nextDict: { [key: string]: SharedComponent[] }; + _parent?: SharedComponent | null; + + constructor(name: string, parent?: SharedComponent | null) { super(name); this._nextDict = {}; if (parent) { @@ -41,14 +43,14 @@ export class SharedComponent extends AutocompleteComponent { this._parent = parent; } /* return the first component with a given name */ - getComponent(name) { + getComponent(name: string): SharedComponent | undefined { return (this._nextDict[name] || [undefined])[0]; } - addComponent(component) { + addComponent(component: SharedComponent): void { const current = this._nextDict[component.name] || []; current.push(component); this._nextDict[component.name] = current; - this.next = [].concat.apply([], _.values(this._nextDict)); + this.next = ([] as SharedComponent[]).concat(...Object.values(this._nextDict)); } } diff --git a/src/plugins/console/public/lib/autocomplete/components/simple_param_component.js b/src/plugins/console/public/lib/autocomplete/components/simple_param_component.ts similarity index 78% rename from src/plugins/console/public/lib/autocomplete/components/simple_param_component.js rename to src/plugins/console/public/lib/autocomplete/components/simple_param_component.ts index 46853466a51d..1e6428d518c0 100644 --- a/src/plugins/console/public/lib/autocomplete/components/simple_param_component.js +++ b/src/plugins/console/public/lib/autocomplete/components/simple_param_component.ts @@ -28,15 +28,19 @@ * under the License. */ +import { CoreEditor } from '../../../types'; +import { AutoCompleteContext } from '../types'; import { SharedComponent } from './shared_component'; export class SimpleParamComponent extends SharedComponent { - constructor(name, parent) { + constructor(name: string, parent: SharedComponent) { super(name, parent); } - match(token, context, editor) { + match(token: string, context: AutoCompleteContext, editor: CoreEditor) { const result = super.match(token, context, editor); - result.context_values = result.context_values || {}; - result.context_values[this.name] = token; + if (result) { + result.context_values = result.context_values || {}; + result.context_values[this.name] = token; + } return result; } } diff --git a/src/plugins/console/public/lib/autocomplete/components/template_autocomplete_component.js b/src/plugins/console/public/lib/autocomplete/components/template_autocomplete_component.ts similarity index 96% rename from src/plugins/console/public/lib/autocomplete/components/template_autocomplete_component.js rename to src/plugins/console/public/lib/autocomplete/components/template_autocomplete_component.ts index b258e6a259b5..347a0ba13b6c 100644 --- a/src/plugins/console/public/lib/autocomplete/components/template_autocomplete_component.js +++ b/src/plugins/console/public/lib/autocomplete/components/template_autocomplete_component.ts @@ -32,7 +32,7 @@ import { getTemplates } from '../../mappings/mappings'; import { ListComponent } from './list_component'; export class TemplateAutocompleteComponent extends ListComponent { - constructor(name, parent) { + constructor(name: string, parent: ListComponent) { super(name, getTemplates, parent, true, true); } getContextKey() { diff --git a/src/plugins/console/public/lib/autocomplete/components/type_autocomplete_component.js b/src/plugins/console/public/lib/autocomplete/components/type_autocomplete_component.ts similarity index 86% rename from src/plugins/console/public/lib/autocomplete/components/type_autocomplete_component.js rename to src/plugins/console/public/lib/autocomplete/components/type_autocomplete_component.ts index 8a70b6af5fd3..698d1790e878 100644 --- a/src/plugins/console/public/lib/autocomplete/components/type_autocomplete_component.js +++ b/src/plugins/console/public/lib/autocomplete/components/type_autocomplete_component.ts @@ -31,17 +31,19 @@ import _ from 'lodash'; import { ListComponent } from './list_component'; import { getTypes } from '../../mappings/mappings'; -function TypeGenerator(context) { +import { AutoCompleteContext } from '../types'; + +function TypeGenerator(context: AutoCompleteContext) { return getTypes(context.indices); } -function nonValidIndexType(token) { +function nonValidIndexType(token: string) { return !(token === '_all' || token[0] !== '_'); } export class TypeAutocompleteComponent extends ListComponent { - constructor(name, parent, multiValued) { + constructor(name: string, parent: ListComponent, multiValued?: boolean) { super(name, TypeGenerator, parent, multiValued); } - validateTokens(tokens) { + validateTokens(tokens: string[]) { if (!this.multiValued && tokens.length > 1) { return false; } diff --git a/src/plugins/console/public/lib/autocomplete/components/url_pattern_matcher.js b/src/plugins/console/public/lib/autocomplete/components/url_pattern_matcher.ts similarity index 70% rename from src/plugins/console/public/lib/autocomplete/components/url_pattern_matcher.js rename to src/plugins/console/public/lib/autocomplete/components/url_pattern_matcher.ts index 0c7b52eb23e7..63d4daf751b8 100644 --- a/src/plugins/console/public/lib/autocomplete/components/url_pattern_matcher.js +++ b/src/plugins/console/public/lib/autocomplete/components/url_pattern_matcher.ts @@ -38,6 +38,17 @@ import { } from './index'; import { FullRequestComponent } from './full_request_component'; +import { Endpoint, UrlComponent, UrlObjectComponent } from '../types'; +import { ComponentFactory, ParametrizedComponentFactories } from '../../osd/osd'; + +interface MethodData { + rootComponent: SharedComponent; + parametrizedComponentFactories: ParametrizedComponentFactories | DefaultComponentFactories; +} + +interface DefaultComponentFactories { + getComponent: () => void; +} /** * @param parametrizedComponentFactories a dict of the following structure @@ -50,13 +61,15 @@ import { FullRequestComponent } from './full_request_component'; * @constructor */ export class UrlPatternMatcher { + private methodsData: Record; // This is not really a component, just a handy container to make iteration logic simpler - constructor(parametrizedComponentFactories) { + constructor(parametrizedComponentFactories?: ParametrizedComponentFactories) { // We'll group endpoints by the methods which are attached to them, - //to avoid suggesting endpoints that are incompatible with the - //method that the user has entered. + // to avoid suggesting endpoints that are incompatible with the + // method that the user has entered. + this.methodsData = {}; ['HEAD', 'GET', 'PUT', 'POST', 'DELETE'].forEach((method) => { - this[method] = { + this.methodsData[method] = { rootComponent: new SharedComponent('ROOT'), parametrizedComponentFactories: parametrizedComponentFactories || { getComponent: () => {}, @@ -64,10 +77,10 @@ export class UrlPatternMatcher { }; }); } - addEndpoint(pattern, endpoint) { + addEndpoint(pattern: string, endpoint: Endpoint) { endpoint.methods.forEach((method) => { - let c; - let activeComponent = this[method].rootComponent; + let c: UrlComponent | ComponentFactory; + let activeComponent = this.methodsData[method].rootComponent; if (endpoint.template) { new FullRequestComponent(pattern + '[body]', activeComponent, endpoint.template); } @@ -78,7 +91,7 @@ export class UrlPatternMatcher { part = part.substr(1, part.length - 2); if (activeComponent.getComponent(part)) { // we already have something for this, reuse - activeComponent = activeComponent.getComponent(part); + activeComponent = activeComponent.getComponent(part) as SharedComponent; return; } // a new path, resolve. @@ -87,27 +100,32 @@ export class UrlPatternMatcher { // endpoint specific. Support list if (Array.isArray(c)) { c = new ListComponent(part, c, activeComponent); - } else if (_.isObject(c) && c.type === 'list') { - c = new ListComponent( - part, - c.list, - activeComponent, - c.multiValued, - c.allow_non_valid - ); + } else if (_.isObject(c)) { + const objComponent = c as UrlObjectComponent; + if (objComponent.type === 'list') { + c = new ListComponent( + part, + objComponent.list, + activeComponent, + objComponent.multiValued, + objComponent.allow_non_valid + ); + } } else { + // eslint-disable-next-line no-console console.warn('incorrectly configured url component ', part, ' in endpoint', endpoint); c = new SharedComponent(part); } - } else if ((c = this[method].parametrizedComponentFactories.getComponent(part))) { + } else if (this.methodsData[method].parametrizedComponentFactories.getComponent(part)) { // c is a f + c = this.methodsData[method].parametrizedComponentFactories.getComponent(part)!; c = c(part, activeComponent); } else { // just accept whatever with not suggestions c = new SimpleParamComponent(part, activeComponent); } - activeComponent = c; + activeComponent = c as SharedComponent; } else { // not pattern let lookAhead = part; @@ -123,8 +141,8 @@ export class UrlPatternMatcher { if (activeComponent.getComponent(part)) { // we already have something for this, reuse - activeComponent = activeComponent.getComponent(part); - activeComponent.addOption(lookAhead); + activeComponent = activeComponent.getComponent(part)!; + (activeComponent as ConstantComponent).addOption(lookAhead); } else { c = new ConstantComponent(part, activeComponent, lookAhead); activeComponent = c; @@ -136,8 +154,8 @@ export class UrlPatternMatcher { }); } - getTopLevelComponents = function (method) { - const methodRoot = this[method]; + getTopLevelComponents = (method: string) => { + const methodRoot = this.methodsData[method]; if (!methodRoot) { return []; } diff --git a/src/plugins/console/public/lib/autocomplete/components/username_autocomplete_component.js b/src/plugins/console/public/lib/autocomplete/components/username_autocomplete_component.ts similarity index 90% rename from src/plugins/console/public/lib/autocomplete/components/username_autocomplete_component.js rename to src/plugins/console/public/lib/autocomplete/components/username_autocomplete_component.ts index 80e89fbeaeeb..e98ecceec2b5 100644 --- a/src/plugins/console/public/lib/autocomplete/components/username_autocomplete_component.js +++ b/src/plugins/console/public/lib/autocomplete/components/username_autocomplete_component.ts @@ -31,14 +31,14 @@ import _ from 'lodash'; import { getIndices } from '../../mappings/mappings'; import { ListComponent } from './list_component'; -function nonValidUsernameType(token) { +function nonValidUsernameType(token: string) { return token[0] === '_'; } export class UsernameAutocompleteComponent extends ListComponent { - constructor(name, parent, multiValued) { + constructor(name: string, parent: ListComponent, multiValued?: boolean) { super(name, getIndices, parent, multiValued); } - validateTokens(tokens) { + validateTokens(tokens: string[]) { if (!this.multiValued && tokens.length > 1) { return false; } diff --git a/src/plugins/console/public/lib/autocomplete/engine.js b/src/plugins/console/public/lib/autocomplete/engine.ts similarity index 57% rename from src/plugins/console/public/lib/autocomplete/engine.js rename to src/plugins/console/public/lib/autocomplete/engine.ts index 5c4cb8088512..2208c76a43b9 100644 --- a/src/plugins/console/public/lib/autocomplete/engine.js +++ b/src/plugins/console/public/lib/autocomplete/engine.ts @@ -29,70 +29,96 @@ */ import _ from 'lodash'; - -export function wrapComponentWithDefaults(component, defaults) { +import { AutoCompleteContext, Template, Term } from './types'; +import { CoreEditor } from '../../types'; +import { PartialAutoCompleteContext } from './components/autocomplete_component'; +import { SharedComponent } from './components'; + +export function wrapComponentWithDefaults( + component: SharedComponent, + defaults: { template: Template } +) { const originalGetTerms = component.getTerms; component.getTerms = function (context, editor) { let result = originalGetTerms.call(component, context, editor); if (!result) { return result; } - result = _.map(result, (term) => { - if (!_.isObject(term)) { + result = result.map((term) => { + if (typeof term !== 'object') { term = { name: term }; } - return _.defaults(term, defaults); + return { ...defaults, ...term }; }); return result; }; return component; } -const tracer = function () { - if (window.engine_trace) { - console.log.call(console, ...arguments); +const tracer = function (...args: unknown[]) { + if ((window as any).engine_trace) { + // eslint-disable-next-line no-console + console.log(...args); } }; -function passThroughContext(context, extensionList) { - function PTC() {} - - PTC.prototype = context; - const result = new PTC(); +function passThroughContext( + context: AutoCompleteContext | PartialAutoCompleteContext, + extensionList?: PartialAutoCompleteContext[] +) { + const result = Object.create(context) as AutoCompleteContext; if (extensionList) { - extensionList.unshift(result); - _.assign.apply(_, extensionList); - extensionList.shift(); + Object.assign(result, ...extensionList); } return result; } -export function WalkingState(parentName, components, contextExtensionList, depth, priority) { - this.parentName = parentName; - this.components = components; - this.contextExtensionList = contextExtensionList; - this.depth = depth || 0; - this.priority = priority; +export class WalkingState { + name?: string; + parentName: string; + components: SharedComponent[]; + contextExtensionList: PartialAutoCompleteContext[]; + depth: number; + priority: number | undefined; + + constructor( + parentName: string, + components: SharedComponent[], + contextExtensionList: PartialAutoCompleteContext[], + depth = 0, + priority?: number + ) { + this.parentName = parentName; + this.components = components; + this.contextExtensionList = contextExtensionList; + this.depth = depth; + this.priority = priority; + } } -export function walkTokenPath(tokenPath, walkingStates, context, editor) { +export function walkTokenPath( + tokenPath: Array, + walkingStates: WalkingState[], + context: AutoCompleteContext | PartialAutoCompleteContext, + editor: CoreEditor | null +): WalkingState[] { if (!tokenPath || tokenPath.length === 0) { return walkingStates; } const token = tokenPath[0]; - const nextWalkingStates = []; + const nextWalkingStates: WalkingState[] = []; tracer('starting token evaluation [' + token + ']'); - _.each(walkingStates, function (ws) { + walkingStates.forEach((ws) => { const contextForState = passThroughContext(context, ws.contextExtensionList); - _.each(ws.components, function (component) { + ws.components.forEach((component) => { tracer('evaluating [' + token + '] with [' + component.name + ']', component); const result = component.match(token, contextForState, editor); - if (result && !_.isEmpty(result)) { + if (result && Object.keys(result).length !== 0) { tracer('matched [' + token + '] with:', result); let next; - let extensionList; + let extensionList: PartialAutoCompleteContext[]; if (result.next && !Array.isArray(result.next)) { next = [result.next]; } else { @@ -100,15 +126,15 @@ export function walkTokenPath(tokenPath, walkingStates, context, editor) { } if (result.context_values) { extensionList = []; - [].push.apply(extensionList, ws.contextExtensionList); + extensionList = [...extensionList, ...ws.contextExtensionList]; extensionList.push(result.context_values); } else { extensionList = ws.contextExtensionList; } let priority = ws.priority; - if (_.isNumber(result.priority)) { - if (_.isNumber(priority)) { + if (typeof result.priority === 'number') { + if (typeof priority === 'number') { priority = Math.min(priority, result.priority); } else { priority = result.priority; @@ -124,15 +150,19 @@ export function walkTokenPath(tokenPath, walkingStates, context, editor) { if (nextWalkingStates.length === 0) { // no where to go, still return context variables returned so far.. - return _.map(walkingStates, function (ws) { - return new WalkingState(ws.name, [], ws.contextExtensionList); - }); + return walkingStates.map((ws) => new WalkingState(ws.name ?? '', [], ws.contextExtensionList)); } return walkTokenPath(tokenPath.slice(1), nextWalkingStates, context, editor); } -export function populateContext(tokenPath, context, editor, includeAutoComplete, components) { +export function populateContext( + tokenPath: Array, + context: AutoCompleteContext | PartialAutoCompleteContext, + editor: CoreEditor | null, + includeAutoComplete: boolean, + components: SharedComponent[] +) { let walkStates = walkTokenPath( tokenPath, [new WalkingState('ROOT', components, [])], @@ -140,19 +170,22 @@ export function populateContext(tokenPath, context, editor, includeAutoComplete, editor ); if (includeAutoComplete) { - let autoCompleteSet = []; - _.each(walkStates, function (ws) { + let autoCompleteSet: Term[] = []; + walkStates.forEach((ws) => { const contextForState = passThroughContext(context, ws.contextExtensionList); - _.each(ws.components, function (component) { - _.each(component.getTerms(contextForState, editor), function (term) { - if (!_.isObject(term)) { - term = { name: term }; - } - autoCompleteSet.push(term); - }); + ws.components.forEach((component) => { + const terms = component.getTerms(contextForState, editor); + if (terms) { + terms.forEach((term) => { + if (typeof term !== 'object') { + term = { name: term }; + } + autoCompleteSet.push(term); + }); + } }); }); - autoCompleteSet = _.uniq(autoCompleteSet); + autoCompleteSet = [...new Set(autoCompleteSet)]; context.autoCompleteSet = autoCompleteSet; } @@ -167,6 +200,7 @@ export function populateContext(tokenPath, context, editor, includeAutoComplete, }); if (!wsToUse && walkStates.length > 1 && !includeAutoComplete) { + // eslint-disable-next-line no-console console.info( "more then one context active for current path, but autocomplete isn't requested", walkStates diff --git a/src/plugins/console/public/lib/autocomplete/types.ts b/src/plugins/console/public/lib/autocomplete/types.ts new file mode 100644 index 000000000000..ce2af144dba0 --- /dev/null +++ b/src/plugins/console/public/lib/autocomplete/types.ts @@ -0,0 +1,77 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Token, Position, Range } from '../../types'; +import { Description } from './body_completer'; +import { SharedComponent } from './components/shared_component'; + +export interface UrlObjectComponent { + list: string[]; + type: string; + allow_non_valid?: boolean; + multiValued?: boolean; +} + +export type UrlComponent = UrlObjectComponent | SharedComponent | string[]; + +export interface Endpoint { + paramsAutocomplete?: { + getTopLevelComponents: (method?: string | null) => unknown; + }; + patterns: string[]; + bodyAutocompleteRootComponents?: SharedComponent[]; + id: string; + documentation?: string; + data_autocomplete_rules: Description; + methods: string[]; + indices_mode?: string; + priority?: number; + template?: string; + url_params?: Record; + url_components?: Record; +} + +export interface AutoCompleteContext { + autoCompleteType: string; + autoCompleteSet: Term[] | null; + createdWithToken?: Token | null; + updatedForToken?: Token; + replacingToken?: boolean; + rangeToReplace?: Range; + textBoxPosition?: Position; + prefixToAdd?: string; + suffixToAdd?: string; + method: string; + token?: Token; + otherTokenValues?: string[]; + urlTokenPath?: string[]; + endpoint?: Endpoint | string | null; + bodyTokenPath?: string[]; + requestStartRow: number; + endpointComponentResolver: (endpoint: string) => SharedComponent[]; + globalComponentResolver: (term: string, throwOnMissing?: boolean) => SharedComponent[]; + addTemplate?: boolean; + indices?: string | string[]; + types?: string | string[]; + [key: string]: unknown; +} + +export interface Template { + __raw?: boolean; + value?: string; +} + +export interface TermObject { + name?: string; + meta?: string; + template?: Template; + value?: string; + insertValue?: string; + score?: number; + context?: AutoCompleteContext; + snippet?: string; +} + +export type Term = string | TermObject; diff --git a/src/plugins/console/public/lib/autocomplete/url_params.js b/src/plugins/console/public/lib/autocomplete/url_params.ts similarity index 68% rename from src/plugins/console/public/lib/autocomplete/url_params.js rename to src/plugins/console/public/lib/autocomplete/url_params.ts index 4d16148db483..7c31e5da8c96 100644 --- a/src/plugins/console/public/lib/autocomplete/url_params.js +++ b/src/plugins/console/public/lib/autocomplete/url_params.ts @@ -28,41 +28,28 @@ * under the License. */ -import _ from 'lodash'; -import { ConstantComponent, ListComponent, SharedComponent } from './components'; - -export class ParamComponent extends ConstantComponent { - constructor(name, parent, description) { - super(name, parent); - this.description = description; - } - getTerms() { - const t = { name: this.name }; - if (this.description === '__flag__') { - t.meta = 'flag'; - } else { - t.meta = 'param'; - t.insertValue = this.name + '='; - } - return [t]; - } -} +import { ConstantComponent, ListComponent, ParamComponent, SharedComponent } from './components'; export class UrlParams { - constructor(description, defaults) { + rootComponent: SharedComponent; + + constructor(description: Record, defaults?: Record) { // This is not really a component, just a handy container to make iteration logic simpler this.rootComponent = new SharedComponent('ROOT'); - if (_.isUndefined(defaults)) { + if (defaults === undefined) { defaults = { pretty: '__flag__', format: ['json', 'yaml'], filter_path: '', }; } - description = _.clone(description || {}); - _.defaults(description, defaults); - _.each(description, (pDescription, param) => { - const component = new ParamComponent(param, this.rootComponent, pDescription); + description = { ...(description || {}), ...defaults }; + Object.entries(description).forEach(([param, pDescription]) => { + const component = new ParamComponent( + param, + this.rootComponent as ConstantComponent, + pDescription + ); if (Array.isArray(pDescription)) { new ListComponent(param, pDescription, component); } else if (pDescription === '__flag__') {