Skip to content

Commit

Permalink
[Canvas] Improve expression autocomplete (#52035)
Browse files Browse the repository at this point in the history
* [Canvas] Autocomplete improvements

Co-authored-by: Elastic Machine <[email protected]>
Co-authored-by: Poff Poffenberger <[email protected]>
  • Loading branch information
3 people committed Jan 24, 2020
1 parent 7165b26 commit 25765a9
Show file tree
Hide file tree
Showing 4 changed files with 284 additions and 171 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@

// @ts-ignore Untyped Library
import { Fn } from '@kbn/interpreter/common';
import { uniq } from 'lodash';
import { functions as browserFns } from '../../canvas_plugin_src/functions/browser';
import { functions as commonFns } from '../../canvas_plugin_src/functions/common';
import { functions as serverFns } from '../../canvas_plugin_src/functions/server';

export const functionSpecs = [...browserFns, ...commonFns, ...serverFns].map(fn => new Fn(fn()));
export const functionSpecs = uniq([...browserFns, ...commonFns, ...serverFns], 'name').map(fn => new Fn(fn()));
104 changes: 52 additions & 52 deletions x-pack/legacy/plugins/canvas/common/lib/autocomplete.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
jest.mock('ui/new_platform');
import { functionSpecs } from '../../__tests__/fixtures/function_specs';

import { getAutocompleteSuggestions, getFnArgDefAtPosition } from './autocomplete';
import {
FunctionSuggestion,
getAutocompleteSuggestions,
getFnArgDefAtPosition,
} from './autocomplete';

describe('autocomplete', () => {
describe('getFnArgDefAtPosition', () => {
Expand All @@ -27,15 +31,6 @@ describe('autocomplete', () => {
expect(suggestions[0].end).toBe(0);
});

it('should suggest functions filtered by text', () => {
const expression = 'pl';
const suggestions = getAutocompleteSuggestions(functionSpecs, expression, 0);
const nonmatching = suggestions.map(s => s.text).filter(text => !text.includes(expression));
expect(nonmatching.length).toBe(0);
expect(suggestions[0].start).toBe(0);
expect(suggestions[0].end).toBe(expression.length);
});

it('should suggest arguments', () => {
const expression = 'plot ';
const suggestions = getAutocompleteSuggestions(functionSpecs, expression, expression.length);
Expand All @@ -45,16 +40,6 @@ describe('autocomplete', () => {
expect(suggestions[0].end).toBe(expression.length);
});

it('should suggest arguments filtered by text', () => {
const expression = 'plot axis';
const suggestions = getAutocompleteSuggestions(functionSpecs, expression, expression.length);
const plotFn = functionSpecs.find(spec => spec.name === 'plot');
const matchingArgs = Object.keys(plotFn.args).filter(key => key.includes('axis'));
expect(suggestions.length).toBe(matchingArgs.length);
expect(suggestions[0].start).toBe('plot '.length);
expect(suggestions[0].end).toBe('plot axis'.length);
});

it('should suggest values', () => {
const expression = 'shape shape=';
const suggestions = getAutocompleteSuggestions(functionSpecs, expression, expression.length);
Expand All @@ -64,16 +49,6 @@ describe('autocomplete', () => {
expect(suggestions[0].end).toBe(expression.length);
});

it('should suggest values filtered by text', () => {
const expression = 'shape shape=ar';
const suggestions = getAutocompleteSuggestions(functionSpecs, expression, expression.length);
const shapeFn = functionSpecs.find(spec => spec.name === 'shape');
const matchingValues = shapeFn.args.shape.options.filter((key: string) => key.includes('ar'));
expect(suggestions.length).toBe(matchingValues.length);
expect(suggestions[0].start).toBe(expression.length - 'ar'.length);
expect(suggestions[0].end).toBe(expression.length);
});

it('should suggest functions inside an expression', () => {
const expression = 'if {}';
const suggestions = getAutocompleteSuggestions(
Expand All @@ -86,6 +61,52 @@ describe('autocomplete', () => {
expect(suggestions[0].end).toBe(expression.length - 1);
});

it('should rank functions inside an expression by their return type first', () => {
const expression = 'plot defaultStyle={}';
const suggestions = getAutocompleteSuggestions(
functionSpecs,
expression,
expression.length - 1
) as FunctionSuggestion[];
expect(suggestions.length).toBe(functionSpecs.length);
expect(suggestions[0].fnDef.name).toBe('seriesStyle');
});

it('should rank functions inside an expression with matching return types and contexts before just return type', () => {
const expression = 'staticColumn "hello" | ply expression={}';
const suggestions = getAutocompleteSuggestions(
functionSpecs,
expression,
expression.length - 1
) as FunctionSuggestion[];
expect(suggestions.length).toBe(functionSpecs.length);

expect(suggestions[0].fnDef.type).toBe('datatable');
expect(suggestions[0].fnDef.context && suggestions[0].fnDef.context.types).toEqual([
'datatable',
]);

const withReturnOnly = suggestions.findIndex(
suggestion =>
suggestion.fnDef.type === 'datatable' &&
suggestion.fnDef.context &&
suggestion.fnDef.context.types &&
!(suggestion.fnDef.context.types as string[]).includes('datatable')
);

const withNeither = suggestions.findIndex(
suggestion =>
suggestion.fnDef.type !== 'datatable' &&
(!suggestion.fnDef.context ||
!(suggestion.fnDef.context.types as string[]).includes('datatable'))
);

expect(suggestions[0].fnDef.type).toBe('datatable');
expect(suggestions[0].fnDef.context?.types).toEqual(['datatable']);

expect(withReturnOnly).toBeLessThan(withNeither);
});

it('should suggest arguments inside an expression', () => {
const expression = 'if {lt }';
const suggestions = getAutocompleteSuggestions(
Expand Down Expand Up @@ -120,22 +141,11 @@ describe('autocomplete', () => {
expression.length - 1
);
const shapeFn = functionSpecs.find(spec => spec.name === 'shape');
const matchingValues = shapeFn.args.shape.options.filter((key: string) => key.includes('ar'));
expect(suggestions.length).toBe(matchingValues.length);
expect(suggestions.length).toBe(shapeFn.args.shape.options.length);
expect(suggestions[0].start).toBe(expression.length - '"ar"'.length);
expect(suggestions[0].end).toBe(expression.length);
});

it('should prioritize functions that start with text', () => {
const expression = 't';
const suggestions = getAutocompleteSuggestions(functionSpecs, expression, expression.length);
const tableIndex = suggestions.findIndex(suggestion => suggestion.text.includes('table'));
const alterColumnIndex = suggestions.findIndex(suggestion =>
suggestion.text.includes('alterColumn')
);
expect(tableIndex).toBeLessThan(alterColumnIndex);
});

it('should prioritize functions that match the previous function type', () => {
const expression = 'plot | ';
const suggestions = getAutocompleteSuggestions(functionSpecs, expression, expression.length);
Expand All @@ -152,16 +162,6 @@ describe('autocomplete', () => {
expect(anyIndex).toBeLessThan(metricIndex);
});

it('should prioritize arguments that start with text', () => {
const expression = 'plot y';
const suggestions = getAutocompleteSuggestions(functionSpecs, expression, expression.length);
const yaxisIndex = suggestions.findIndex(suggestion => suggestion.text.includes('yaxis'));
const defaultStyleIndex = suggestions.findIndex(suggestion =>
suggestion.text.includes('defaultStyle')
);
expect(yaxisIndex).toBeLessThan(defaultStyleIndex);
});

it('should prioritize unnamed arguments', () => {
const expression = 'case ';
const suggestions = getAutocompleteSuggestions(functionSpecs, expression, expression.length);
Expand Down
Loading

0 comments on commit 25765a9

Please sign in to comment.