Skip to content

Commit

Permalink
[8.x] [ES|QL] Separate ENRICH autocomplete routine (#211657) (#212979)
Browse files Browse the repository at this point in the history
# Backport

This will backport the following commits from `main` to `8.x`:
- [[ES|QL] Separate `ENRICH` autocomplete routine
(#211657)](#211657)

<!--- Backport version: 9.6.6 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sorenlouv/backport)

<!--BACKPORT [{"author":{"name":"Drew
Tate","email":"drew.tate@elastic.co"},"sourceCommit":{"committedDate":"2025-03-03T20:24:23Z","message":"[ES|QL]
Separate `ENRICH` autocomplete routine (#211657)\n\n## Summary\n\nPart
of https://github.com/elastic/kibana/issues/195418\n\nGives `ENRICH`
autocomplete logic its own home 🏡\n\n### Checklist\n\n- [x] [Unit or
functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere
updated or added to match the most common scenarios\n\n### Identify
risks\n\n- [ ] As with any refactor, there's a possibility this will
introduce a\nregression in the behavior of commands. However, all
automated tests are\npassing and I have tested the behavior manually and
can detect no\nregression.\n\n---------\n\nCo-authored-by: Stratoula
Kalafateli
<efstratia.kalafateli@elastic.co>","sha":"f2a91732d8f8d20a22bf761bfe9ec85e8a8e1c0c","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","Feature:ES|QL","Team:ESQL","backport:version","v9.1.0","v8.19.0"],"title":"[ES|QL]
Separate `ENRICH` autocomplete
routine","number":211657,"url":"https://github.com/elastic/kibana/pull/211657","mergeCommit":{"message":"[ES|QL]
Separate `ENRICH` autocomplete routine (#211657)\n\n## Summary\n\nPart
of https://github.com/elastic/kibana/issues/195418\n\nGives `ENRICH`
autocomplete logic its own home 🏡\n\n### Checklist\n\n- [x] [Unit or
functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere
updated or added to match the most common scenarios\n\n### Identify
risks\n\n- [ ] As with any refactor, there's a possibility this will
introduce a\nregression in the behavior of commands. However, all
automated tests are\npassing and I have tested the behavior manually and
can detect no\nregression.\n\n---------\n\nCo-authored-by: Stratoula
Kalafateli
<efstratia.kalafateli@elastic.co>","sha":"f2a91732d8f8d20a22bf761bfe9ec85e8a8e1c0c"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/211657","number":211657,"mergeCommit":{"message":"[ES|QL]
Separate `ENRICH` autocomplete routine (#211657)\n\n## Summary\n\nPart
of https://github.com/elastic/kibana/issues/195418\n\nGives `ENRICH`
autocomplete logic its own home 🏡\n\n### Checklist\n\n- [x] [Unit or
functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere
updated or added to match the most common scenarios\n\n### Identify
risks\n\n- [ ] As with any refactor, there's a possibility this will
introduce a\nregression in the behavior of commands. However, all
automated tests are\npassing and I have tested the behavior manually and
can detect no\nregression.\n\n---------\n\nCo-authored-by: Stratoula
Kalafateli
<efstratia.kalafateli@elastic.co>","sha":"f2a91732d8f8d20a22bf761bfe9ec85e8a8e1c0c"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Drew Tate <drew.tate@elastic.co>
  • Loading branch information
kibanamachine and drewdaemon authored Mar 3, 2025
1 parent 6d8f8a2 commit 088e356
Show file tree
Hide file tree
Showing 13 changed files with 549 additions and 376 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { camelCase } from 'lodash';
import { getFieldNamesByType, getPolicyFields, policies, setup } from './helpers';

describe('autocomplete.suggest', () => {
describe('ENRICH', () => {
const modes = ['any', 'coordinator', 'remote'];
const expectedPolicyNameSuggestions = policies
.map(({ name, suggestedAs }) => suggestedAs || name)
.map((name) => `${name} `);

let assertSuggestions: Awaited<ReturnType<typeof setup>>['assertSuggestions'];
beforeEach(async () => {
const setupResult = await setup();
assertSuggestions = setupResult.assertSuggestions;
});

it('suggests policy names', async () => {
await assertSuggestions(`from a | enrich /`, expectedPolicyNameSuggestions);
await assertSuggestions(`from a | enrich po/`, expectedPolicyNameSuggestions);
});

test('modes', async () => {
await assertSuggestions(
`from a | enrich _/`,
modes.map((mode) => `_${mode}:$0`),
{ triggerCharacter: '_' }
);
await assertSuggestions('from a | enrich _any: /', []);
for (const mode of modes) {
await assertSuggestions(`from a | enrich _${mode}:/`, expectedPolicyNameSuggestions, {
triggerCharacter: ':',
});

await assertSuggestions(
`from a | enrich _${mode.toUpperCase()}:/`,
expectedPolicyNameSuggestions,
{ triggerCharacter: ':' }
);

await assertSuggestions(
`from a | enrich _${camelCase(mode)}:/`,
expectedPolicyNameSuggestions,
{ triggerCharacter: ':' }
);
}
});

it('suggests ON and WITH after policy name', async () => {
await assertSuggestions(`from a | enrich policy /`, ['ON ', 'WITH ', '| ']);
await assertSuggestions(`from a | enrich policy O/`, ['ON ', 'WITH ', '| ']);
});

it('suggests fields after ON', async () => {
await assertSuggestions(
`from a | enrich policy on /`,
getFieldNamesByType('any').map((v) => `${v} `)
);
await assertSuggestions(
`from a | enrich policy on fi/`,
getFieldNamesByType('any').map((v) => `${v} `)
);
});

describe('WITH', () => {
it('suggests WITH after ON <field>', async () => {
await assertSuggestions(`from a | enrich policy on field /`, ['WITH ', '| ']);
});

it('suggests fields for new WITH clauses', async () => {
await assertSuggestions(`from a | enrich policy on field with /`, [
'var0 = ',
...getPolicyFields('policy').map((name) => ({
text: name,
// Makes sure the suggestion menu isn't opened when a field is accepted
command: undefined,
})),
]);
await assertSuggestions(`from a | enrich policy on field with fi/`, [
'var0 = ',
...getPolicyFields('policy'),
]);
await assertSuggestions(`from a | enrich policy on b with var0 = otherField, /`, [
'var1 = ',
...getPolicyFields('policy'),
]);
await assertSuggestions(`from a | enrich policy on b with var0 = otherField, fi/`, [
'var1 = ',
...getPolicyFields('policy'),
]);
});

test('waits to suggest fields until space', async () => {
await assertSuggestions(`from a | enrich policy on b with var0 = otherField,/`, []);
await assertSuggestions(`from a | enrich policy on b with/`, []);
});

test('after first word', async () => {
// not a recognized column name
await assertSuggestions(`from a | enrich policy on b with var0 /`, ['= $0']);
// recognized column name
await assertSuggestions(`from a | enrich policy on b with otherField /`, [',', '| ']);
});

test('suggests enrich fields after open assignment', async () => {
await assertSuggestions(`from a | enrich policy on b with var0 = /`, [
...getPolicyFields('policy'),
]);
await assertSuggestions(`from a | enrich policy on b with var0 = fi/`, [
...getPolicyFields('policy'),
]);
await assertSuggestions(`from a | enrich policy on b with var0 = otherField, var1 = /`, [
...getPolicyFields('policy'),
]);
});

test('after complete clause', async () => {
// works with escaped field names
await assertSuggestions(`from a | enrich policy on b with var0 = \`otherField\` /`, [
',',
'| ',
]);
await assertSuggestions(`from a | enrich policy on b with var0=otherField /`, [',', '| ']);
await assertSuggestions(`from a | enrich policy on b with otherField /`, [',', '| ']);
});

test('after user-defined column name', async () => {
await assertSuggestions(`from a | enrich policy on b with var0 = otherField, var1 /`, [
'= $0',
]);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { scalarFunctionDefinitions } from '../definitions/generated/scalar_funct
import { timeUnitsToSuggest } from '../definitions/literals';
import { commandDefinitions as unmodifiedCommandDefinitions } from '../definitions/commands';
import { getSafeInsertText, TIME_SYSTEM_PARAMS, TRIGGER_SUGGESTION_COMMAND } from './factories';
import { camelCase } from 'lodash';
import { getAstAndSyntaxErrors } from '@kbn/esql-ast';
import {
policies,
Expand Down Expand Up @@ -206,84 +205,6 @@ describe('autocomplete', () => {
});
}

describe('enrich', () => {
const modes = ['any', 'coordinator', 'remote'];
const expectedPolicyNameSuggestions = policies
.map(({ name, suggestedAs }) => suggestedAs || name)
.map((name) => `${name} `);
for (const prevCommand of [
'',
// '| enrich other-policy ',
// '| enrich other-policy on b ',
// '| enrich other-policy with c ',
]) {
testSuggestions(`from a ${prevCommand}| enrich /`, expectedPolicyNameSuggestions);
testSuggestions(
`from a ${prevCommand}| enrich _/`,
modes.map((mode) => `_${mode}:$0`),
'_'
);
for (const mode of modes) {
testSuggestions(
`from a ${prevCommand}| enrich _${mode}:/`,
expectedPolicyNameSuggestions,
':'
);
testSuggestions(
`from a ${prevCommand}| enrich _${mode.toUpperCase()}:/`,
expectedPolicyNameSuggestions,
':'
);
testSuggestions(
`from a ${prevCommand}| enrich _${camelCase(mode)}:/`,
expectedPolicyNameSuggestions,
':'
);
}
testSuggestions(`from a ${prevCommand}| enrich policy /`, ['ON $0', 'WITH $0', '| ']);
testSuggestions(
`from a ${prevCommand}| enrich policy on /`,
getFieldNamesByType('any').map((v) => `${v} `)
);
testSuggestions(`from a ${prevCommand}| enrich policy on b /`, ['WITH $0', '| ']);
testSuggestions(
`from a ${prevCommand}| enrich policy on b with /`,
['var0 = ', ...getPolicyFields('policy')],
' '
);
testSuggestions(`from a ${prevCommand}| enrich policy on b with var0 /`, ['= $0', ',', '| ']);
testSuggestions(`from a ${prevCommand}| enrich policy on b with var0 = /`, [
...getPolicyFields('policy'),
]);
testSuggestions(`from a ${prevCommand}| enrich policy on b with var0 = keywordField /`, [
',',
'| ',
]);
testSuggestions(`from a ${prevCommand}| enrich policy on b with var0 = keywordField, /`, [
'var1 = ',
...getPolicyFields('policy'),
]);
testSuggestions(
`from a ${prevCommand}| enrich policy on b with var0 = keywordField, var1 /`,
['= $0', ',', '| ']
);
testSuggestions(
`from a ${prevCommand}| enrich policy on b with var0 = keywordField, var1 = /`,
[...getPolicyFields('policy')]
);
testSuggestions(
`from a ${prevCommand}| enrich policy with /`,
['var0 = ', ...getPolicyFields('policy')],
' '
);
testSuggestions(`from a ${prevCommand}| enrich policy with keywordField /`, [
'= $0',
',',
'| ',
]);
}
});

// @TODO: get updated eval block from main
describe('values suggestions', () => {
testSuggestions('FROM "i/"', []);
Expand Down Expand Up @@ -449,7 +370,7 @@ describe('autocomplete', () => {
);

// ENRICH policy ON
testSuggestions('FROM index1 | ENRICH policy O/', ['ON $0', 'WITH $0', '| ']);
testSuggestions('FROM index1 | ENRICH policy O/', ['ON ', 'WITH ', '| ']);

// ENRICH policy ON field
testSuggestions(
Expand Down Expand Up @@ -816,10 +737,7 @@ describe('autocomplete', () => {
.map(attachTriggerCommand)
.map((s) => ({ ...s, rangeToReplace: { start: 17, end: 20 } }))
);
testSuggestions(
'FROM a | ENRICH policy /',
['ON $0', 'WITH $0', '| '].map(attachTriggerCommand)
);
testSuggestions('FROM a | ENRICH policy /', ['ON ', 'WITH ', '| '].map(attachTriggerCommand));

testSuggestions(
'FROM a | ENRICH policy ON /',
Expand All @@ -829,12 +747,12 @@ describe('autocomplete', () => {
);
testSuggestions(
'FROM a | ENRICH policy ON @timestamp /',
['WITH $0', '| '].map(attachTriggerCommand)
['WITH ', '| '].map(attachTriggerCommand)
);
// nothing fancy with this field list
testSuggestions('FROM a | ENRICH policy ON @timestamp WITH /', [
'var0 = ',
...getPolicyFields('policy').map((name) => ({ text: name, command: undefined })),
...getPolicyFields('policy'),
]);
describe('replacement range', () => {
testSuggestions('FROM a | ENRICH policy ON @timestamp WITH othe/', [
Expand Down
Loading

0 comments on commit 088e356

Please sign in to comment.