Skip to content

Commit

Permalink
[Console] Fix condition auto-completion for templates (#126881) (#130972
Browse files Browse the repository at this point in the history
)

* Fix condition autocompletion for templates

* Added block level matching logic

* Fix lint

* Fixed types

* Resolved comments

* Added a custom type guard

* Minor refactor

* Add type to type imports

* Add functional tests and comments

Co-authored-by: Muhammad Ibragimov <[email protected]>
Co-authored-by: Kibana Machine <[email protected]>
(cherry picked from commit 6447923)
  • Loading branch information
mibragimov authored Apr 26, 2022
1 parent 1b77f75 commit 4dd7ba0
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 6 deletions.
78 changes: 74 additions & 4 deletions src/plugins/console/public/lib/autocomplete/autocomplete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,22 @@ import { i18n } from '@kbn/i18n';

// TODO: All of these imports need to be moved to the core editor so that it can inject components from there.
import {
getTopLevelUrlCompleteComponents,
getEndpointBodyCompleteComponents,
getGlobalAutocompleteComponents,
getTopLevelUrlCompleteComponents,
getUnmatchedEndpointComponents,
// @ts-ignore
} from '../kb/kb';

import { createTokenIterator } from '../../application/factories';
import { Position, Token, Range, CoreEditor } from '../../types';
import type { CoreEditor, Position, Range, Token } from '../../types';
import type RowParser from '../row_parser';

import * as utils from '../utils';

// @ts-ignore
import { populateContext } from './engine';
import { AutoCompleteContext, ResultTerm } from './types';
import type { AutoCompleteContext, DataAutoCompleteRulesOneOf, ResultTerm } from './types';
// @ts-ignore
import { URL_PATH_END_MARKER } from './components/index';

Expand Down Expand Up @@ -349,14 +349,84 @@ export default function ({
});
}

/**
* Get a different set of templates based on the value configured in the request.
* For example, when creating a snapshot repository of different types (`fs`, `url` etc),
* different properties are inserted in the textarea based on the type.
* E.g. https://github.com/elastic/kibana/blob/main/src/plugins/console/server/lib/spec_definitions/json/overrides/snapshot.create_repository.json
*/
function getConditionalTemplate(
name: string,
autocompleteRules: Record<string, unknown> | null | undefined
) {
const obj = autocompleteRules && autocompleteRules[name];

if (obj) {
const currentLineNumber = editor.getCurrentPosition().lineNumber;

if (hasOneOfIn(obj)) {
// Get the line number of value that should provide different templates based on that
const startLine = getStartLineNumber(currentLineNumber, obj.__one_of);
// Join line values from start to current line
const lines = editor.getLines(startLine, currentLineNumber).join('\n');
// Get the correct template by comparing the autocomplete rules against the lines
const prop = getProperty(lines, obj.__one_of);
if (prop && prop.__template) {
return prop.__template;
}
}
}
}

/**
* Check if object has a property of '__one_of'
*/
function hasOneOfIn(value: unknown): value is { __one_of: DataAutoCompleteRulesOneOf[] } {
return typeof value === 'object' && value !== null && '__one_of' in value;
}

/**
* Get the start line of value that matches the autocomplete rules condition
*/
function getStartLineNumber(currentLine: number, rules: DataAutoCompleteRulesOneOf[]): number {
if (currentLine === 1) {
return currentLine;
}
const value = editor.getLineValue(currentLine);
const prop = getProperty(value, rules);
if (prop) {
return currentLine;
}
return getStartLineNumber(currentLine - 1, rules);
}

/**
* Get the matching property based on the given condition
*/
function getProperty(condition: string, rules: DataAutoCompleteRulesOneOf[]) {
return rules.find((rule) => {
if (rule.__condition && rule.__condition.lines_regex) {
return new RegExp(rule.__condition.lines_regex, 'm').test(condition);
}
return false;
});
}

function applyTerm(term: {
value?: string;
context?: AutoCompleteContext;
template?: { __raw: boolean; value: string };
template?: { __raw?: boolean; value?: string; [key: string]: unknown };
insertValue?: string;
}) {
const context = term.context!;

if (context?.endpoint && term.value) {
const { data_autocomplete_rules: autocompleteRules } = context.endpoint;
const template = getConditionalTemplate(term.value, autocompleteRules);
if (template) {
term.template = template;
}
}
// make sure we get up to date replacement info.
addReplacementInfoToContext(context, editor.getCurrentPosition(), term.insertValue);

Expand Down
9 changes: 9 additions & 0 deletions src/plugins/console/public/lib/autocomplete/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ export interface ResultTerm {
value?: string;
}

export interface DataAutoCompleteRulesOneOf {
__condition?: {
lines_regex: string;
};
__template: Record<string, unknown>;
[key: string]: unknown;
}

export interface AutoCompleteContext {
autoCompleteSet?: null | ResultTerm[];
endpoint?: null | {
Expand All @@ -24,6 +32,7 @@ export interface AutoCompleteContext {
bodyAutocompleteRootComponents: unknown;
id?: string;
documentation?: string;
data_autocomplete_rules?: Record<string, unknown> | null;
};
urlPath?: null | unknown;
urlParamsTokenPath?: Array<Record<string, string>> | null;
Expand Down
44 changes: 44 additions & 0 deletions test/functional/apps/console/_autocomplete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
*/

import expect from '@kbn/expect';
import { asyncForEach } from '@kbn/std';
import { FtrProviderContext } from '../../ftr_provider_context';

export default function ({ getService, getPageObjects }: FtrProviderContext) {
const log = getService('log');
const retry = getService('retry');
const PageObjects = getPageObjects(['common', 'console']);

describe('console autocomplete feature', function describeIndexTests() {
Expand Down Expand Up @@ -62,5 +64,47 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(lastChar).to.be.eql(',');
});
});

describe('with conditional templates', async () => {
const CONDITIONAL_TEMPLATES = [
{
type: 'fs',
template: `"location": "path"`,
},
{
type: 'url',
template: `"url": ""`,
},
{ type: 's3', template: `"bucket": ""` },
{
type: 'azure',
template: `"path": ""`,
},
];

beforeEach(async () => {
await PageObjects.console.clearTextArea();
await PageObjects.console.enterRequest('\n POST _snapshot/test_repo');
});

await asyncForEach(CONDITIONAL_TEMPLATES, async ({ type, template }) => {
it('should insert different templates depending on the value of type', async () => {
await PageObjects.console.enterText(`{\n\t"type": "${type}"`);
await PageObjects.console.pressEnter();
// Prompt autocomplete for 'settings'
await PageObjects.console.promptAutocomplete('s');

await retry.waitFor('autocomplete to be visible', () =>
PageObjects.console.isAutocompleteVisible()
);
await PageObjects.console.pressEnter();
await retry.try(async () => {
const request = await PageObjects.console.getRequest();
log.debug(request);
expect(request).to.contain(`${template}`);
});
});
});
});
});
}
5 changes: 3 additions & 2 deletions test/functional/page_objects/console_page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,11 @@ export class ConsolePageObject extends FtrService {
}
}

public async promptAutocomplete() {
// Prompt autocomplete window and provide a initial letter of properties to narrow down the results. E.g. 'b' = 'bool'
public async promptAutocomplete(letter = 'b') {
const textArea = await this.testSubjects.find('console-textarea');
await textArea.clickMouseButton();
await textArea.type('b');
await textArea.type(letter);
await this.retry.waitFor('autocomplete to be visible', () => this.isAutocompleteVisible());
}

Expand Down

0 comments on commit 4dd7ba0

Please sign in to comment.