Console cleanup (#19654)

* refactoring to class style and moving components to own files

* making top level components fetch sensitive to HTTP verb

* fixing issue with _ endpoints getting mistaken as index names for autocomplete possibilities:wq:

* removing stray console log

* removing console log

* PR feedback

* accounting for _all in isNotAnIndexName
This commit is contained in:
Bill McConaghy 2018-06-06 12:00:05 -04:00 committed by GitHub
parent aba92631a3
commit 831b9f83fd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 1191 additions and 908 deletions

View file

@ -25,7 +25,7 @@ import {
} from './kb';
import utils from './utils';
import { populateContext } from './autocomplete/engine';
import { URL_PATH_END_MARKER } from './autocomplete/url_pattern_matcher';
import { URL_PATH_END_MARKER } from './autocomplete/components';
import _ from 'lodash';
import ace from 'brace';
import 'brace/ext/language_tools';
@ -529,7 +529,10 @@ export default function (editor) {
context.token = ret.token;
context.otherTokenValues = ret.otherTokenValues;
context.urlTokenPath = ret.urlTokenPath;
populateContext(ret.urlTokenPath, context, editor, true, getTopLevelUrlCompleteComponents());
const components = getTopLevelUrlCompleteComponents(context.method);
populateContext(ret.urlTokenPath, context, editor, true, components);
context.autoCompleteSet = addMetaToTermsList(context.autoCompleteSet, 'endpoint');
}
@ -544,7 +547,7 @@ export default function (editor) {
return context;
}
populateContext(ret.urlTokenPath, context, editor, false, getTopLevelUrlCompleteComponents());
populateContext(ret.urlTokenPath, context, editor, false, getTopLevelUrlCompleteComponents(context.method));
if (!context.endpoint) {
console.log('couldn\'t resolve an endpoint.');
@ -563,7 +566,7 @@ export default function (editor) {
}
populateContext(tokenPath, context, editor, true,
context.endpoint.paramsAutocomplete.getTopLevelComponents());
context.endpoint.paramsAutocomplete.getTopLevelComponents(context.method));
return context;
}
@ -579,7 +582,7 @@ export default function (editor) {
return context;
}
populateContext(ret.urlTokenPath, context, editor, false, getTopLevelUrlCompleteComponents());
populateContext(ret.urlTokenPath, context, editor, false, getTopLevelUrlCompleteComponents(context.method));
context.bodyTokenPath = ret.bodyTokenPath;
if (!ret.bodyTokenPath) { // zero length tokenPath is true
@ -741,6 +744,10 @@ export default function (editor) {
if (t.type === 'url.part' || t.type === 'url.param' || t.type === 'url.value') {
// we are on the same line as cursor and dealing with a url. Current token is not part of the context
t = tokenIter.stepBackward();
// This will force method parsing
while (t.type === 'whitespace') {
t = tokenIter.stepBackward();
}
}
bodyTokenPath = null; // no not on a body line.
}

View file

@ -18,225 +18,20 @@
*/
const _ = require('lodash');
const engine = require('./engine');
import { WalkingState, walkTokenPath, wrapComponentWithDefaults } from './engine';
import {
ConstantComponent,
SharedComponent,
ObjectComponent,
ConditionalProxy,
GlobalOnlyComponent,
} from './components';
function CompilingContext(endpointId, parametrizedComponentFactories) {
this.parametrizedComponentFactories = parametrizedComponentFactories;
this.endpointId = endpointId;
}
function getTemplate(description) {
if (description.__template) {
return description.__template;
}
else if (description.__one_of) {
return getTemplate(description.__one_of[0]);
}
else if (description.__any_of) {
return [];
}
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);
if (!_.isUndefined(template)) {
options.template = template;
}
return options;
}
/**
* @param description a json dict describing the endpoint
* @param compilingContext
*/
function compileDescription(description, compilingContext) {
if (Array.isArray(description)) {
return [compileList(description, compilingContext)];
}
else if (_.isObject(description)) {
// test for objects list as arrays are also objects
if (description.__scope_link) {
return [new ScopeResolver(description.__scope_link, compilingContext)];
}
if (description.__any_of) {
return [compileList(description.__any_of, compilingContext)];
}
if (description.__one_of) {
return _.flatten(_.map(description.__one_of, function (d) {
return compileDescription(d, compilingContext);
}));
}
const obj = compileObject(description, compilingContext);
if (description.__condition) {
return [compileCondition(description.__condition, obj, compilingContext)];
} else {
return [obj];
}
}
else if (_.isString(description) && /^\{.*\}$/.test(description)) {
return [compileParametrizedValue(description, compilingContext)];
}
else {
return [new engine.ConstantComponent(description)];
}
}
function compileParametrizedValue(value, compilingContext, template) {
value = value.substr(1, value.length - 2).toLowerCase();
let component = compilingContext.parametrizedComponentFactories[value];
if (!component) {
throw new Error('no factory found for \'' + value + '\'');
}
component = component(value, null, template);
if (!_.isUndefined(template)) {
component = engine.wrapComponentWithDefaults(component, { template: template });
}
return component;
}
function compileObject(objDescription, compilingContext) {
const objectC = new engine.ConstantComponent('{');
const constants = [];
const patterns = [];
_.each(objDescription, function (desc, key) {
if (key.indexOf('__') === 0) {
// meta key
return;
}
const options = getOptions(desc);
let component;
if (/^\{.*\}$/.test(key)) {
component = compileParametrizedValue(key, compilingContext, options.template);
patterns.push(component);
}
else if (key === '*') {
component = new engine.SharedComponent(key);
patterns.push(component);
}
else {
options.name = key;
component = new engine.ConstantComponent(key, null, [options]);
constants.push(component);
}
_.map(compileDescription(desc, compilingContext), function (subComponent) {
component.addComponent(subComponent);
});
});
objectC.addComponent(new ObjectComponent('inner', constants, patterns));
return objectC;
}
function compileList(listRule, compilingContext) {
const listC = new engine.ConstantComponent('[');
_.each(listRule, function (desc) {
_.each(compileDescription(desc, compilingContext), function (component) {
listC.addComponent(component);
});
});
return listC;
}
/** takes a compiled object and wraps in a {@link ConditionalProxy }*/
function compileCondition(description, compiledObject) {
if (description.lines_regex) {
return new ConditionalProxy(function (context, editor) {
const lines = editor.getSession().getLines(context.requestStartRow, editor.getCursorPosition().row).join('\n');
return new RegExp(description.lines_regex, 'm').test(lines);
}, compiledObject);
} else {
throw 'unknown condition type - got: ' + JSON.stringify(description);
}
}
/**
* @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
*/
function ObjectComponent(name, constants, patternsAndWildCards) {
engine.AutocompleteComponent.call(this, name);
this.constants = constants;
this.patternsAndWildCards = patternsAndWildCards;
}
ObjectComponent.prototype = _.create(
engine.AutocompleteComponent.prototype,
{ 'constructor': ObjectComponent });
(function (cls) {
cls.getTerms = function (context, editor) {
const options = [];
_.each(this.constants, function (component) {
options.push.apply(options, component.getTerms(context, editor));
});
_.each(this.patternsAndWildCards, function (component) {
options.push.apply(options, component.getTerms(context, editor));
});
return options;
};
cls.match = function (token, context, editor) {
const result = {
next: []
};
_.each(this.constants, function (component) {
const componentResult = component.match(token, context, editor);
if (componentResult && componentResult.next) {
result.next.push.apply(result.next, componentResult.next);
}
});
// try to link to GLOBAL rules
const globalRules = context.globalComponentResolver(token, false);
if (globalRules) {
result.next.push.apply(result.next, globalRules);
}
if (result.next.length) {
return result;
}
_.each(this.patternsAndWildCards, function (component) {
const componentResult = component.match(token, context, editor);
if (componentResult && componentResult.next) {
result.next.push.apply(result.next, componentResult.next);
}
});
return result;
};
}(ObjectComponent.prototype));
/**
* 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)
@ -248,29 +43,31 @@ ObjectComponent.prototype = _.create(
* For this to work we expect the context to include a method context.endpointComponentResolver(endpoint)
* which should return the top level components for the given endpoint
*/
function ScopeResolver(link, compilingContext) {
engine.SharedComponent.call(this, '__scope_link', null);
if (_.isString(link) && link[0] === '.') {
// relative link, inject current endpoint
if (link === '.') {
link = compilingContext.endpointId;
}
else {
link = compilingContext.endpointId + link;
}
}
this.link = link;
this.compilingContext = compilingContext;
function resolvePathToComponents(tokenPath, context, editor, components) {
const walkStates = walkTokenPath(tokenPath, [new WalkingState('ROOT', components, [])], context, editor);
const result = [].concat.apply([], _.pluck(walkStates, 'components'));
return result;
}
ScopeResolver.prototype = _.create(
engine.SharedComponent.prototype,
{ 'constructor': ScopeResolver });
class ScopeResolver extends SharedComponent {
constructor(link, compilingContext) {
super('__scope_link');
if (_.isString(link) && link[0] === '.') {
// relative link, inject current endpoint
if (link === '.') {
link = compilingContext.endpointId;
}
else {
link = compilingContext.endpointId + link;
}
}
this.link = link;
this.compilingContext = compilingContext;
}
(function (cls) {
cls.resolveLinkToComponents = function (context, editor) {
resolveLinkToComponents(context, editor) {
if (_.isFunction(this.link)) {
const desc = this.link(context, editor);
return compileDescription(desc, this.compilingContext);
@ -300,19 +97,19 @@ ScopeResolver.prototype = _.create(
catch (e) {
throw new Error('failed to resolve link [' + this.link + ']: ' + e);
}
return engine.resolvePathToComponents(path, context, editor, components);
};
return resolvePathToComponents(path, context, editor, components);
}
cls.getTerms = function (context, editor) {
getTerms(context, editor) {
const options = [];
const components = this.resolveLinkToComponents(context, editor);
_.each(components, function (component) {
options.push.apply(options, component.getTerms(context, editor));
});
return options;
};
}
cls.match = function (token, context, editor) {
match(token, context, editor) {
const result = {
next: []
};
@ -326,78 +123,154 @@ ScopeResolver.prototype = _.create(
});
return result;
};
}(ScopeResolver.prototype));
}
}
function getTemplate(description) {
if (description.__template) {
return description.__template;
} else if (description.__one_of) {
return getTemplate(description.__one_of[0]);
} else if (description.__any_of) {
return [];
} 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]);
function ConditionalProxy(predicate, delegate) {
engine.SharedComponent.call(this, '__condition', null);
this.predicate = predicate;
this.delegate = delegate;
return innerTemplate != null ? [innerTemplate] : [];
}
}
return [];
} else if (_.isObject(description)) {
return {};
} else if (_.isString(description) && !/^\{.*\}$/.test(description)) {
return description;
} else {
return description;
}
}
ConditionalProxy.prototype = _.create(
engine.SharedComponent.prototype,
{ 'constructor': ConditionalProxy });
function getOptions(description) {
const options = {};
const template = getTemplate(description);
(function (cls) {
cls.getTerms = function (context, editor) {
if (this.predicate(context, editor)) {
return this.delegate.getTerms(context, editor);
} else {
return null;
}
};
cls.match = function (token, context, editor) {
if (this.predicate(context, editor)) {
return this.delegate.match(token, context, editor);
} else {
return false;
}
};
}(ConditionalProxy.prototype));
function GlobalOnlyComponent(name) {
engine.AutocompleteComponent.call(this, name);
if (!_.isUndefined(template)) {
options.template = template;
}
return options;
}
GlobalOnlyComponent.prototype = _.create(
engine.AutocompleteComponent.prototype,
{ 'constructor': ObjectComponent });
/**
* @param description a json dict describing the endpoint
* @param compilingContext
*/
function compileDescription(description, compilingContext) {
if (Array.isArray(description)) {
return [compileList(description, compilingContext)];
} else if (_.isObject(description)) {
// test for objects list as arrays are also objects
if (description.__scope_link) {
return [new ScopeResolver(description.__scope_link, compilingContext)];
}
if (description.__any_of) {
return [compileList(description.__any_of, compilingContext)];
}
if (description.__one_of) {
return _.flatten(
_.map(description.__one_of, function (d) {
return compileDescription(d, compilingContext);
})
);
}
const obj = compileObject(description, compilingContext);
if (description.__condition) {
return [compileCondition(description.__condition, obj, compilingContext)];
} else {
return [obj];
}
} else if (_.isString(description) && /^\{.*\}$/.test(description)) {
return [compileParametrizedValue(description, compilingContext)];
} else {
return [new ConstantComponent(description)];
}
}
function compileParametrizedValue(value, compilingContext, template) {
value = value.substr(1, value.length - 2).toLowerCase();
let component = compilingContext.parametrizedComponentFactories[value];
if (!component) {
throw new Error('no factory found for \'' + value + '\'');
}
component = component(value, null, template);
if (!_.isUndefined(template)) {
component = wrapComponentWithDefaults(component, { template: template });
}
return component;
}
(function (cls) {
cls.getTerms = function () {
return null;
};
cls.match = function (token, context) {
const result = {
next: []
};
// try to link to GLOBAL rules
const globalRules = context.globalComponentResolver(token, false);
if (globalRules) {
result.next.push.apply(result.next, globalRules);
function compileObject(objDescription, compilingContext) {
const objectC = new ConstantComponent('{');
const constants = [];
const patterns = [];
_.each(objDescription, function (desc, key) {
if (key.indexOf('__') === 0) {
// meta key
return;
}
if (result.next.length) {
return result;
const options = getOptions(desc);
let component;
if (/^\{.*\}$/.test(key)) {
component = compileParametrizedValue(
key,
compilingContext,
options.template
);
patterns.push(component);
} else if (key === '*') {
component = new SharedComponent(key);
patterns.push(component);
} else {
options.name = key;
component = new ConstantComponent(key, null, [options]);
constants.push(component);
}
// just loop back to us
result.next = [this];
_.map(compileDescription(desc, compilingContext), function (subComponent) {
component.addComponent(subComponent);
});
});
objectC.addComponent(new ObjectComponent('inner', constants, patterns));
return objectC;
}
return result;
};
}(GlobalOnlyComponent.prototype));
function compileList(listRule, compilingContext) {
const listC = new ConstantComponent('[');
_.each(listRule, function (desc) {
_.each(compileDescription(desc, compilingContext), function (component) {
listC.addComponent(component);
});
});
return listC;
}
/** takes a compiled object and wraps in a {@link ConditionalProxy }*/
function compileCondition(description, compiledObject) {
if (description.lines_regex) {
return new ConditionalProxy(function (context, editor) {
const lines = editor
.getSession()
.getLines(context.requestStartRow, editor.getCursorPosition().row)
.join('\n');
return new RegExp(description.lines_regex, 'm').test(lines);
}, compiledObject);
} else {
throw 'unknown condition type - got: ' + JSON.stringify(description);
}
}
// a list of component that match anything but give auto complete suggestions based on global API entries.
export function globalsOnlyAutocompleteComponents() {
@ -412,11 +285,18 @@ export function globalsOnlyAutocompleteComponents() {
* @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)
* {
* TYPE: function (part, parent, endpoint) {
* return new SharedComponent(part, parent)
* }
* }
* TYPE: function (part, parent, endpoint) {
* return new SharedComponent(part, parent)
* }
* }
*/
export function compileBodyDescription(endpointId, description, parametrizedComponentFactories) {
return compileDescription(description, new CompilingContext(endpointId, parametrizedComponentFactories));
export function compileBodyDescription(
endpointId,
description,
parametrizedComponentFactories
) {
return compileDescription(
description,
new CompilingContext(endpointId, parametrizedComponentFactories)
);
}

View file

@ -0,0 +1,43 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import _ from 'lodash';
import { SharedComponent } from './shared_component';
export const URL_PATH_END_MARKER = '__url_path_end__';
export class AcceptEndpointComponent extends SharedComponent {
constructor(endpoint, parent) {
super(endpoint.id, parent);
this.endpoint = endpoint;
}
match(token, context, editor) {
if (token !== URL_PATH_END_MARKER) {
return null;
}
if (this.endpoint.methods && -1 === _.indexOf(this.endpoint.methods, 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;
}
return r;
}
}

View file

@ -0,0 +1,45 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export class AutocompleteComponent {
constructor(name) {
this.name = name;
}
/** 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() {
return [];
}
/*
if the current matcher matches this term, this method should return an object with the following keys
{
context_values: {
values extract from term that should be added to the context
}
next: AutocompleteComponent(s) to use next
priority: optional priority to solve collisions between multiple paths. Min value is used across entire chain
}
*/
match() {
return {
next: this.next,
};
}
}

View file

@ -0,0 +1,43 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { SharedComponent } from './shared_component';
export class ConditionalProxy extends SharedComponent {
constructor(predicate, delegate) {
super('__condition');
this.predicate = predicate;
this.delegate = delegate;
}
getTerms(context, editor) {
if (this.predicate(context, editor)) {
return this.delegate.getTerms(context, editor);
} else {
return null;
}
}
match(token, context, editor) {
if (this.predicate(context, editor)) {
return this.delegate.match(token, context, editor);
} else {
return false;
}
}
}

View file

@ -0,0 +1,50 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import _ from 'lodash';
import { SharedComponent } from './shared_component';
export class ConstantComponent extends SharedComponent {
constructor(name, parent, options) {
super(name, parent);
if (_.isString(options)) {
options = [options];
}
this.options = options || [name];
}
getTerms() {
return this.options;
}
addOption(options) {
if (!Array.isArray(options)) {
options = [options];
}
[].push.apply(this.options, options);
this.options = _.uniq(this.options);
}
match(token, context, editor) {
if (token !== this.name) {
return null;
}
return super.match(token, context, editor);
}
}

View file

@ -0,0 +1,50 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import _ from 'lodash';
import mappings from '../../mappings';
import { ListComponent } from './list_component';
function FieldGenerator(context) {
return _.map(mappings.getFields(context.indices, context.types), function (field) {
return { name: field.name, meta: field.type };
});
}
export class FieldAutocompleteComponent extends ListComponent {
constructor(name, parent, multiValued) {
super(name, FieldGenerator, parent, multiValued);
}
validateTokens(tokens) {
if (!this.multiValued && tokens.length > 1) {
return false;
}
return !_.find(tokens, function (token) {
return token.match(/[^\w.?*]/);
});
}
getDefaultTermMeta() {
return 'field';
}
getContextKey() {
return 'fields';
}
}

View file

@ -0,0 +1,46 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { SharedComponent } from './shared_component';
export class GlobalOnlyComponent extends SharedComponent {
getTerms() {
return null;
}
match(token, context) {
const result = {
next: []
};
// try to link to GLOBAL rules
const globalRules = context.globalComponentResolver(token, false);
if (globalRules) {
result.next.push.apply(result.next, globalRules);
}
if (result.next.length) {
return result;
}
// just loop back to us
result.next = [this];
return result;
}
}

View file

@ -0,0 +1,45 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import _ from 'lodash';
import { SharedComponent } from './shared_component';
export class IdAutocompleteComponent extends SharedComponent {
constructor(name, parent, multi) {
super(name, parent);
this.multi_match = multi;
}
match(token, context, editor) {
if (!token) {
return null;
}
if (!this.multi_match && Array.isArray(token)) {
return null;
}
token = Array.isArray(token) ? token : [token];
if (_.find(token, function (t) {
return t.match(/[\/,]/);
})) {
return null;
}
const r = super.match(token, context, editor);
r.context_values = r.context_values || {};
r.context_values.id = token;
return r;
}
}

View file

@ -0,0 +1,33 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export { AutocompleteComponent } from './autocomplete_component';
export { SharedComponent } from './shared_component';
export { ConstantComponent } from './constant_component';
export { ListComponent } from './list_component';
export { SimpleParamComponent } from './simple_param_component';
export { ConditionalProxy } from './conditional_proxy';
export { GlobalOnlyComponent } from './global_only_component';
export { ObjectComponent } from './object_component';
export { AcceptEndpointComponent, URL_PATH_END_MARKER } from './accept_endpoint_component';
export { UrlPatternMatcher } from './url_pattern_matcher';
export { IndexAutocompleteComponent } from './index_autocomplete_component';
export { FieldAutocompleteComponent } from './field_autocomplete_component';
export { TypeAutocompleteComponent } from './type_autocomplete_component';
export { IdAutocompleteComponent } from './id_autocomplete_component';

View file

@ -0,0 +1,43 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import _ from 'lodash';
import mappings from '../../mappings';
import { ListComponent } from './list_component';
function nonValidIndexType(token) {
return !(token === '_all' || token[0] !== '_');
}
export class IndexAutocompleteComponent extends ListComponent {
constructor(name, parent, multiValued) {
super(name, mappings.getIndices, parent, multiValued);
}
validateTokens(tokens) {
if (!this.multiValued && tokens.length > 1) {
return false;
}
return !_.find(tokens, nonValidIndexType);
}
getDefaultTermMeta() {
return 'index';
}
getContextKey() {
return 'indices';
}
}

View file

@ -0,0 +1,94 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import _ from 'lodash';
import { SharedComponent } from './shared_component';
/** A component that suggests one of the give options, but accepts anything */
export class ListComponent extends SharedComponent {
constructor(name, list, parent, multiValued, allowNonValidValues) {
super(name, parent);
this.listGenerator = Array.isArray(list) ? function () {
return list;
} : list;
this.multiValued = _.isUndefined(multiValued) ? true : multiValued;
this.allowNonValidValues = _.isUndefined(allowNonValidValues) ? false : allowNonValidValues;
}
getTerms(context, editor) {
if (!this.multiValued && context.otherTokenValues) {
// already have a value -> no suggestions
return [];
}
let alreadySet = context.otherTokenValues || [];
if (_.isString(alreadySet)) {
alreadySet = [alreadySet];
}
let ret = _.difference(this.listGenerator(context, editor), alreadySet);
if (this.getDefaultTermMeta()) {
const meta = this.getDefaultTermMeta();
ret = _.map(ret, function (term) {
if (_.isString(term)) {
term = { 'name': term };
}
return _.defaults(term, { meta: meta });
});
}
return ret;
}
validateTokens(tokens) {
if (!this.multiValued && tokens.length > 1) {
return false;
}
// verify we have all tokens
const list = this.listGenerator();
const notFound = _.any(tokens, function (token) {
return list.indexOf(token) === -1;
});
if (notFound) {
return false;
}
return true;
}
getContextKey() {
return this.name;
}
getDefaultTermMeta() {
return this.name;
}
match(token, context, editor) {
if (!Array.isArray(token)) {
token = [token];
}
if (!this.allowNonValidValues && !this.validateTokens(token, context, editor)) {
return null;
}
const result = super.match(token, context, editor);
result.context_values = result.context_values || {};
result.context_values[this.getContextKey()] = token;
return result;
}
}

View file

@ -0,0 +1,74 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import _ from 'lodash';
import { SharedComponent } from '.';
/**
* @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) {
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));
});
_.each(this.patternsAndWildCards, function (component) {
options.push.apply(options, component.getTerms(context, editor));
});
return options;
}
match(token, context, editor) {
const result = {
next: []
};
_.each(this.constants, function (component) {
const componentResult = component.match(token, context, editor);
if (componentResult && componentResult.next) {
result.next.push.apply(result.next, componentResult.next);
}
});
// try to link to GLOBAL rules
const globalRules = context.globalComponentResolver(token, false);
if (globalRules) {
result.next.push.apply(result.next, globalRules);
}
if (result.next.length) {
return result;
}
_.each(this.patternsAndWildCards, function (component) {
const componentResult = component.match(token, context, editor);
if (componentResult && componentResult.next) {
result.next.push.apply(result.next, componentResult.next);
}
});
return result;
}
}

View file

@ -0,0 +1,42 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import _ from 'lodash';
import { AutocompleteComponent } from './autocomplete_component';
export class SharedComponent extends AutocompleteComponent {
constructor(name, parent) {
super(name);
this._nextDict = {};
if (parent) {
parent.addComponent(this);
}
// for debugging purposes
this._parent = parent;
}
/* return the first component with a given name */
getComponent(name) {
return (this._nextDict[name] || [undefined])[0];
}
addComponent(component) {
const current = this._nextDict[component.name] || [];
current.push(component);
this._nextDict[component.name] = current;
this.next = [].concat.apply([], _.values(this._nextDict));
}
}

View file

@ -0,0 +1,32 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { SharedComponent } from './shared_component';
export class SimpleParamComponent extends SharedComponent {
constructor(name, parent) {
super(name, parent);
}
match(token, context, editor) {
const result = super.match(token, context, editor);
result.context_values = result.context_values || {};
result.context_values[this.name] = token;
return result;
}
}

View file

@ -0,0 +1,47 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import _ from 'lodash';
import { ListComponent } from './list_component';
import mappings from '../../mappings';
function TypeGenerator(context) {
return mappings.getTypes(context.indices);
}
function nonValidIndexType(token) {
return !(token === '_all' || token[0] !== '_');
}
export class TypeAutocompleteComponent extends ListComponent {
constructor(name, parent, multiValued) {
super(name, TypeGenerator, parent, multiValued);
}
validateTokens(tokens) {
if (!this.multiValued && tokens.length > 1) {
return false;
}
return !_.find(tokens, nonValidIndexType);
}
getDefaultTermMeta() {
return 'type';
}
getContextKey() {
return 'types';
}
}

View file

@ -0,0 +1,136 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import _ from 'lodash';
import {
SharedComponent,
ConstantComponent,
AcceptEndpointComponent,
ListComponent,
SimpleParamComponent,
} from '.';
/**
* @param parametrizedComponentFactories a dict of the following structure
* that will be used as a fall back for pattern parameters (i.e.: {indices})
* {
* indices: function (part, parent) {
* return new SharedComponent(part, parent)
* }
* }
* @constructor
*/
export class UrlPatternMatcher {
// This is not really a component, just a handy container to make iteration logic simpler
constructor(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.
['HEAD', 'GET', 'PUT', 'POST', 'DELETE'].forEach((method) => {
this[method] = {
rootComponent: new SharedComponent('ROOT'),
parametrizedComponentFactories: parametrizedComponentFactories || {}
};
});
}
addEndpoint(pattern, endpoint) {
endpoint.methods.forEach((method) => {
let c;
let activeComponent = this[method].rootComponent;
const endpointComponents = endpoint.url_components || {};
const partList = pattern.split('/');
_.each(
partList,
function (part, partIndex) {
if (part.search(/^{.+}$/) >= 0) {
part = part.substr(1, part.length - 2);
if (activeComponent.getComponent(part)) {
// we already have something for this, reuse
activeComponent = activeComponent.getComponent(part);
return;
}
// a new path, resolve.
if ((c = endpointComponents[part])) {
// 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 {
console.warn(
'incorrectly configured url component ',
part,
' in endpoint',
endpoint
);
c = new SharedComponent(part);
}
} else if ((c = this[method].parametrizedComponentFactories[part])) {
// c is a f
c = c(part, activeComponent);
} else {
// just accept whatever with not suggestions
c = new SimpleParamComponent(part, activeComponent);
}
activeComponent = c;
} else {
// not pattern
let lookAhead = part;
let s;
for (partIndex++; partIndex < partList.length; partIndex++) {
s = partList[partIndex];
if (s.indexOf('{') >= 0) {
break;
}
lookAhead += '/' + s;
}
if (activeComponent.getComponent(part)) {
// we already have something for this, reuse
activeComponent = activeComponent.getComponent(part);
activeComponent.addOption(lookAhead);
} else {
c = new ConstantComponent(part, activeComponent, lookAhead);
activeComponent = c;
}
}
},
this
);
// mark end of endpoint path
new AcceptEndpointComponent(endpoint, activeComponent);
});
}
getTopLevelComponents = function (method) {
const methodRoot = this[method];
if (!methodRoot) {
return [];
}
return methodRoot.rootComponent.next;
};
}

View file

@ -19,205 +19,10 @@
const _ = require('lodash');
export function AutocompleteComponent(name) {
this.name = name;
}
/** 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)
*/
AutocompleteComponent.prototype.getTerms = function () {
return [];
};
/*
if the current matcher matches this term, this method should return an object with the following keys
{
context_values: {
values extract from term that should be added to the context
}
next: AutocompleteComponent(s) to use next
priority: optional priority to solve collisions between multiple paths. Min value is used across entire chain
}
*/
AutocompleteComponent.prototype.match = function () {
return {
next: this.next
};
};
function SharedComponent(name, parent) {
AutocompleteComponent.call(this, name);
this._nextDict = {};
if (parent) {
parent.addComponent(this);
}
// for debugging purposes
this._parent = parent;
}
SharedComponent.prototype = _.create(
AutocompleteComponent.prototype,
{ 'constructor': SharedComponent });
(function (cls) {
/* return the first component with a given name */
cls.getComponent = function (name) {
return (this._nextDict[name] || [undefined])[0];
};
cls.addComponent = function (component) {
const current = this._nextDict[component.name] || [];
current.push(component);
this._nextDict[component.name] = current;
this.next = [].concat.apply([], _.values(this._nextDict));
};
}(SharedComponent.prototype));
/** A component that suggests one of the give options, but accepts anything */
function ListComponent(name, list, parent, multiValued, allowNonValidValues) {
SharedComponent.call(this, name, parent);
this.listGenerator = Array.isArray(list) ? function () {
return list;
} : list;
this.multiValued = _.isUndefined(multiValued) ? true : multiValued;
this.allowNonValidValues = _.isUndefined(allowNonValidValues) ? false : allowNonValidValues;
}
ListComponent.prototype = _.create(SharedComponent.prototype, { 'constructor': ListComponent });
(function (cls) {
cls.getTerms = function (context, editor) {
if (!this.multiValued && context.otherTokenValues) {
// already have a value -> no suggestions
return [];
}
let alreadySet = context.otherTokenValues || [];
if (_.isString(alreadySet)) {
alreadySet = [alreadySet];
}
let ret = _.difference(this.listGenerator(context, editor), alreadySet);
if (this.getDefaultTermMeta()) {
const meta = this.getDefaultTermMeta();
ret = _.map(ret, function (term) {
if (_.isString(term)) {
term = { 'name': term };
}
return _.defaults(term, { meta: meta });
});
}
return ret;
};
cls.validateTokens = function (tokens) {
if (!this.multiValued && tokens.length > 1) {
return false;
}
// verify we have all tokens
const list = this.listGenerator();
const notFound = _.any(tokens, function (token) {
return list.indexOf(token) === -1;
});
if (notFound) {
return false;
}
return true;
};
cls.getContextKey = function () {
return this.name;
};
cls.getDefaultTermMeta = function () {
return this.name;
};
cls.match = function (token, context, editor) {
if (!Array.isArray(token)) {
token = [token];
}
if (!this.allowNonValidValues && !this.validateTokens(token, context, editor)) {
return null;
}
const result = Object.getPrototypeOf(cls).match.call(this, token, context, editor);
result.context_values = result.context_values || {};
result.context_values[this.getContextKey()] = token;
return result;
};
}(ListComponent.prototype));
function SimpleParamComponent(name, parent) {
SharedComponent.call(this, name, parent);
}
SimpleParamComponent.prototype = _.create(SharedComponent.prototype, { 'constructor': SimpleParamComponent });
(function (cls) {
cls.match = function (token, context, editor) {
const result = Object.getPrototypeOf(cls).match.call(this, token, context, editor);
result.context_values = result.context_values || {};
result.context_values[this.name] = token;
return result;
};
}(SimpleParamComponent.prototype));
function ConstantComponent(name, parent, options) {
SharedComponent.call(this, name, parent);
if (_.isString(options)) {
options = [options];
}
this.options = options || [name];
}
ConstantComponent.prototype = _.create(SharedComponent.prototype, { 'constructor': ConstantComponent });
export { SharedComponent, ListComponent, SimpleParamComponent, ConstantComponent };
(function (cls) {
cls.getTerms = function () {
return this.options;
};
cls.addOption = function (options) {
if (!Array.isArray(options)) {
options = [options];
}
[].push.apply(this.options, options);
this.options = _.uniq(this.options);
};
cls.match = function (token, context, editor) {
if (token !== this.name) {
return null;
}
return Object.getPrototypeOf(cls).match.call(this, token, context, editor);
};
}(ConstantComponent.prototype));
export function wrapComponentWithDefaults(component, defaults) {
function Wrapper() {
}
Wrapper.prototype = {};
for (const key in component) {
if (_.isFunction(component[key])) {
Wrapper.prototype[key] = _.bindKey(component, key);
}
}
Wrapper.prototype.getTerms = function (context, editor) {
let result = component.getTerms(context, editor);
const originalGetTerms = component.getTerms;
component.getTerms = function (context, editor) {
let result = originalGetTerms.call(component, context, editor);
if (!result) {
return result;
}
@ -229,7 +34,7 @@ export function wrapComponentWithDefaults(component, defaults) {
}, this);
return result;
};
return new Wrapper();
return component;
}
const tracer = function () {
@ -254,7 +59,7 @@ function passThroughContext(context, extensionList) {
return result;
}
function WalkingState(parentName, components, contextExtensionList, depth, priority) {
export function WalkingState(parentName, components, contextExtensionList, depth, priority) {
this.parentName = parentName;
this.components = components;
this.contextExtensionList = contextExtensionList;
@ -263,7 +68,7 @@ function WalkingState(parentName, components, contextExtensionList, depth, prior
}
function walkTokenPath(tokenPath, walkingStates, context, editor) {
export function walkTokenPath(tokenPath, walkingStates, context, editor) {
if (!tokenPath || tokenPath.length === 0) {
return walkingStates;
}
@ -321,12 +126,6 @@ function walkTokenPath(tokenPath, walkingStates, context, editor) {
return walkTokenPath(tokenPath.slice(1), nextWalkingStates, context, editor);
}
export function resolvePathToComponents(tokenPath, context, editor, components) {
const walkStates = walkTokenPath(tokenPath, [new WalkingState('ROOT', components, [])], context, editor);
const result = [].concat.apply([], _.pluck(walkStates, 'components'));
return result;
}
export function populateContext(tokenPath, context, editor, includeAutoComplete, components) {
let walkStates = walkTokenPath(tokenPath, [new WalkingState('ROOT', components, [])], context, editor);

View file

@ -18,17 +18,14 @@
*/
const _ = require('lodash');
const engine = require('./engine');
import { ConstantComponent, ListComponent, SharedComponent } from './components';
export function ParamComponent(name, parent, description) {
engine.ConstantComponent.call(this, name, parent);
this.description = description;
}
ParamComponent.prototype = _.create(engine.ConstantComponent.prototype, { 'constructor': ParamComponent });
(function (cls) {
cls.getTerms = function () {
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';
@ -38,38 +35,33 @@ ParamComponent.prototype = _.create(engine.ConstantComponent.prototype, { 'const
t.insertValue = this.name + '=';
}
return [t];
};
}(ParamComponent.prototype));
export function UrlParams(description, defaults) {
// This is not really a component, just a handy container to make iteration logic simpler
this.rootComponent = new engine.SharedComponent('ROOT');
if (_.isUndefined(defaults)) {
defaults = {
'pretty': '__flag__',
'format': ['json', 'yaml'],
'filter_path': '',
};
}
description = _.clone(description || {});
_.defaults(description, defaults);
_.each(description, function (pDescription, param) {
const component = new ParamComponent(param, this.rootComponent, pDescription);
if (Array.isArray(pDescription)) {
new engine.ListComponent(param, pDescription, component);
}
else if (pDescription === '__flag__') {
new engine.ListComponent(param, ['true', 'false'], component);
}
}, this);
}
(function (cls) {
cls.getTopLevelComponents = function () {
export class UrlParams {
constructor(description, defaults) {
// This is not really a component, just a handy container to make iteration logic simpler
this.rootComponent = new SharedComponent('ROOT');
if (_.isUndefined(defaults)) {
defaults = {
'pretty': '__flag__',
'format': ['json', 'yaml'],
'filter_path': '',
};
}
description = _.clone(description || {});
_.defaults(description, defaults);
_.each(description, function (pDescription, param) {
const component = new ParamComponent(param, this.rootComponent, pDescription);
if (Array.isArray(pDescription)) {
new ListComponent(param, pDescription, component);
}
else if (pDescription === '__flag__') {
new ListComponent(param, ['true', 'false'], component);
}
}, this);
}
getTopLevelComponents() {
return this.rootComponent.next;
};
}(UrlParams.prototype));
}
}

View file

@ -1,141 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
const _ = require('lodash');
const engine = require('./engine');
export const URL_PATH_END_MARKER = '__url_path_end__';
function AcceptEndpointComponent(endpoint, parent) {
engine.SharedComponent.call(this, endpoint.id, parent);
this.endpoint = endpoint;
}
AcceptEndpointComponent.prototype = _.create(engine.SharedComponent.prototype, { 'constructor': AcceptEndpointComponent });
(function (cls) {
cls.match = function (token, context, editor) {
if (token !== URL_PATH_END_MARKER) {
return null;
}
if (this.endpoint.methods && -1 === _.indexOf(this.endpoint.methods, context.method)) {
return null;
}
const r = Object.getPrototypeOf(cls).match.call(this, 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;
}
return r;
};
}(AcceptEndpointComponent.prototype));
/**
* @param parametrizedComponentFactories a dict of the following structure
* that will be used as a fall back for pattern parameters (i.e.: {indices})
* {
* indices: function (part, parent) {
* return new SharedComponent(part, parent)
* }
* }
* @constructor
*/
export function UrlPatternMatcher(parametrizedComponentFactories) {
// This is not really a component, just a handy container to make iteration logic simpler
this.rootComponent = new engine.SharedComponent('ROOT');
this.parametrizedComponentFactories = parametrizedComponentFactories || {};
}
(function (cls) {
cls.addEndpoint = function (pattern, endpoint) {
let c;
let activeComponent = this.rootComponent;
const endpointComponents = endpoint.url_components || {};
const partList = pattern.split('/');
_.each(partList, function (part, partIndex) {
if (part.search(/^{.+}$/) >= 0) {
part = part.substr(1, part.length - 2);
if (activeComponent.getComponent(part)) {
// we already have something for this, reuse
activeComponent = activeComponent.getComponent(part);
return;
}
// a new path, resolve.
if ((c = endpointComponents[part])) {
// endpoint specific. Support list
if (Array.isArray(c)) {
c = new engine.ListComponent(part, c, activeComponent);
}
else if (_.isObject(c) && c.type === 'list') {
c = new engine.ListComponent(part, c.list, activeComponent, c.multiValued, c.allow_non_valid);
}
else {
console.warn('incorrectly configured url component ', part, ' in endpoint', endpoint);
c = new engine.SharedComponent(part);
}
}
else if ((c = this.parametrizedComponentFactories[part])) {
// c is a f
c = c(part, activeComponent);
}
else {
// just accept whatever with not suggestions
c = new engine.SimpleParamComponent(part, activeComponent);
}
activeComponent = c;
}
else {
// not pattern
let lookAhead = part;
let s;
for (partIndex++; partIndex < partList.length; partIndex++) {
s = partList[partIndex];
if (s.indexOf('{') >= 0) {
break;
}
lookAhead += '/' + s;
}
if (activeComponent.getComponent(part)) {
// we already have something for this, reuse
activeComponent = activeComponent.getComponent(part);
activeComponent.addOption(lookAhead);
}
else {
c = new engine.ConstantComponent(part, activeComponent, lookAhead);
activeComponent = c;
}
}
}, this);
// mark end of endpoint path
new AcceptEndpointComponent(endpoint, activeComponent);
};
cls.getTopLevelComponents = function () {
return this.rootComponent.next;
};
}(UrlPatternMatcher.prototype));

View file

@ -16,173 +16,66 @@
* specific language governing permissions and limitations
* under the License.
*/
import {
TypeAutocompleteComponent,
IdAutocompleteComponent,
IndexAutocompleteComponent,
FieldAutocompleteComponent,
ListComponent
} from './autocomplete/components';
const $ = require('jquery');
const _ = require('lodash');
const mappings = require('./mappings');
const Api = require('./kb/api');
const autocompleteEngine = require('./autocomplete/engine');
import $ from 'jquery';
import _ from 'lodash';
import Api from './kb/api';
let ACTIVE_API = new Api();
function nonValidIndexType(token) {
return !(token === '_all' || token[0] !== '_');
}
function IndexAutocompleteComponent(name, parent, multiValued) {
autocompleteEngine.ListComponent.call(this, name, mappings.getIndices, parent, multiValued);
}
IndexAutocompleteComponent.prototype = _.create(
autocompleteEngine.ListComponent.prototype,
{ 'constructor': IndexAutocompleteComponent });
(function (cls) {
cls.validateTokens = function (tokens) {
if (!this.multiValued && tokens.length > 1) {
return false;
}
return !_.find(tokens, nonValidIndexType);
};
cls.getDefaultTermMeta = function () {
return 'index';
};
cls.getContextKey = function () {
return 'indices';
};
}(IndexAutocompleteComponent.prototype));
function TypeGenerator(context) {
return mappings.getTypes(context.indices);
}
function TypeAutocompleteComponent(name, parent, multiValued) {
autocompleteEngine.ListComponent.call(this, name, TypeGenerator, parent, multiValued);
}
TypeAutocompleteComponent.prototype = _.create(
autocompleteEngine.ListComponent.prototype,
{ 'constructor': TypeAutocompleteComponent });
(function (cls) {
cls.validateTokens = function (tokens) {
if (!this.multiValued && tokens.length > 1) {
return false;
}
return !_.find(tokens, nonValidIndexType);
};
cls.getDefaultTermMeta = function () {
return 'type';
};
cls.getContextKey = function () {
return 'types';
};
}(TypeAutocompleteComponent.prototype));
function FieldGenerator(context) {
return _.map(mappings.getFields(context.indices, context.types), function (field) {
return { name: field.name, meta: field.type };
});
}
function FieldAutocompleteComponent(name, parent, multiValued) {
autocompleteEngine.ListComponent.call(this, name, FieldGenerator, parent, multiValued);
}
FieldAutocompleteComponent.prototype = _.create(
autocompleteEngine.ListComponent.prototype,
{ 'constructor': FieldAutocompleteComponent });
(function (cls) {
cls.validateTokens = function (tokens) {
if (!this.multiValued && tokens.length > 1) {
return false;
}
return !_.find(tokens, function (token) {
return token.match(/[^\w.?*]/);
});
};
cls.getDefaultTermMeta = function () {
return 'field';
};
cls.getContextKey = function () {
return 'fields';
};
}(FieldAutocompleteComponent.prototype));
function IdAutocompleteComponent(name, parent, multi) {
autocompleteEngine.SharedComponent.call(this, name, parent);
this.multi_match = multi;
}
IdAutocompleteComponent.prototype = _.create(
autocompleteEngine.SharedComponent.prototype,
{ 'constructor': IdAutocompleteComponent });
(function (cls) {
cls.match = function (token, context, editor) {
if (!token) {
return null;
}
if (!this.multi_match && Array.isArray(token)) {
return null;
}
token = Array.isArray(token) ? token : [token];
if (_.find(token, function (t) {
return t.match(/[\/,]/);
})) {
return null;
}
const r = Object.getPrototypeOf(cls).match.call(this, token, context, editor);
r.context_values = r.context_values || {};
r.context_values.id = token;
return r;
};
}(IdAutocompleteComponent.prototype));
const isNotAnIndexName = name => name[0] === '_' && name !== '_all';
const parametrizedComponentFactories = {
'index': function (name, parent) {
index: function (name, parent) {
if (isNotAnIndexName(name)) return;
return new IndexAutocompleteComponent(name, parent, false);
},
'indices': function (name, parent) {
indices: function (name, parent) {
if (isNotAnIndexName(name)) return;
return new IndexAutocompleteComponent(name, parent, true);
},
'type': function (name, parent) {
type: function (name, parent) {
return new TypeAutocompleteComponent(name, parent, false);
},
'types': function (name, parent) {
types: function (name, parent) {
return new TypeAutocompleteComponent(name, parent, true);
},
'id': function (name, parent) {
id: function (name, parent) {
return new IdAutocompleteComponent(name, parent);
},
'ids': function (name, parent) {
ids: function (name, parent) {
return new IdAutocompleteComponent(name, parent, true);
},
'fields': function (name, parent) {
fields: function (name, parent) {
return new FieldAutocompleteComponent(name, parent, true);
},
'field': function (name, parent) {
field: function (name, parent) {
return new FieldAutocompleteComponent(name, parent, false);
},
'nodes': function (name, parent) {
return new autocompleteEngine.ListComponent(name, ['_local', '_master', 'data:true', 'data:false',
'master:true', 'master:false'], parent);
nodes: function (name, parent) {
return new ListComponent(
name,
[
'_local',
'_master',
'data:true',
'data:false',
'master:true',
'master:false',
],
parent
);
},
node: function (name, parent) {
return new ListComponent(name, [], parent, false);
},
'node': function (name, parent) {
return new autocompleteEngine.ListComponent(name, [], parent, false);
}
};
export function getUnmatchedEndpointComponents() {
@ -201,18 +94,27 @@ export function getEndpointBodyCompleteComponents(endpoint) {
return desc.bodyAutocompleteRootComponents;
}
export function getTopLevelUrlCompleteComponents() {
return ACTIVE_API.getTopLevelUrlCompleteComponents();
export function getTopLevelUrlCompleteComponents(method) {
return ACTIVE_API.getTopLevelUrlCompleteComponents(method);
}
export function getGlobalAutocompleteComponents(term, throwOnMissing) {
return ACTIVE_API.getGlobalAutocompleteComponents(term, throwOnMissing);
}
function loadApisFromJson(json, urlParametrizedComponentFactories, bodyParametrizedComponentFactories) {
urlParametrizedComponentFactories = urlParametrizedComponentFactories || parametrizedComponentFactories;
bodyParametrizedComponentFactories = bodyParametrizedComponentFactories || urlParametrizedComponentFactories;
const api = new Api(urlParametrizedComponentFactories, bodyParametrizedComponentFactories);
function loadApisFromJson(
json,
urlParametrizedComponentFactories,
bodyParametrizedComponentFactories
) {
urlParametrizedComponentFactories =
urlParametrizedComponentFactories || parametrizedComponentFactories;
bodyParametrizedComponentFactories =
bodyParametrizedComponentFactories || urlParametrizedComponentFactories;
const api = new Api(
urlParametrizedComponentFactories,
bodyParametrizedComponentFactories
);
const names = [];
_.each(json, function (apiJson, name) {
names.unshift(name);
@ -230,18 +132,21 @@ function loadApisFromJson(json, urlParametrizedComponentFactories, bodyParametri
export function setActiveApi(api) {
if (_.isString(api)) {
$.ajax({
url: '../api/console/api_server?sense_version=' + encodeURIComponent('@@SENSE_VERSION') + '&apis=' + encodeURIComponent(api),
url:
'../api/console/api_server?sense_version=' +
encodeURIComponent('@@SENSE_VERSION') +
'&apis=' +
encodeURIComponent(api),
dataType: 'json', // disable automatic guessing
}
).then(
}).then(
function (data) {
setActiveApi(loadApisFromJson(data));
},
function (jqXHR) {
console.log('failed to load API \'' + api + '\': ' + jqXHR.responseText);
});
}
);
return;
}
console.log('setting active api to [' + api.name + ']');
@ -251,5 +156,5 @@ export function setActiveApi(api) {
setActiveApi('es_6_0');
export const _test = {
loadApisFromJson: loadApisFromJson
loadApisFromJson: loadApisFromJson,
};

View file

@ -18,7 +18,7 @@
*/
const _ = require('lodash');
import { UrlPatternMatcher } from '../autocomplete/url_pattern_matcher';
import { UrlPatternMatcher } from '../autocomplete/components';
import { UrlParams } from '../autocomplete/url_params';
import { globalsOnlyAutocompleteComponents, compileBodyDescription } from '../autocomplete/body_completer';
@ -78,8 +78,8 @@ function Api(urlParametrizedComponentFactories, bodyParametrizedComponentFactori
};
cls.getTopLevelUrlCompleteComponents = function () {
return this.urlPatternMatcher.getTopLevelComponents();
cls.getTopLevelUrlCompleteComponents = function (method) {
return this.urlPatternMatcher.getTopLevelComponents(method);
};
cls.getUnmatchedEndpointComponents = function () {

View file

@ -50,7 +50,6 @@ describe('Integration', () => {
});
function processContextTest(data, mapping, kbSchemes, requestLine, testToRun) {
test(testToRun.name, async function (done) {
let rowOffset = 0; // add one for the extra method line
let editorValue = data;
@ -70,19 +69,18 @@ describe('Integration', () => {
const json = {};
json[test.name] = kbSchemes || {};
const testApi = kb._test.loadApisFromJson(json);
//if (kbSchemes) {
if (kbSchemes) {
// if (kbSchemes.globals) {
// $.each(kbSchemes.globals, function (parent, rules) {
// testApi.addGlobalAutocompleteRules(parent, rules);
// });
// }
// if (kbSchemes.endpoints) {
// $.each(kbSchemes.endpoints, function (endpoint, scheme) {
// _.defaults(scheme, {methods: null}); // disable method testing unless specified in test
// testApi.addEndpointDescription(endpoint, scheme);
// });
// }
//}
if (kbSchemes.endpoints) {
$.each(kbSchemes.endpoints, function (endpoint, scheme) {
testApi.addEndpointDescription(endpoint, scheme);
});
}
}
kb.setActiveApi(testApi);
const { cursor } = testToRun;
const { row, column } = cursor;
@ -1021,16 +1019,19 @@ describe('Integration', () => {
search_type: ['count', 'query_then_fetch'],
scroll: '10m',
},
methods: ['GET'],
data_autocomplete_rules: {},
},
'_cluster/stats': {
patterns: ['_cluster/stats'],
indices_mode: 'none',
data_autocomplete_rules: {},
methods: ['GET'],
},
'_cluster/nodes/stats': {
patterns: ['_cluster/nodes/stats'],
data_autocomplete_rules: {},
methods: ['GET'],
},
},
};
@ -1131,7 +1132,7 @@ describe('Integration', () => {
contextTests(null, MAPPING, CLUSTER_KB, 'GET cl', [
{
name: 'Endpoints by subpart',
name: 'Endpoints by subpart GET',
cursor: { row: 0, column: 6 },
autoCompleteSet: [
{ name: '_cluster/nodes/stats', meta: 'endpoint' },
@ -1143,20 +1144,15 @@ describe('Integration', () => {
prefixToAdd: '',
suffixToAdd: '',
initialValue: 'cl',
method: 'GET'
},
]);
contextTests(null, MAPPING, CLUSTER_KB, 'POST cl', [
{
name: 'Endpoints by subpart',
name: 'Endpoints by subpart POST',
cursor: { row: 0, column: 7 },
autoCompleteSet: [
{ name: '_cluster/nodes/stats', meta: 'endpoint' },
{ name: '_cluster/stats', meta: 'endpoint' },
{ name: '_search', meta: 'endpoint' },
{ name: 'index1', meta: 'index' },
{ name: 'index2', meta: 'index' },
],
no_context: true,
prefixToAdd: '',
suffixToAdd: '',
initialValue: 'cl',

View file

@ -78,7 +78,7 @@ describe('Knowledge base', () => {
context,
null,
expectedContext.autoCompleteSet,
kb.getTopLevelUrlCompleteComponents()
kb.getTopLevelUrlCompleteComponents('GET')
);
// override context to just check on id

View file

@ -24,8 +24,10 @@ const _ = require('lodash');
import {
URL_PATH_END_MARKER,
UrlPatternMatcher,
} from '../../src/autocomplete/url_pattern_matcher';
import { populateContext, ListComponent } from '../../src/autocomplete/engine';
ListComponent
} from '../../src/autocomplete/components';
import { populateContext } from '../../src/autocomplete/engine';
describe('Url autocomplete', () => {
function patternsTest(
@ -84,7 +86,7 @@ describe('Url autocomplete', () => {
context,
null,
expectedContext.autoCompleteSet,
patternMatcher.getTopLevelComponents()
patternMatcher.getTopLevelComponents(context.method)
);
// override context to just check on id
@ -111,17 +113,19 @@ describe('Url autocomplete', () => {
const endpoints = {
'1': {
patterns: ['a/b'],
methods: ['GET']
},
};
patternsTest('simple single path - completion', endpoints, 'a/b$', {
endpoint: '1',
method: 'GET'
});
patternsTest(
'simple single path - completion, with auto complete',
endpoints,
'a/b',
{ autoCompleteSet: [] }
{ method: 'GET', autoCompleteSet: [] }
);
patternsTest(
@ -135,14 +139,14 @@ describe('Url autocomplete', () => {
'simple single path - partial, with auto complete',
endpoints,
'a',
{ autoCompleteSet: ['b'] }
{ method: 'GET', autoCompleteSet: ['b'] }
);
patternsTest(
'simple single path - partial, with auto complete',
endpoints,
[],
{ autoCompleteSet: ['a/b'] }
{ method: 'GET', autoCompleteSet: ['a/b'] }
);
patternsTest('simple single path - different path', endpoints, 'a/c', {});
@ -152,56 +156,61 @@ describe('Url autocomplete', () => {
const endpoints = {
'1': {
patterns: ['a/b', 'a/b/{p}'],
methods: ['GET']
},
'2': {
patterns: ['a/c'],
methods: ['GET']
},
};
patternsTest('shared path - completion 1', endpoints, 'a/b$', {
endpoint: '1',
method: 'GET'
});
patternsTest('shared path - completion 2', endpoints, 'a/c$', {
endpoint: '2',
method: 'GET'
});
patternsTest(
'shared path - completion 1 with param',
endpoints,
'a/b/v$',
{ endpoint: '1', p: 'v' }
{ method: 'GET', endpoint: '1', p: 'v' }
);
patternsTest('shared path - partial, with auto complete', endpoints, 'a', {
autoCompleteSet: ['b', 'c'],
method: 'GET',
});
patternsTest(
'shared path - partial, with auto complete of param, no options',
endpoints,
'a/b',
{ autoCompleteSet: [] }
{ method: 'GET', autoCompleteSet: [] }
);
patternsTest(
'shared path - partial, without auto complete',
endpoints,
'a',
{}
{ method: 'GET', }
);
patternsTest(
'shared path - different path - with auto complete',
endpoints,
'a/e',
{ autoCompleteSet: [] }
{ method: 'GET', autoCompleteSet: [] }
);
patternsTest(
'shared path - different path - without auto complete',
endpoints,
'a/e',
{}
{ method: 'GET', }
);
}());
@ -212,51 +221,57 @@ describe('Url autocomplete', () => {
url_components: {
p: ['a', 'b'],
},
methods: [ 'GET' ]
},
'2': {
patterns: ['a/c'],
methods: [ 'GET' ]
},
};
patternsTest('option testing - completion 1', endpoints, 'a/a$', {
method: 'GET',
endpoint: '1',
p: ['a'],
});
patternsTest('option testing - completion 2', endpoints, 'a/b$', {
method: 'GET',
endpoint: '1',
p: ['b'],
});
patternsTest('option testing - completion 3', endpoints, 'a/b,a$', {
method: 'GET',
endpoint: '1',
p: ['b', 'a'],
});
patternsTest('option testing - completion 4', endpoints, 'a/c$', {
method: 'GET',
endpoint: '2',
});
patternsTest('option testing - completion 5', endpoints, 'a/d$', {});
patternsTest('option testing - completion 5', endpoints, 'a/d$', { method: 'GET', });
patternsTest(
'option testing - partial, with auto complete',
endpoints,
'a',
{ autoCompleteSet: [t('a', 'p'), t('b', 'p'), 'c'] }
{ method: 'GET', autoCompleteSet: [t('a', 'p'), t('b', 'p'), 'c'] }
);
patternsTest(
'option testing - partial, without auto complete',
endpoints,
'a',
{}
{ method: 'GET', }
);
patternsTest(
'option testing - different path - with auto complete',
endpoints,
'a/e',
{ autoCompleteSet: [] }
{ method: 'GET', autoCompleteSet: [] }
);
}());
@ -267,12 +282,15 @@ describe('Url autocomplete', () => {
url_components: {
p: ['a', 'b'],
},
methods: [ 'GET' ]
},
'2': {
patterns: ['b/{p}'],
methods: [ 'GET' ]
},
'3': {
patterns: ['b/{l}/c'],
methods: [ 'GET' ],
url_components: {
l: {
type: 'list',
@ -292,7 +310,7 @@ describe('Url autocomplete', () => {
'global parameters testing - completion 1',
endpoints,
'a/a$',
{ endpoint: '1', p: ['a'] },
{ method: 'GET', endpoint: '1', p: ['a'] },
globalFactories
);
@ -300,7 +318,7 @@ describe('Url autocomplete', () => {
'global parameters testing - completion 2',
endpoints,
'b/g1$',
{ endpoint: '2', p: ['g1'] },
{ method: 'GET', endpoint: '2', p: ['g1'] },
globalFactories
);
@ -308,7 +326,7 @@ describe('Url autocomplete', () => {
'global parameters testing - partial, with auto complete',
endpoints,
'a',
{ autoCompleteSet: [t('a', 'p'), t('b', 'p')] },
{ method: 'GET', autoCompleteSet: [t('a', 'p'), t('b', 'p')] },
globalFactories
);
@ -317,6 +335,7 @@ describe('Url autocomplete', () => {
endpoints,
'b',
{
method: 'GET',
autoCompleteSet: [
t('g1', 'p'),
t('g2', 'p'),
@ -331,14 +350,14 @@ describe('Url autocomplete', () => {
'Non valid token acceptance - partial, with auto complete 1',
endpoints,
'b/la',
{ autoCompleteSet: ['c'], l: ['la'] },
{ method: 'GET', autoCompleteSet: ['c'], l: ['la'] },
globalFactories
);
patternsTest(
'Non valid token acceptance - partial, with auto complete 2',
endpoints,
'b/non_valid',
{ autoCompleteSet: ['c'], l: ['non_valid'] },
{ method: 'GET', autoCompleteSet: ['c'], l: ['non_valid'] },
globalFactories
);
}());
@ -347,13 +366,16 @@ describe('Url autocomplete', () => {
const endpoints = {
'1': {
patterns: ['a/b/{p}/c/e'],
methods: [ 'GET' ]
},
};
patternsTest('look ahead - autocomplete before param 1', endpoints, 'a', {
method: 'GET',
autoCompleteSet: ['b'],
});
patternsTest('look ahead - autocomplete before param 2', endpoints, [], {
method: 'GET',
autoCompleteSet: ['a/b'],
});
@ -361,14 +383,14 @@ describe('Url autocomplete', () => {
'look ahead - autocomplete after param 1',
endpoints,
'a/b/v',
{ autoCompleteSet: ['c/e'], p: 'v' }
{ method: 'GET', autoCompleteSet: ['c/e'], p: 'v' }
);
patternsTest(
'look ahead - autocomplete after param 2',
endpoints,
'a/b/v/c',
{ autoCompleteSet: ['e'], p: 'v' }
{ method: 'GET', autoCompleteSet: ['e'], p: 'v' }
);
}());

View file

@ -1,10 +1,10 @@
{
"{index_name}/_xpack/graph/_explore": {
"{index}/_xpack/graph/_explore": {
"methods": [
"POST"
],
"patterns": [
"{index_name}/_xpack/graph/_explore"
"{index}/_xpack/graph/_explore"
],
"data_autocomplete_rules": {
"query": {},

View file

@ -5,10 +5,10 @@
],
"patterns": [
"_xpack/migration/assistance",
"_xpack/migration/assistance/{index_name}"
"_xpack/migration/assistance/{index}"
]
},
"_xpack/migration/upgrade/{index_name}": {
"_xpack/migration/upgrade/{index}": {
"url_params": {
"wait_for_completion": ["true", "false"]
},
@ -16,7 +16,7 @@
"POST"
],
"patterns": [
"_xpack/migration/upgrade/{index_name}"
"_xpack/migration/upgrade/{index}"
]
},
"_xpack/migration/deprecations": {
@ -25,7 +25,7 @@
],
"patterns": [
"_xpack/migration/deprecations",
"{index_name}/_xpack/migration/deprecations"
"{index}/_xpack/migration/deprecations"
]
}
}

View file

@ -55,17 +55,17 @@
"POST"
]
},
"_xpack/rollup/data/{index_name}": {
"_xpack/rollup/data/{index}": {
"patterns": [
"_xpack/rollup/data/{index_name}"
"_xpack/rollup/data/{index}"
],
"methods": [
"GET"
]
},
"{index_name}/_rollup_search": {
"{index}/_rollup_search": {
"patterns": [
"{index_name}/_rollup_search"
"{index}/_rollup_search"
],
"methods": [
"GET"