From 597b7b0df0e048f8c123411c7e4baee1302fd031 Mon Sep 17 00:00:00 2001 From: Stratoula Date: Wed, 17 Dec 2025 09:15:56 +0100 Subject: [PATCH 01/60] [KQL] Create different plugin --- .../shared/kbn-kql-language/README.md | 1 + .../packages/shared/kbn-kql-language/index.ts | 8 ++++ .../shared/kbn-kql-language/jest.config.js | 14 ++++++ .../shared/kbn-kql-language/kibana.jsonc | 9 ++++ .../packages/shared/kbn-kql-language/moon.yml | 46 +++++++++++++++++++ .../shared/kbn-kql-language/package.json | 7 +++ .../shared/kbn-kql-language/tsconfig.json | 20 ++++++++ .../plugins/shared/esql/public/plugin.ts | 2 + 8 files changed, 107 insertions(+) create mode 100644 src/platform/packages/shared/kbn-kql-language/README.md create mode 100644 src/platform/packages/shared/kbn-kql-language/index.ts create mode 100644 src/platform/packages/shared/kbn-kql-language/jest.config.js create mode 100644 src/platform/packages/shared/kbn-kql-language/kibana.jsonc create mode 100644 src/platform/packages/shared/kbn-kql-language/moon.yml create mode 100644 src/platform/packages/shared/kbn-kql-language/package.json create mode 100644 src/platform/packages/shared/kbn-kql-language/tsconfig.json diff --git a/src/platform/packages/shared/kbn-kql-language/README.md b/src/platform/packages/shared/kbn-kql-language/README.md new file mode 100644 index 0000000000000..70025ceb566ed --- /dev/null +++ b/src/platform/packages/shared/kbn-kql-language/README.md @@ -0,0 +1 @@ +# KQL diff --git a/src/platform/packages/shared/kbn-kql-language/index.ts b/src/platform/packages/shared/kbn-kql-language/index.ts new file mode 100644 index 0000000000000..ad21076a9ca93 --- /dev/null +++ b/src/platform/packages/shared/kbn-kql-language/index.ts @@ -0,0 +1,8 @@ +/* + * 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". + */ diff --git a/src/platform/packages/shared/kbn-kql-language/jest.config.js b/src/platform/packages/shared/kbn-kql-language/jest.config.js new file mode 100644 index 0000000000000..9e6c2c599685b --- /dev/null +++ b/src/platform/packages/shared/kbn-kql-language/jest.config.js @@ -0,0 +1,14 @@ +/* + * 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". + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../../../..', + roots: ['/src/platform/packages/shared/kbn-kql-language'], +}; diff --git a/src/platform/packages/shared/kbn-kql-language/kibana.jsonc b/src/platform/packages/shared/kbn-kql-language/kibana.jsonc new file mode 100644 index 0000000000000..f218fc54d89a4 --- /dev/null +++ b/src/platform/packages/shared/kbn-kql-language/kibana.jsonc @@ -0,0 +1,9 @@ +{ + "type": "shared-common", + "id": "@kbn/kql-language", + "owner": [ + "@elastic/kibana-presentation" + ], + "group": "platform", + "visibility": "shared" +} \ No newline at end of file diff --git a/src/platform/packages/shared/kbn-kql-language/moon.yml b/src/platform/packages/shared/kbn-kql-language/moon.yml new file mode 100644 index 0000000000000..62f652bdc7734 --- /dev/null +++ b/src/platform/packages/shared/kbn-kql-language/moon.yml @@ -0,0 +1,46 @@ +# This file is generated by the @kbn/moon package. Any manual edits will be erased! +# To extend this, write your extensions/overrides to 'moon.extend.yml' +# then regenerate this file with: 'node scripts/regenerate_moon_projects.js --update --filter @kbn/kql-language' + +$schema: https://moonrepo.dev/schemas/project.json +id: '@kbn/kql-language' +type: unknown +owners: + defaultOwner: '@elastic/kibana-presentation' +toolchain: + default: node +language: typescript +project: + name: '@kbn/kql-language' + description: Moon project for @kbn/kql-language + channel: '' + owner: '@elastic/kibana-presentation' + metadata: + sourceRoot: src/platform/packages/shared/kbn-kql-language +dependsOn: + - '@kbn/i18n' +tags: + - shared-common + - package + - prod + - group-platform + - shared + - jest-unit-tests +fileGroups: + src: + - src/**/* + - '**/*.ts' + - '!target/**/*' +tasks: + jest: + args: + - '--config' + - $projectRoot/jest.config.js + inputs: + - '@group(src)' + jestCI: + args: + - '--config' + - $projectRoot/jest.config.js + inputs: + - '@group(src)' diff --git a/src/platform/packages/shared/kbn-kql-language/package.json b/src/platform/packages/shared/kbn-kql-language/package.json new file mode 100644 index 0000000000000..90b9baf232e15 --- /dev/null +++ b/src/platform/packages/shared/kbn-kql-language/package.json @@ -0,0 +1,7 @@ +{ + "name": "@kbn/kql-language", + "version": "1.0.0", + "license": "Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0", + "private": true, + "sideEffects": false +} \ No newline at end of file diff --git a/src/platform/packages/shared/kbn-kql-language/tsconfig.json b/src/platform/packages/shared/kbn-kql-language/tsconfig.json new file mode 100644 index 0000000000000..3bf9393319a17 --- /dev/null +++ b/src/platform/packages/shared/kbn-kql-language/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "@kbn/tsconfig-base/tsconfig.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node" + ] + }, + "include": [ + "src/**/*", + "**/*.ts", + ], + "kbn_references": [ + "@kbn/i18n", + ], + "exclude": [ + "target/**/*", + ] +} diff --git a/src/platform/plugins/shared/esql/public/plugin.ts b/src/platform/plugins/shared/esql/public/plugin.ts index c7668c3c2cbc0..ee159de0625ac 100755 --- a/src/platform/plugins/shared/esql/public/plugin.ts +++ b/src/platform/plugins/shared/esql/public/plugin.ts @@ -13,6 +13,7 @@ import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { UiActionsSetup, UiActionsStart } from '@kbn/ui-actions-plugin/public'; import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public'; import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; +import { QueryStringInput } from '@kbn/unified-search-plugin/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import { registerESQLEditorAnalyticsEvents } from '@kbn/esql-editor'; import { registerIndexEditorActions, registerIndexEditorAnalyticsEvents } from '@kbn/index-editor'; @@ -123,6 +124,7 @@ export class EsqlPlugin implements Plugin<{}, EsqlPluginStart> { isServerless, variablesService, getLicense: async () => await licensing?.getLicense(), + getQueryStringInputComponent: async () => QueryStringInput, }; setKibanaServices(start, core, data, storage, uiActions, fieldsMetadata, usageCollection); From 43029cefb8f572b066790fb6572242a2452b21e8 Mon Sep 17 00:00:00 2001 From: Stratoula Date: Wed, 17 Dec 2025 09:25:59 +0100 Subject: [PATCH 02/60] Remove package --- .../shared/kbn-kql-language/README.md | 1 - .../packages/shared/kbn-kql-language/index.ts | 8 ---- .../shared/kbn-kql-language/jest.config.js | 14 ------ .../shared/kbn-kql-language/kibana.jsonc | 9 ---- .../packages/shared/kbn-kql-language/moon.yml | 46 ------------------- .../shared/kbn-kql-language/package.json | 7 --- .../shared/kbn-kql-language/tsconfig.json | 20 -------- src/platform/plugins/shared/esql/kibana.jsonc | 4 +- .../shared/unified_search/kibana.jsonc | 2 +- 9 files changed, 3 insertions(+), 108 deletions(-) delete mode 100644 src/platform/packages/shared/kbn-kql-language/README.md delete mode 100644 src/platform/packages/shared/kbn-kql-language/index.ts delete mode 100644 src/platform/packages/shared/kbn-kql-language/jest.config.js delete mode 100644 src/platform/packages/shared/kbn-kql-language/kibana.jsonc delete mode 100644 src/platform/packages/shared/kbn-kql-language/moon.yml delete mode 100644 src/platform/packages/shared/kbn-kql-language/package.json delete mode 100644 src/platform/packages/shared/kbn-kql-language/tsconfig.json diff --git a/src/platform/packages/shared/kbn-kql-language/README.md b/src/platform/packages/shared/kbn-kql-language/README.md deleted file mode 100644 index 70025ceb566ed..0000000000000 --- a/src/platform/packages/shared/kbn-kql-language/README.md +++ /dev/null @@ -1 +0,0 @@ -# KQL diff --git a/src/platform/packages/shared/kbn-kql-language/index.ts b/src/platform/packages/shared/kbn-kql-language/index.ts deleted file mode 100644 index ad21076a9ca93..0000000000000 --- a/src/platform/packages/shared/kbn-kql-language/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * 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". - */ diff --git a/src/platform/packages/shared/kbn-kql-language/jest.config.js b/src/platform/packages/shared/kbn-kql-language/jest.config.js deleted file mode 100644 index 9e6c2c599685b..0000000000000 --- a/src/platform/packages/shared/kbn-kql-language/jest.config.js +++ /dev/null @@ -1,14 +0,0 @@ -/* - * 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". - */ - -module.exports = { - preset: '@kbn/test', - rootDir: '../../../../..', - roots: ['/src/platform/packages/shared/kbn-kql-language'], -}; diff --git a/src/platform/packages/shared/kbn-kql-language/kibana.jsonc b/src/platform/packages/shared/kbn-kql-language/kibana.jsonc deleted file mode 100644 index f218fc54d89a4..0000000000000 --- a/src/platform/packages/shared/kbn-kql-language/kibana.jsonc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "type": "shared-common", - "id": "@kbn/kql-language", - "owner": [ - "@elastic/kibana-presentation" - ], - "group": "platform", - "visibility": "shared" -} \ No newline at end of file diff --git a/src/platform/packages/shared/kbn-kql-language/moon.yml b/src/platform/packages/shared/kbn-kql-language/moon.yml deleted file mode 100644 index 62f652bdc7734..0000000000000 --- a/src/platform/packages/shared/kbn-kql-language/moon.yml +++ /dev/null @@ -1,46 +0,0 @@ -# This file is generated by the @kbn/moon package. Any manual edits will be erased! -# To extend this, write your extensions/overrides to 'moon.extend.yml' -# then regenerate this file with: 'node scripts/regenerate_moon_projects.js --update --filter @kbn/kql-language' - -$schema: https://moonrepo.dev/schemas/project.json -id: '@kbn/kql-language' -type: unknown -owners: - defaultOwner: '@elastic/kibana-presentation' -toolchain: - default: node -language: typescript -project: - name: '@kbn/kql-language' - description: Moon project for @kbn/kql-language - channel: '' - owner: '@elastic/kibana-presentation' - metadata: - sourceRoot: src/platform/packages/shared/kbn-kql-language -dependsOn: - - '@kbn/i18n' -tags: - - shared-common - - package - - prod - - group-platform - - shared - - jest-unit-tests -fileGroups: - src: - - src/**/* - - '**/*.ts' - - '!target/**/*' -tasks: - jest: - args: - - '--config' - - $projectRoot/jest.config.js - inputs: - - '@group(src)' - jestCI: - args: - - '--config' - - $projectRoot/jest.config.js - inputs: - - '@group(src)' diff --git a/src/platform/packages/shared/kbn-kql-language/package.json b/src/platform/packages/shared/kbn-kql-language/package.json deleted file mode 100644 index 90b9baf232e15..0000000000000 --- a/src/platform/packages/shared/kbn-kql-language/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "@kbn/kql-language", - "version": "1.0.0", - "license": "Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0", - "private": true, - "sideEffects": false -} \ No newline at end of file diff --git a/src/platform/packages/shared/kbn-kql-language/tsconfig.json b/src/platform/packages/shared/kbn-kql-language/tsconfig.json deleted file mode 100644 index 3bf9393319a17..0000000000000 --- a/src/platform/packages/shared/kbn-kql-language/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extends": "@kbn/tsconfig-base/tsconfig.json", - "compilerOptions": { - "outDir": "target/types", - "types": [ - "jest", - "node" - ] - }, - "include": [ - "src/**/*", - "**/*.ts", - ], - "kbn_references": [ - "@kbn/i18n", - ], - "exclude": [ - "target/**/*", - ] -} diff --git a/src/platform/plugins/shared/esql/kibana.jsonc b/src/platform/plugins/shared/esql/kibana.jsonc index e60703fd9172e..6ee64dc9a4fe3 100644 --- a/src/platform/plugins/shared/esql/kibana.jsonc +++ b/src/platform/plugins/shared/esql/kibana.jsonc @@ -15,7 +15,6 @@ ], "requiredPlugins": [ "data", - "expressions", "dataViews", "uiActions", "contentManagement", @@ -25,7 +24,8 @@ ], "requiredBundles": [ "kibanaReact", - "kibanaUtils" + "kibanaUtils", + "unifiedSearch" ] } } diff --git a/src/platform/plugins/shared/unified_search/kibana.jsonc b/src/platform/plugins/shared/unified_search/kibana.jsonc index c2fcf824054dc..927337e8bd283 100644 --- a/src/platform/plugins/shared/unified_search/kibana.jsonc +++ b/src/platform/plugins/shared/unified_search/kibana.jsonc @@ -23,7 +23,7 @@ "uiActions", "screenshotMode", "savedObjectsManagement", - "cps" + "cps", ], "optionalPlugins": [ "usageCollection" From dfa176f23fff398b68c3d4d9cb5f00b8e67516fa Mon Sep 17 00:00:00 2001 From: Stratoula Date: Wed, 17 Dec 2025 11:42:49 +0100 Subject: [PATCH 03/60] Create new plugin --- package.json | 1 + src/platform/plugins/shared/esql/kibana.jsonc | 3 +- .../plugins/shared/esql/public/plugin.ts | 2 - src/platform/plugins/shared/kql/.i18nrc.json | 6 + src/platform/plugins/shared/kql/README.md | 1 + .../plugins/shared/kql/jest.config.js | 20 + .../shared/kql/jest.integration.config.js | 14 + src/platform/plugins/shared/kql/kibana.jsonc | 31 + src/platform/plugins/shared/kql/moon.yml | 46 + src/platform/plugins/shared/kql/package.json | 6 + .../autocomplete/autocomplete_service.ts | 104 ++ .../collectors/create_usage_collector.ts | 60 ++ .../public/autocomplete/collectors/index.ts | 12 + .../public/autocomplete/collectors/types.ts | 22 + .../shared/kql/public/autocomplete/index.ts | 20 + .../shared/kql/public/autocomplete/mocks.ts | 21 + .../providers/kql_query_suggestion/README.md | 1 + .../__fixtures__/index_pattern_response.json | 333 ++++++ .../kql_query_suggestion/conjunction.test.ts | 66 ++ .../kql_query_suggestion/conjunction.tsx | 80 ++ .../kql_query_suggestion/field.test.ts | 233 +++++ .../providers/kql_query_suggestion/field.tsx | 106 ++ .../providers/kql_query_suggestion/index.ts | 105 ++ .../kql_query_suggestion/kql_module.ts | 14 + .../kql_query_suggestion/operator.test.ts | 130 +++ .../kql_query_suggestion/operator.tsx | 191 ++++ .../sort_prefix_first.test.ts | 82 ++ .../kql_query_suggestion/sort_prefix_first.ts | 25 + .../providers/kql_query_suggestion/types.ts | 17 + .../kql_query_suggestion/value.test.ts | 200 ++++ .../providers/kql_query_suggestion/value.ts | 74 ++ .../providers/query_suggestion_provider.ts | 58 ++ .../value_suggestion_provider.test.ts | 334 ++++++ .../providers/value_suggestion_provider.ts | 156 +++ .../fetch_index_patterns.ts | 54 + .../filter_button_group.tsx | 84 ++ .../query_string_input/from_user.test.ts | 35 + .../query_string_input/from_user.ts | 46 + .../get_query_string_input.tsx | 18 + .../language_switcher.test.tsx | 118 +++ .../query_string_input/language_switcher.tsx | 141 +++ .../query_string_input/match_pairs.ts | 136 +++ .../query_string_input.styles.tsx | 101 ++ .../query_string_input.test.mocks.ts | 38 + .../query_string_input.test.tsx | 449 ++++++++ .../query_string_input/query_string_input.tsx | 969 ++++++++++++++++++ .../query_string_input/to_user.test.ts | 36 + .../components/query_string_input/to_user.ts | 26 + .../query_string_input/use_memo_css.ts | 52 + .../public/components/typeahead/constants.ts | 26 + .../kql/public/components/typeahead/index.tsx | 24 + .../typeahead/suggestion_component.test.tsx | 136 +++ .../typeahead/suggestion_component.tsx | 254 +++++ .../typeahead/suggestions_component.test.tsx | 142 +++ .../typeahead/suggestions_component.tsx | 335 ++++++ .../kql/public/components/typeahead/types.ts | 14 + .../components/utils/combined_filter.ts | 21 + .../kql/public/components/utils/index.ts | 13 + .../kql/public/components/utils/on_raf.ts | 23 + .../public/components/utils/shallow_equal.ts | 37 + .../plugins/shared/kql/public/plugin.ts | 80 ++ .../autocomplete/autocomplete_service.ts | 45 + .../shared/kql/server/autocomplete/index.ts | 10 + .../shared/kql/server/autocomplete/routes.ts | 19 + .../kql/server/autocomplete/terms_agg.test.ts | 155 +++ .../kql/server/autocomplete/terms_agg.ts | 115 +++ .../server/autocomplete/terms_enum.test.ts | 113 ++ .../kql/server/autocomplete/terms_enum.ts | 59 ++ .../autocomplete/value_suggestions_route.ts | 86 ++ .../plugins/shared/kql/server/config.ts | 38 + .../kql/server/config_deprecations.test.ts | 138 +++ .../shared/kql/server/config_deprecations.ts | 56 + .../shared/kql/server/data_views/index.ts | 10 + .../plugins/shared/kql/server/index.ts | 36 + .../plugins/shared/kql/server/plugin.ts | 59 ++ src/platform/plugins/shared/kql/tsconfig.json | 18 + .../shared/unified_search/kibana.jsonc | 2 +- tsconfig.base.json | 2 + 78 files changed, 6938 insertions(+), 5 deletions(-) create mode 100755 src/platform/plugins/shared/kql/.i18nrc.json create mode 100644 src/platform/plugins/shared/kql/README.md create mode 100644 src/platform/plugins/shared/kql/jest.config.js create mode 100644 src/platform/plugins/shared/kql/jest.integration.config.js create mode 100644 src/platform/plugins/shared/kql/kibana.jsonc create mode 100644 src/platform/plugins/shared/kql/moon.yml create mode 100644 src/platform/plugins/shared/kql/package.json create mode 100644 src/platform/plugins/shared/kql/public/autocomplete/autocomplete_service.ts create mode 100644 src/platform/plugins/shared/kql/public/autocomplete/collectors/create_usage_collector.ts create mode 100644 src/platform/plugins/shared/kql/public/autocomplete/collectors/index.ts create mode 100644 src/platform/plugins/shared/kql/public/autocomplete/collectors/types.ts create mode 100644 src/platform/plugins/shared/kql/public/autocomplete/index.ts create mode 100644 src/platform/plugins/shared/kql/public/autocomplete/mocks.ts create mode 100644 src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/README.md create mode 100644 src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/__fixtures__/index_pattern_response.json create mode 100644 src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts create mode 100644 src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/conjunction.tsx create mode 100644 src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/field.test.ts create mode 100644 src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/field.tsx create mode 100644 src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/index.ts create mode 100644 src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/kql_module.ts create mode 100644 src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/operator.test.ts create mode 100644 src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/operator.tsx create mode 100644 src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.test.ts create mode 100644 src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.ts create mode 100644 src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/types.ts create mode 100644 src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/value.test.ts create mode 100644 src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/value.ts create mode 100644 src/platform/plugins/shared/kql/public/autocomplete/providers/query_suggestion_provider.ts create mode 100644 src/platform/plugins/shared/kql/public/autocomplete/providers/value_suggestion_provider.test.ts create mode 100644 src/platform/plugins/shared/kql/public/autocomplete/providers/value_suggestion_provider.ts create mode 100644 src/platform/plugins/shared/kql/public/components/query_string_input/fetch_index_patterns.ts create mode 100644 src/platform/plugins/shared/kql/public/components/query_string_input/filter_button_group.tsx create mode 100644 src/platform/plugins/shared/kql/public/components/query_string_input/from_user.test.ts create mode 100644 src/platform/plugins/shared/kql/public/components/query_string_input/from_user.ts create mode 100644 src/platform/plugins/shared/kql/public/components/query_string_input/get_query_string_input.tsx create mode 100644 src/platform/plugins/shared/kql/public/components/query_string_input/language_switcher.test.tsx create mode 100644 src/platform/plugins/shared/kql/public/components/query_string_input/language_switcher.tsx create mode 100644 src/platform/plugins/shared/kql/public/components/query_string_input/match_pairs.ts create mode 100644 src/platform/plugins/shared/kql/public/components/query_string_input/query_string_input.styles.tsx create mode 100644 src/platform/plugins/shared/kql/public/components/query_string_input/query_string_input.test.mocks.ts create mode 100644 src/platform/plugins/shared/kql/public/components/query_string_input/query_string_input.test.tsx create mode 100644 src/platform/plugins/shared/kql/public/components/query_string_input/query_string_input.tsx create mode 100644 src/platform/plugins/shared/kql/public/components/query_string_input/to_user.test.ts create mode 100644 src/platform/plugins/shared/kql/public/components/query_string_input/to_user.ts create mode 100644 src/platform/plugins/shared/kql/public/components/query_string_input/use_memo_css.ts create mode 100644 src/platform/plugins/shared/kql/public/components/typeahead/constants.ts create mode 100644 src/platform/plugins/shared/kql/public/components/typeahead/index.tsx create mode 100644 src/platform/plugins/shared/kql/public/components/typeahead/suggestion_component.test.tsx create mode 100644 src/platform/plugins/shared/kql/public/components/typeahead/suggestion_component.tsx create mode 100644 src/platform/plugins/shared/kql/public/components/typeahead/suggestions_component.test.tsx create mode 100644 src/platform/plugins/shared/kql/public/components/typeahead/suggestions_component.tsx create mode 100644 src/platform/plugins/shared/kql/public/components/typeahead/types.ts create mode 100644 src/platform/plugins/shared/kql/public/components/utils/combined_filter.ts create mode 100644 src/platform/plugins/shared/kql/public/components/utils/index.ts create mode 100644 src/platform/plugins/shared/kql/public/components/utils/on_raf.ts create mode 100644 src/platform/plugins/shared/kql/public/components/utils/shallow_equal.ts create mode 100644 src/platform/plugins/shared/kql/public/plugin.ts create mode 100644 src/platform/plugins/shared/kql/server/autocomplete/autocomplete_service.ts create mode 100644 src/platform/plugins/shared/kql/server/autocomplete/index.ts create mode 100644 src/platform/plugins/shared/kql/server/autocomplete/routes.ts create mode 100644 src/platform/plugins/shared/kql/server/autocomplete/terms_agg.test.ts create mode 100644 src/platform/plugins/shared/kql/server/autocomplete/terms_agg.ts create mode 100644 src/platform/plugins/shared/kql/server/autocomplete/terms_enum.test.ts create mode 100644 src/platform/plugins/shared/kql/server/autocomplete/terms_enum.ts create mode 100644 src/platform/plugins/shared/kql/server/autocomplete/value_suggestions_route.ts create mode 100644 src/platform/plugins/shared/kql/server/config.ts create mode 100644 src/platform/plugins/shared/kql/server/config_deprecations.test.ts create mode 100644 src/platform/plugins/shared/kql/server/config_deprecations.ts create mode 100644 src/platform/plugins/shared/kql/server/data_views/index.ts create mode 100644 src/platform/plugins/shared/kql/server/index.ts create mode 100644 src/platform/plugins/shared/kql/server/plugin.ts create mode 100644 src/platform/plugins/shared/kql/tsconfig.json diff --git a/package.json b/package.json index c29c8b3296976..df4f82341b33d 100644 --- a/package.json +++ b/package.json @@ -568,6 +568,7 @@ "@kbn/eso-model-version-example": "link:examples/eso_model_version_example", "@kbn/eso-plugin": "link:x-pack/platform/test/encrypted_saved_objects_api_integration/plugins/api_consumer_plugin", "@kbn/esql": "link:src/platform/plugins/shared/esql", + "@kbn/kql": "link:src/platform/plugins/shared/kql", "@kbn/esql-ast": "link:src/platform/packages/shared/kbn-esql-ast", "@kbn/esql-ast-inspector-plugin": "link:examples/esql_ast_inspector", "@kbn/esql-composer": "link:src/platform/packages/shared/kbn-esql-composer", diff --git a/src/platform/plugins/shared/esql/kibana.jsonc b/src/platform/plugins/shared/esql/kibana.jsonc index 6ee64dc9a4fe3..793aab7d37ff7 100644 --- a/src/platform/plugins/shared/esql/kibana.jsonc +++ b/src/platform/plugins/shared/esql/kibana.jsonc @@ -24,8 +24,7 @@ ], "requiredBundles": [ "kibanaReact", - "kibanaUtils", - "unifiedSearch" + "kibanaUtils" ] } } diff --git a/src/platform/plugins/shared/esql/public/plugin.ts b/src/platform/plugins/shared/esql/public/plugin.ts index ee159de0625ac..c7668c3c2cbc0 100755 --- a/src/platform/plugins/shared/esql/public/plugin.ts +++ b/src/platform/plugins/shared/esql/public/plugin.ts @@ -13,7 +13,6 @@ import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { UiActionsSetup, UiActionsStart } from '@kbn/ui-actions-plugin/public'; import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public'; import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; -import { QueryStringInput } from '@kbn/unified-search-plugin/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import { registerESQLEditorAnalyticsEvents } from '@kbn/esql-editor'; import { registerIndexEditorActions, registerIndexEditorAnalyticsEvents } from '@kbn/index-editor'; @@ -124,7 +123,6 @@ export class EsqlPlugin implements Plugin<{}, EsqlPluginStart> { isServerless, variablesService, getLicense: async () => await licensing?.getLicense(), - getQueryStringInputComponent: async () => QueryStringInput, }; setKibanaServices(start, core, data, storage, uiActions, fieldsMetadata, usageCollection); diff --git a/src/platform/plugins/shared/kql/.i18nrc.json b/src/platform/plugins/shared/kql/.i18nrc.json new file mode 100755 index 0000000000000..af217a7d8ebd0 --- /dev/null +++ b/src/platform/plugins/shared/kql/.i18nrc.json @@ -0,0 +1,6 @@ +{ + "prefix": "kql", + "paths": { + "kql": "." + } +} diff --git a/src/platform/plugins/shared/kql/README.md b/src/platform/plugins/shared/kql/README.md new file mode 100644 index 0000000000000..d72e1f6c9be14 --- /dev/null +++ b/src/platform/plugins/shared/kql/README.md @@ -0,0 +1 @@ +# @kbn/kql diff --git a/src/platform/plugins/shared/kql/jest.config.js b/src/platform/plugins/shared/kql/jest.config.js new file mode 100644 index 0000000000000..11f2aea48ab6c --- /dev/null +++ b/src/platform/plugins/shared/kql/jest.config.js @@ -0,0 +1,20 @@ +/* + * 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". + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../../../..', + roots: ['/src/platform/plugins/shared/kql'], + coverageDirectory: '/target/kibana-coverage/jest/src/platform/plugins/shared/kql', + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '/src/platform/plugins/shared/kql/{common,public,server}/**/*.{js,ts,tsx}', + ], + setupFiles: ['jest-canvas-mock'], +}; diff --git a/src/platform/plugins/shared/kql/jest.integration.config.js b/src/platform/plugins/shared/kql/jest.integration.config.js new file mode 100644 index 0000000000000..a8c63e7acaba3 --- /dev/null +++ b/src/platform/plugins/shared/kql/jest.integration.config.js @@ -0,0 +1,14 @@ +/* + * 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". + */ + +module.exports = { + preset: '@kbn/test/jest_integration', + rootDir: '../../../../..', + roots: ['/src/platform/plugins/shared/kql'], +}; diff --git a/src/platform/plugins/shared/kql/kibana.jsonc b/src/platform/plugins/shared/kql/kibana.jsonc new file mode 100644 index 0000000000000..de6126fcc53ea --- /dev/null +++ b/src/platform/plugins/shared/kql/kibana.jsonc @@ -0,0 +1,31 @@ +{ + "type": "plugin", + "id": "@kbn/kql", + "owner": "@elastic/kibana-presentation", + "group": "platform", + "visibility": "shared", + "plugin": { + "id": "kql", + "server": true, + "browser": true, + "optionalPlugins": [ + "fieldsMetadata", + "usageCollection", + "licensing" + ], + "requiredPlugins": [ + "data", + "dataViews", + "uiActions", + "contentManagement", + "share", + "fileUpload", + "fieldFormats" + ], + "requiredBundles": [ + "kibanaReact", + "kibanaUtils", + "unifiedSearch" + ] + } +} diff --git a/src/platform/plugins/shared/kql/moon.yml b/src/platform/plugins/shared/kql/moon.yml new file mode 100644 index 0000000000000..77faf2785d93b --- /dev/null +++ b/src/platform/plugins/shared/kql/moon.yml @@ -0,0 +1,46 @@ +# This file is generated by the @kbn/moon package. Any manual edits will be erased! +# To extend this, write your extensions/overrides to 'moon.extend.yml' +# then regenerate this file with: 'node scripts/regenerate_moon_projects.js --update --filter @kbn/kql' + +$schema: https://moonrepo.dev/schemas/project.json +id: '@kbn/kql' +type: unknown +owners: + defaultOwner: '@elastic/kibana-presentation' +toolchain: + default: node +language: typescript +project: + name: '@kbn/kql' + description: Moon project for @kbn/kql + channel: '' + owner: '@elastic/kibana-presentation' + metadata: + sourceRoot: src/platform/plugins/shared/kql +dependsOn: + - '@kbn/core' + - '@kbn/i18n' +tags: + - plugin + - prod + - group-platform + - shared + - jest-unit-tests +fileGroups: + src: + - public/**/* + - server/**/* + - '!target/**/*' +tasks: + jest: + args: + - '--config' + - $projectRoot/jest.config.js + inputs: + - '@group(src)' + jestCI: + args: + - '--config' + - $projectRoot/jest.config.js + inputs: + - '@group(src)' diff --git a/src/platform/plugins/shared/kql/package.json b/src/platform/plugins/shared/kql/package.json new file mode 100644 index 0000000000000..f2b59ee0e2f07 --- /dev/null +++ b/src/platform/plugins/shared/kql/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/kql", + "private": true, + "version": "1.0.0", + "license": "Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0" +} diff --git a/src/platform/plugins/shared/kql/public/autocomplete/autocomplete_service.ts b/src/platform/plugins/shared/kql/public/autocomplete/autocomplete_service.ts new file mode 100644 index 0000000000000..2905a139a1ec5 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/autocomplete/autocomplete_service.ts @@ -0,0 +1,104 @@ +/* + * 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 type { CoreSetup, PluginInitializerContext } from '@kbn/core/public'; +import moment from 'moment'; +import type { TimefilterSetup } from '@kbn/data-plugin/public'; +import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; +import type { QuerySuggestionGetFn } from './providers/query_suggestion_provider'; +import { + getEmptyValueSuggestions, + setupValueSuggestionProvider, +} from './providers/value_suggestion_provider'; +import type { ValueSuggestionsGetFn } from './providers/value_suggestion_provider'; + +import type { ConfigSchema } from '../../server/config'; +import { createUsageCollector } from './collectors'; +import { + KUERY_LANGUAGE_NAME, + setupKqlQuerySuggestionProvider, +} from './providers/kql_query_suggestion'; +import type { UnifiedSearchPublicPluginStart, UnifiedSearchStartDependencies } from '../types'; + +export class AutocompleteService { + autocompleteConfig: ConfigSchema['autocomplete']; + + constructor(private initializerContext: PluginInitializerContext) { + const { autocomplete } = initializerContext.config.get(); + + this.autocompleteConfig = autocomplete; + } + + private readonly querySuggestionProviders: Map = new Map(); + private getValueSuggestions?: ValueSuggestionsGetFn; + + private getQuerySuggestions: QuerySuggestionGetFn = (args) => { + const { language } = args; + const provider = this.querySuggestionProviders.get(language); + + if (provider) { + return provider(args); + } + }; + + private hasQuerySuggestions = (language: string) => this.querySuggestionProviders.has(language); + + /** @public **/ + public setup( + core: CoreSetup, + { + timefilter, + usageCollection, + }: { timefilter: TimefilterSetup; usageCollection?: UsageCollectionSetup } + ) { + const { autocomplete } = this.initializerContext.config.get(); + const { terminateAfter, timeout } = autocomplete.valueSuggestions; + const usageCollector = createUsageCollector(core.getStartServices, usageCollection); + + this.getValueSuggestions = this.autocompleteConfig.valueSuggestions.enabled + ? setupValueSuggestionProvider(core, { timefilter, usageCollector }) + : getEmptyValueSuggestions; + + if (this.autocompleteConfig.querySuggestions.enabled) { + this.querySuggestionProviders.set(KUERY_LANGUAGE_NAME, setupKqlQuerySuggestionProvider(core)); + } + + return { + /** + * @deprecated + * please use "getQuerySuggestions" from the start contract + */ + getQuerySuggestions: this.getQuerySuggestions, + getAutocompleteSettings: () => ({ + terminateAfter: moment.duration(terminateAfter).asMilliseconds(), + timeout: moment.duration(timeout).asMilliseconds(), + }), + }; + } + + /** @public **/ + public start() { + return { + getQuerySuggestions: this.getQuerySuggestions, + hasQuerySuggestions: this.hasQuerySuggestions, + getValueSuggestions: this.getValueSuggestions!, + }; + } + + /** @internal **/ + public clearProviders(): void { + this.querySuggestionProviders.clear(); + } +} + +/** @public **/ +export type AutocompleteSetup = ReturnType; + +/** @public **/ +export type AutocompleteStart = ReturnType; diff --git a/src/platform/plugins/shared/kql/public/autocomplete/collectors/create_usage_collector.ts b/src/platform/plugins/shared/kql/public/autocomplete/collectors/create_usage_collector.ts new file mode 100644 index 0000000000000..c1679de364d26 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/autocomplete/collectors/create_usage_collector.ts @@ -0,0 +1,60 @@ +/* + * 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 { first } from 'rxjs'; +import { METRIC_TYPE } from '@kbn/analytics'; +import type { StartServicesAccessor } from '@kbn/core/public'; +import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; +import type { AutocompleteUsageCollector } from './types'; +import { AUTOCOMPLETE_EVENT_TYPE } from './types'; + +export const createUsageCollector = ( + getStartServices: StartServicesAccessor, + usageCollection?: UsageCollectionSetup +): AutocompleteUsageCollector => { + const getCurrentApp = async () => { + const [{ application }] = await getStartServices(); + return application.currentAppId$.pipe(first()).toPromise(); + }; + + return { + trackCall: async () => { + const currentApp = await getCurrentApp(); + return usageCollection?.reportUiCounter( + currentApp!, + METRIC_TYPE.LOADED, + AUTOCOMPLETE_EVENT_TYPE.CALL + ); + }, + trackRequest: async () => { + const currentApp = await getCurrentApp(); + return usageCollection?.reportUiCounter( + currentApp!, + METRIC_TYPE.LOADED, + AUTOCOMPLETE_EVENT_TYPE.REQUEST + ); + }, + trackResult: async () => { + const currentApp = await getCurrentApp(); + return usageCollection?.reportUiCounter( + currentApp!, + METRIC_TYPE.LOADED, + AUTOCOMPLETE_EVENT_TYPE.RESULT + ); + }, + trackError: async () => { + const currentApp = await getCurrentApp(); + return usageCollection?.reportUiCounter( + currentApp!, + METRIC_TYPE.LOADED, + AUTOCOMPLETE_EVENT_TYPE.ERROR + ); + }, + }; +}; diff --git a/src/platform/plugins/shared/kql/public/autocomplete/collectors/index.ts b/src/platform/plugins/shared/kql/public/autocomplete/collectors/index.ts new file mode 100644 index 0000000000000..758587e69d85d --- /dev/null +++ b/src/platform/plugins/shared/kql/public/autocomplete/collectors/index.ts @@ -0,0 +1,12 @@ +/* + * 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". + */ + +export { createUsageCollector } from './create_usage_collector'; +export type { AutocompleteUsageCollector } from './types'; +export { AUTOCOMPLETE_EVENT_TYPE } from './types'; diff --git a/src/platform/plugins/shared/kql/public/autocomplete/collectors/types.ts b/src/platform/plugins/shared/kql/public/autocomplete/collectors/types.ts new file mode 100644 index 0000000000000..b25cd305ba51e --- /dev/null +++ b/src/platform/plugins/shared/kql/public/autocomplete/collectors/types.ts @@ -0,0 +1,22 @@ +/* + * 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". + */ + +export enum AUTOCOMPLETE_EVENT_TYPE { + CALL = 'autocomplete:call', + REQUEST = 'autocomplete:req', + RESULT = 'autocomplete:res', + ERROR = 'autocomplete:err', +} + +export interface AutocompleteUsageCollector { + trackCall: () => Promise; + trackRequest: () => Promise; + trackResult: () => Promise; + trackError: () => Promise; +} diff --git a/src/platform/plugins/shared/kql/public/autocomplete/index.ts b/src/platform/plugins/shared/kql/public/autocomplete/index.ts new file mode 100644 index 0000000000000..0d54c2b2c97e6 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/autocomplete/index.ts @@ -0,0 +1,20 @@ +/* + * 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". + */ + +export type { + QuerySuggestion, + QuerySuggestionGetFn, + QuerySuggestionGetFnArgs, + QuerySuggestionBasic, + QuerySuggestionField, +} from './providers/query_suggestion_provider'; +export { QuerySuggestionTypes } from './providers/query_suggestion_provider'; + +export type { AutocompleteSetup, AutocompleteStart } from './autocomplete_service'; +export { AutocompleteService } from './autocomplete_service'; diff --git a/src/platform/plugins/shared/kql/public/autocomplete/mocks.ts b/src/platform/plugins/shared/kql/public/autocomplete/mocks.ts new file mode 100644 index 0000000000000..666d248c96973 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/autocomplete/mocks.ts @@ -0,0 +1,21 @@ +/* + * 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 type { AutocompleteStart, AutocompleteSetup } from '.'; + +export const autocompleteSetupMock: jest.Mocked = { + getQuerySuggestions: jest.fn(), + getAutocompleteSettings: jest.fn(), +}; + +export const autocompleteStartMock: jest.Mocked = { + getValueSuggestions: jest.fn(), + getQuerySuggestions: jest.fn(), + hasQuerySuggestions: jest.fn(), +}; diff --git a/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/README.md b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/README.md new file mode 100644 index 0000000000000..2ab87a7a490c1 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/README.md @@ -0,0 +1 @@ +This is implementation of KQL query suggestions diff --git a/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/__fixtures__/index_pattern_response.json b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/__fixtures__/index_pattern_response.json new file mode 100644 index 0000000000000..b69403326f74c --- /dev/null +++ b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/__fixtures__/index_pattern_response.json @@ -0,0 +1,333 @@ +{ + "id": "logstash-*", + "title": "logstash-*", + "fields": [ + { + "name": "bytes", + "type": "number", + "esTypes": ["long"], + "count": 10, + "scripted": false, + "searchable": true, + "aggregatable": true, + "readFromDocValues": true + }, + { + "name": "bytes_range", + "type": "number_range", + "esTypes": ["long_range"], + "count": 10, + "scripted": false, + "searchable": true, + "aggregatable": true, + "readFromDocValues": true + }, + { + "name": "ssl", + "type": "boolean", + "esTypes": ["boolean"], + "count": 20, + "scripted": false, + "searchable": true, + "aggregatable": true, + "readFromDocValues": true + }, + { + "name": "@timestamp", + "type": "date", + "esTypes": ["date"], + "count": 30, + "scripted": false, + "searchable": true, + "aggregatable": true, + "readFromDocValues": true + }, + { + "name": "time", + "type": "date", + "esTypes": ["date"], + "count": 30, + "scripted": false, + "searchable": true, + "aggregatable": true, + "readFromDocValues": true + }, + { + "name": "time_range", + "type": "date_range", + "esTypes": ["date_range"], + "count": 10, + "scripted": false, + "searchable": true, + "aggregatable": true, + "readFromDocValues": true + }, + { + "name": "@tags", + "type": "string", + "esTypes": ["keyword"], + "count": 0, + "scripted": false, + "searchable": true, + "aggregatable": true, + "readFromDocValues": true + }, + { + "name": "utc_time", + "type": "date", + "esTypes": ["date"], + "count": 0, + "scripted": false, + "searchable": true, + "aggregatable": true, + "readFromDocValues": true + }, + { + "name": "phpmemory", + "type": "number", + "esTypes": ["integer"], + "count": 0, + "scripted": false, + "searchable": true, + "aggregatable": true, + "readFromDocValues": true + }, + { + "name": "ip", + "type": "ip", + "esTypes": ["ip"], + "count": 0, + "scripted": false, + "searchable": true, + "aggregatable": true, + "readFromDocValues": true + }, + { + "name": "ip_range", + "type": "ip_range", + "esTypes": ["ip_range"], + "count": 0, + "scripted": false, + "searchable": true, + "aggregatable": true, + "readFromDocValues": true + }, + { + "name": "request_body", + "type": "attachment", + "esTypes": ["attachment"], + "count": 0, + "scripted": false, + "searchable": true, + "aggregatable": true, + "readFromDocValues": true + }, + { + "name": "point", + "type": "geo_point", + "esTypes": ["geo_point"], + "count": 0, + "scripted": false, + "searchable": true, + "aggregatable": true, + "readFromDocValues": true + }, + { + "name": "area", + "type": "geo_shape", + "esTypes": ["geo_shape"], + "count": 0, + "scripted": false, + "searchable": true, + "aggregatable": true, + "readFromDocValues": true + }, + { + "name": "hashed", + "type": "murmur3", + "esTypes": ["murmur3"], + "count": 0, + "scripted": false, + "searchable": true, + "aggregatable": false, + "readFromDocValues": false + }, + { + "name": "geo.coordinates", + "type": "geo_point", + "esTypes": ["geo_point"], + "count": 0, + "scripted": false, + "searchable": true, + "aggregatable": true, + "readFromDocValues": true + }, + { + "name": "extension", + "type": "string", + "esTypes": ["keyword"], + "count": 0, + "scripted": false, + "searchable": true, + "aggregatable": true, + "readFromDocValues": true + }, + { + "name": "machine.os", + "type": "string", + "esTypes": ["text"], + "count": 0, + "scripted": false, + "searchable": true, + "aggregatable": true, + "readFromDocValues": false + }, + { + "name": "machine.os.raw", + "type": "string", + "esTypes": ["keyword"], + "count": 0, + "scripted": false, + "searchable": true, + "aggregatable": true, + "readFromDocValues": true, + "subType":{ "multi":{ "parent": "machine.os" } } + }, + { + "name": "geo.src", + "type": "string", + "esTypes": ["keyword"], + "count": 0, + "scripted": false, + "searchable": true, + "aggregatable": true, + "readFromDocValues": true + }, + { + "name": "_id", + "type": "string", + "esTypes": ["_id"], + "count": 0, + "scripted": false, + "searchable": true, + "aggregatable": true, + "readFromDocValues": false + }, + { + "name": "_type", + "type": "string", + "esTypes": ["_type"], + "count": 0, + "scripted": false, + "searchable": true, + "aggregatable": true, + "readFromDocValues": false + }, + { + "name": "_source", + "type": "_source", + "esTypes": ["_source"], + "count": 0, + "scripted": false, + "searchable": true, + "aggregatable": true, + "readFromDocValues": false + }, + { + "name": "non-filterable", + "type": "string", + "esTypes": ["text"], + "count": 0, + "scripted": false, + "searchable": false, + "aggregatable": true, + "readFromDocValues": false + }, + { + "name": "non-sortable", + "type": "string", + "esTypes": ["text"], + "count": 0, + "scripted": false, + "searchable": false, + "aggregatable": false, + "readFromDocValues": false + }, + { + "name": "custom_user_field", + "type": "conflict", + "esTypes": ["long", "text"], + "count": 0, + "scripted": false, + "searchable": true, + "aggregatable": true, + "readFromDocValues": true + }, + { + "name": "script string", + "type": "string", + "count": 0, + "scripted": true, + "script": "'i am a string'", + "lang": "expression", + "searchable": true, + "aggregatable": true, + "readFromDocValues": false + }, + { + "name": "script number", + "type": "number", + "count": 0, + "scripted": true, + "script": "1234", + "lang": "expression", + "searchable": true, + "aggregatable": true, + "readFromDocValues": false + }, + { + "name": "script date", + "type": "date", + "count": 0, + "scripted": true, + "script": "1234", + "lang": "painless", + "searchable": true, + "aggregatable": true, + "readFromDocValues": false + }, + { + "name": "script murmur3", + "type": "murmur3", + "count": 0, + "scripted": true, + "script": "1234", + "lang": "expression", + "searchable": true, + "aggregatable": true, + "readFromDocValues": false + }, + { + "name": "nestedField.child", + "type": "string", + "esTypes": ["text"], + "count": 0, + "scripted": false, + "searchable": true, + "aggregatable": false, + "readFromDocValues": false, + "subType": { "nested": { "path": "nestedField" } } + }, + { + "name": "nestedField.nestedChild.doublyNestedChild", + "type": "string", + "esTypes": ["text"], + "count": 0, + "scripted": false, + "searchable": true, + "aggregatable": false, + "readFromDocValues": false, + "subType": { "nested": { "path": "nestedField.nestedChild" } } + } + ] +} diff --git a/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts new file mode 100644 index 0000000000000..206840648bf5d --- /dev/null +++ b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts @@ -0,0 +1,66 @@ +/* + * 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 { coreMock } from '@kbn/core/public/mocks'; +import type { KueryNode } from '@kbn/es-query'; +import { setupGetConjunctionSuggestions } from './conjunction'; +import type { QuerySuggestionGetFnArgs } from '../query_suggestion_provider'; + +const mockKueryNode = (kueryNode: Partial) => kueryNode as unknown as KueryNode; + +describe('Kuery conjunction suggestions', () => { + const querySuggestionsArgs = null as unknown as QuerySuggestionGetFnArgs; + let getSuggestions: ReturnType; + + beforeEach(() => { + getSuggestions = setupGetConjunctionSuggestions(coreMock.createSetup()); + }); + + test('should return a function', () => { + expect(typeof getSuggestions).toBe('function'); + }); + + test('should not suggest anything for phrases not ending in whitespace', async () => { + const text = 'foo'; + const suggestions = await getSuggestions(querySuggestionsArgs, mockKueryNode({ text })); + + expect(suggestions).toEqual([]); + }); + + test('should suggest and/or for phrases ending in whitespace', async () => { + const text = 'foo '; + const suggestions = await getSuggestions(querySuggestionsArgs, mockKueryNode({ text })); + + expect(suggestions.length).toBe(2); + expect(suggestions.map((suggestion) => suggestion.text)).toEqual(['and ', 'or ']); + }); + + test('should suggest to insert the suggestion at the end of the string', async () => { + const text = 'bar '; + const end = text.length; + const suggestions = await getSuggestions(querySuggestionsArgs, mockKueryNode({ text, end })); + + expect(suggestions.length).toBe(2); + expect(suggestions.map((suggestion) => suggestion.start)).toEqual([end, end]); + expect(suggestions.map((suggestion) => suggestion.end)).toEqual([end, end]); + }); + + test('should have descriptions', async () => { + const text = ' '; + const suggestions = await getSuggestions(querySuggestionsArgs, mockKueryNode({ text })); + + expect(typeof suggestions).toBe('object'); + expect(Object.keys(suggestions).length).toBe(2); + + suggestions.forEach((suggestion) => { + expect(typeof suggestion).toBe('object'); + expect(suggestion).toHaveProperty('description'); + }); + }); +}); diff --git a/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/conjunction.tsx b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/conjunction.tsx new file mode 100644 index 0000000000000..9c34f0919687a --- /dev/null +++ b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/conjunction.tsx @@ -0,0 +1,80 @@ +/* + * 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 React from 'react'; +import type { $Keys } from 'utility-types'; +import { FormattedMessage } from '@kbn/i18n-react'; +import type { KqlQuerySuggestionProvider } from './types'; +import type { QuerySuggestion } from '../query_suggestion_provider'; +import { QuerySuggestionTypes } from '../query_suggestion_provider'; + +const bothArgumentsText = ( + +); + +const oneOrMoreArgumentsText = ( + +); + +const conjunctions: Record = { + and: ( +

+ {bothArgumentsText}, + }} + description="Full text: ' Requires both arguments to be true'. See + 'unifiedSearch.kueryAutocomplete.andOperatorDescription.bothArgumentsText' for 'both arguments' part." + /> +

+ ), + or: ( +

+ {oneOrMoreArgumentsText} + ), + }} + description="Full text: 'Requires one or more arguments to be true'. See + 'unifiedSearch.kueryAutocomplete.orOperatorDescription.oneOrMoreArgumentsText' for 'one or more arguments' part." + /> +

+ ), +}; + +export const setupGetConjunctionSuggestions: KqlQuerySuggestionProvider = (core) => { + return (querySuggestionsArgs, { text, end }) => { + let suggestions: QuerySuggestion[] | [] = []; + + if (text.endsWith(' ')) { + suggestions = Object.keys(conjunctions).map((key: $Keys) => ({ + type: QuerySuggestionTypes.Conjunction, + text: `${key} `, + description: conjunctions[key], + start: end, + end, + })); + } + + return Promise.resolve(suggestions); + }; +}; diff --git a/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/field.test.ts b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/field.test.ts new file mode 100644 index 0000000000000..76bc53461d0b4 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/field.test.ts @@ -0,0 +1,233 @@ +/* + * 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 indexPatternResponse from './__fixtures__/index_pattern_response.json'; + +import { isFilterable } from '@kbn/data-views-plugin/common'; +import type { KueryNode } from '@kbn/es-query'; +import { setupGetFieldSuggestions } from './field'; +import type { QuerySuggestionGetFnArgs } from '../query_suggestion_provider'; +import { coreMock } from '@kbn/core/public/mocks'; +import type { DataViewField } from '@kbn/data-views-plugin/public'; + +const mockKueryNode = (kueryNode: Partial) => kueryNode as unknown as KueryNode; + +describe('Kuery field suggestions', () => { + let querySuggestionsArgs: QuerySuggestionGetFnArgs; + let getSuggestions: ReturnType; + + beforeEach(() => { + querySuggestionsArgs = { + indexPatterns: [indexPatternResponse], + } as unknown as QuerySuggestionGetFnArgs; + + getSuggestions = setupGetFieldSuggestions(coreMock.createSetup()); + }); + + test('should return a function', () => { + expect(typeof getSuggestions).toBe('function'); + }); + + test('should return filterable fields', async () => { + const prefix = ''; + const suffix = ''; + const suggestions = await getSuggestions( + querySuggestionsArgs, + mockKueryNode({ prefix, suffix }) + ); + const filterableFields = (indexPatternResponse.fields as DataViewField[]).filter(isFilterable); + + expect(suggestions.length).toBe(filterableFields.length); + }); + + test('should filter suggestions based on the query', async () => { + const prefix = 'machine'; + const suffix = ''; + const suggestions = await getSuggestions( + querySuggestionsArgs, + mockKueryNode({ prefix, suffix }) + ); + + expect(suggestions.find(({ text }) => text === 'machine.os ')).toBeDefined(); + }); + + test('should filter suggestions case insensitively', async () => { + const prefix = 'MACHINE'; + const suffix = ''; + const suggestions = await getSuggestions( + querySuggestionsArgs, + mockKueryNode({ prefix, suffix }) + ); + + expect(suggestions.find(({ text }) => text === 'machine.os ')).toBeDefined(); + }); + + test('should return suggestions where the query matches somewhere in the middle', async () => { + const prefix = '.'; + const suffix = ''; + const suggestions = await getSuggestions( + querySuggestionsArgs, + mockKueryNode({ prefix, suffix }) + ); + + expect(suggestions.find(({ text }) => text === 'machine.os ')).toBeDefined(); + }); + + test('should field names that match the search', async () => { + const prefix = 'machi'; + const suffix = 'ne.os'; + const suggestions = await getSuggestions( + querySuggestionsArgs, + mockKueryNode({ prefix, suffix }) + ); + + expect(suggestions.find(({ text }) => text === 'machine.os ')).toBeDefined(); + }); + + test('should return field names that start with the query first', async () => { + const prefix = 'e'; + const suffix = ''; + const suggestions = await getSuggestions( + querySuggestionsArgs, + mockKueryNode({ prefix, suffix }) + ); + const extensionIndex = suggestions.findIndex(({ text }) => text === 'extension '); + const bytesIndex = suggestions.findIndex(({ text }) => text === 'bytes '); + + expect(extensionIndex).toBeLessThan(bytesIndex); + }); + + test('should sort keyword fields before analyzed versions', async () => { + const prefix = ''; + const suffix = ''; + const suggestions = await getSuggestions( + querySuggestionsArgs, + mockKueryNode({ prefix, suffix }) + ); + const analyzedIndex = suggestions.findIndex(({ text }) => text === 'machine.os '); + const keywordIndex = suggestions.findIndex(({ text }) => text === 'machine.os.raw '); + + expect(keywordIndex).toBeLessThan(analyzedIndex); + }); + + test('should not have descriptions', async () => { + const prefix = ''; + const suffix = ''; + const suggestions = await getSuggestions( + querySuggestionsArgs, + mockKueryNode({ prefix, suffix }) + ); + expect(suggestions.length).toBeGreaterThan(0); + suggestions.forEach((suggestion) => { + expect(suggestion).not.toHaveProperty('description'); + }); + }); + + describe('nested fields', () => { + test("should automatically wrap nested fields in KQL's nested syntax", async () => { + const prefix = 'ch'; + const suffix = ''; + const suggestions = await getSuggestions( + querySuggestionsArgs, + mockKueryNode({ prefix, suffix }) + ); + + const suggestion = suggestions.find(({ field }) => field.name === 'nestedField.child'); + + expect(suggestion).toBeDefined(); + + if (suggestion) { + expect(suggestion.text).toBe('nestedField:{ child }'); + + // For most suggestions the cursor can be placed at the end of the suggestion text, but + // for the nested field syntax we want to place the cursor inside the curly braces + expect(suggestion.cursorIndex).toBe(20); + } + }); + + test('should narrow suggestions to children of a nested path if provided', async () => { + const prefix = 'ch'; + const suffix = ''; + + const allSuggestions = await getSuggestions( + querySuggestionsArgs, + mockKueryNode({ prefix, suffix }) + ); + expect(allSuggestions.length).toBeGreaterThan(2); + + const nestedSuggestions = await getSuggestions( + querySuggestionsArgs, + mockKueryNode({ + prefix, + suffix, + nestedPath: 'nestedField', + }) + ); + expect(nestedSuggestions).toHaveLength(2); + }); + + test("should not wrap the suggestion in KQL's nested syntax if the correct nested path is already provided", async () => { + const prefix = 'ch'; + const suffix = ''; + + const suggestions = await getSuggestions( + querySuggestionsArgs, + mockKueryNode({ + prefix, + suffix, + nestedPath: 'nestedField', + }) + ); + const suggestion = suggestions.find(({ field }) => field.name === 'nestedField.child'); + + expect(suggestion).toBeDefined(); + + if (suggestion) { + expect(suggestion.text).toBe('child '); + } + }); + + test('should handle fields nested multiple levels deep', async () => { + const prefix = 'doubly'; + const suffix = ''; + + const suggestionsWithNoPath = await getSuggestions( + querySuggestionsArgs, + mockKueryNode({ prefix, suffix }) + ); + expect(suggestionsWithNoPath).toHaveLength(1); + const [noPathSuggestion] = suggestionsWithNoPath; + expect(noPathSuggestion.text).toBe('nestedField.nestedChild:{ doublyNestedChild }'); + + const suggestionsWithPartialPath = await getSuggestions( + querySuggestionsArgs, + mockKueryNode({ + prefix, + suffix, + nestedPath: 'nestedField', + }) + ); + expect(suggestionsWithPartialPath).toHaveLength(1); + const [partialPathSuggestion] = suggestionsWithPartialPath; + expect(partialPathSuggestion.text).toBe('nestedChild:{ doublyNestedChild }'); + + const suggestionsWithFullPath = await getSuggestions( + querySuggestionsArgs, + mockKueryNode({ + prefix, + suffix, + nestedPath: 'nestedField.nestedChild', + }) + ); + expect(suggestionsWithFullPath).toHaveLength(1); + const [fullPathSuggestion] = suggestionsWithFullPath; + expect(fullPathSuggestion.text).toBe('doublyNestedChild '); + }); + }); +}); diff --git a/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/field.tsx b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/field.tsx new file mode 100644 index 0000000000000..8aa123bb1af1f --- /dev/null +++ b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/field.tsx @@ -0,0 +1,106 @@ +/* + * 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 { isFilterable, getFieldSubtypeNested } from '@kbn/data-views-plugin/common'; +import type { DataViewField } from '@kbn/data-views-plugin/public'; +import { flatten } from 'lodash'; +import { escapeKuery } from '@kbn/es-query'; +import { sortPrefixFirst } from './sort_prefix_first'; +import type { QuerySuggestionField } from '../query_suggestion_provider'; +import { QuerySuggestionTypes } from '../query_suggestion_provider'; +import type { KqlQuerySuggestionProvider } from './types'; + +const keywordComparator = (first: DataViewField, second: DataViewField) => { + const extensions = ['raw', 'keyword']; + if (extensions.map((ext) => `${first.name}.${ext}`).includes(second.name)) { + return 1; + } else if (extensions.map((ext) => `${second.name}.${ext}`).includes(first.name)) { + return -1; + } + + return first.name.localeCompare(second.name); +}; + +export const setupGetFieldSuggestions: KqlQuerySuggestionProvider = ( + core +) => { + return async ( + { indexPatterns, suggestionsAbstraction }, + { start, end, prefix, suffix, nestedPath = '' } + ) => { + const allFields = flatten( + indexPatterns.map((indexPattern) => { + return indexPattern.fields.filter(isFilterable); + }) + // temp until IIndexPattern => DataView + ) as DataViewField[]; + const search = `${prefix}${suffix}`.trim().toLowerCase(); + const matchingFields = allFields.filter((field) => { + const subTypeNested = getFieldSubtypeNested(field); + if (suggestionsAbstraction?.fields?.[field.name]) { + return ( + (!nestedPath || (nestedPath && subTypeNested?.nested.path.includes(nestedPath))) && + (suggestionsAbstraction?.fields[field.name]?.displayField ?? '') + .toLowerCase() + .includes(search) + ); + } else { + return ( + (!nestedPath || (nestedPath && subTypeNested?.nested.path.includes(nestedPath))) && + field.name.toLowerCase().includes(search) + ); + } + }); + const sortedFields = sortPrefixFirst(matchingFields.sort(keywordComparator), search, 'name'); + const suggestions: QuerySuggestionField[] = sortedFields.map((field) => { + const isNested = field.subType && field.subType.nested; + const isSuggestionsAbstractionOn = !!suggestionsAbstraction?.fields?.[field.name]; + + const remainingPath = + field.subType && field.subType.nested + ? isSuggestionsAbstractionOn + ? (suggestionsAbstraction?.fields[field.name].displayField ?? '').slice( + nestedPath ? nestedPath.length + 1 : 0 + ) + : field.subType.nested.path.slice(nestedPath ? nestedPath.length + 1 : 0) + : ''; + let text = + isNested && remainingPath.length > 0 + ? `${escapeKuery(remainingPath)}:{ ${escapeKuery( + field.name.slice(field.subType.nested.path.length + 1) + )} }` + : `${escapeKuery(field.name.slice(nestedPath ? nestedPath.length + 1 : 0))} `; + + if (isSuggestionsAbstractionOn) { + if (isNested && remainingPath.length > 0) { + text = `${escapeKuery(remainingPath)}:{ ${escapeKuery( + suggestionsAbstraction?.fields[field.name]?.nestedDisplayField ?? '' + )} }`; + } else if (isNested && remainingPath.length === 0) { + text = suggestionsAbstraction?.fields[field.name]?.nestedDisplayField ?? ''; + } else { + text = suggestionsAbstraction?.fields[field.name].displayField ?? ''; + } + } + + const cursorIndex = isNested && remainingPath.length > 0 ? text.length - 2 : text.length; + + return { + type: QuerySuggestionTypes.Field, + text, + start, + end, + cursorIndex, + field, + }; + }); + + return Promise.resolve(suggestions); + }; +}; diff --git a/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/index.ts b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/index.ts new file mode 100644 index 0000000000000..2c964543ec5a0 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/index.ts @@ -0,0 +1,105 @@ +/* + * 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 type { CoreSetup } from '@kbn/core/public'; +import type { $Keys } from 'utility-types'; +import { flatten, uniqBy } from 'lodash'; +import type { UnifiedSearchPublicPluginStart } from '../../../types'; + +import type { + QuerySuggestion, + QuerySuggestionGetFnArgs, + QuerySuggestionGetFn, +} from '../query_suggestion_provider'; + +const cursorSymbol = '@kuery-cursor@'; + +const dedup = (suggestions: QuerySuggestion[]): QuerySuggestion[] => + uniqBy(suggestions, ({ type, text, start, end }) => [type, text, start, end].join('|')); + +export const KUERY_LANGUAGE_NAME = 'kuery'; + +export const setupKqlQuerySuggestionProvider = ( + core: CoreSetup +): QuerySuggestionGetFn => { + let getSuggestionsByType: + | (( + cursoredQuery: string, + querySuggestionsArgs: QuerySuggestionGetFnArgs + ) => Promise> | []>) + | undefined; + + const asyncGetSuggestionsByTypeFn = async () => { + if (getSuggestionsByType) { + return getSuggestionsByType; + } + + const { + fromKueryExpression, + setupGetFieldSuggestions, + setupGetValueSuggestions, + setupGetOperatorSuggestions, + setupGetConjunctionSuggestions, + } = await import('./kql_module'); + + const providers = { + field: setupGetFieldSuggestions(core), + value: setupGetValueSuggestions(core), + operator: setupGetOperatorSuggestions(core), + conjunction: setupGetConjunctionSuggestions(core), + }; + + return (getSuggestionsByType = async ( + cursoredQuery: string, + querySuggestionsArgs: QuerySuggestionGetFnArgs + ): Promise> | []> => { + try { + const { suggestionsAbstraction } = querySuggestionsArgs; + let cursorNode = fromKueryExpression(cursoredQuery, { + cursorSymbol, + parseCursor: true, + }); + const isNested = cursorNode.nestedPath && cursorNode.nestedPath.length > 0; + const fieldName = isNested + ? `${cursorNode.nestedPath}.${cursorNode.fieldName}` + : cursorNode.fieldName; + if (suggestionsAbstraction && suggestionsAbstraction?.fields[fieldName]) { + if (isNested) { + cursorNode = { + ...cursorNode, + fieldName: suggestionsAbstraction?.fields[fieldName]?.nestedField ?? fieldName, + nestedPath: suggestionsAbstraction?.fields[fieldName]?.nestedPath ?? fieldName, + }; + } else { + cursorNode = { + ...cursorNode, + fieldName: suggestionsAbstraction?.fields[fieldName]?.field ?? fieldName, + }; + } + } + return cursorNode.suggestionTypes.map((type: $Keys) => + providers[type](querySuggestionsArgs, cursorNode) + ); + } catch (e) { + return []; + } + }); + }; + + return async (querySuggestionsArgs): Promise => { + const { query, selectionStart, selectionEnd } = querySuggestionsArgs; + const cursoredQuery = `${query.substr(0, selectionStart)}${cursorSymbol}${query.substr( + selectionEnd + )}`; + const fn = await asyncGetSuggestionsByTypeFn(); + return Promise.all(await fn(cursoredQuery, querySuggestionsArgs)).then((suggestionsByType) => + dedup(flatten(suggestionsByType)) + ); + }; +}; diff --git a/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/kql_module.ts b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/kql_module.ts new file mode 100644 index 0000000000000..924af99469aa6 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/kql_module.ts @@ -0,0 +1,14 @@ +/* + * 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". + */ + +export { fromKueryExpression } from '@kbn/es-query'; +export { setupGetFieldSuggestions } from './field'; +export { setupGetValueSuggestions } from './value'; +export { setupGetOperatorSuggestions } from './operator'; +export { setupGetConjunctionSuggestions } from './conjunction'; diff --git a/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/operator.test.ts b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/operator.test.ts new file mode 100644 index 0000000000000..18a2aee2ad754 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/operator.test.ts @@ -0,0 +1,130 @@ +/* + * 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 indexPatternResponse from './__fixtures__/index_pattern_response.json'; + +import { setupGetOperatorSuggestions } from './operator'; +import type { KueryNode } from '@kbn/es-query'; +import type { QuerySuggestionGetFnArgs } from '../query_suggestion_provider'; +import { coreMock } from '@kbn/core/public/mocks'; + +const mockKueryNode = (kueryNode: Partial) => kueryNode as unknown as KueryNode; + +describe('Kuery operator suggestions', () => { + let getSuggestions: ReturnType; + let querySuggestionsArgs: QuerySuggestionGetFnArgs; + + beforeEach(() => { + querySuggestionsArgs = { + indexPatterns: [indexPatternResponse], + } as unknown as QuerySuggestionGetFnArgs; + + getSuggestions = setupGetOperatorSuggestions(coreMock.createSetup()); + }); + + test('should return a function', () => { + expect(typeof getSuggestions).toBe('function'); + }); + + test('should not return suggestions for non-fields', async () => { + const suggestions = await getSuggestions( + querySuggestionsArgs, + mockKueryNode({ fieldName: 'foo' }) + ); + + expect(suggestions).toEqual([]); + }); + + test('should return exists for every field', async () => { + const suggestions = await getSuggestions( + querySuggestionsArgs, + mockKueryNode({ + fieldName: 'custom_user_field', + }) + ); + + expect(suggestions.length).toEqual(1); + expect(suggestions[0].text).toBe(': * '); + }); + + test('should return equals for string fields', async () => { + const suggestions = await getSuggestions( + querySuggestionsArgs, + mockKueryNode({ fieldName: 'machine.os' }) + ); + + expect(suggestions.find(({ text }) => text === ': ')).toBeDefined(); + expect(suggestions.find(({ text }) => text === '< ')).not.toBeDefined(); + }); + + test('should return numeric operators for numeric fields', async () => { + const suggestions = await getSuggestions( + querySuggestionsArgs, + mockKueryNode({ fieldName: 'bytes' }) + ); + + expect(suggestions.find(({ text }) => text === ': ')).toBeDefined(); + expect(suggestions.find(({ text }) => text === '< ')).toBeDefined(); + }); + + test('should return numeric operators for numeric range fields', async () => { + const suggestions = await getSuggestions( + querySuggestionsArgs, + mockKueryNode({ fieldName: 'bytes_range' }) + ); + + expect(suggestions.find(({ text }) => text === ': ')).toBeDefined(); + expect(suggestions.find(({ text }) => text === '< ')).toBeDefined(); + }); + + test('should return range operators for date range fields', async () => { + const suggestions = await getSuggestions( + querySuggestionsArgs, + mockKueryNode({ fieldName: 'time_range' }) + ); + + expect(suggestions.find(({ text }) => text === ': ')).toBeDefined(); + expect(suggestions.find(({ text }) => text === '< ')).toBeDefined(); + }); + + test('should return range operators for ip range fields', async () => { + const suggestions = await getSuggestions( + querySuggestionsArgs, + mockKueryNode({ fieldName: 'ip_range' }) + ); + + expect(suggestions.find(({ text }) => text === ': ')).toBeDefined(); + expect(suggestions.find(({ text }) => text === '< ')).toBeDefined(); + }); + + test('should have descriptions', async () => { + const suggestions = await getSuggestions( + querySuggestionsArgs, + mockKueryNode({ fieldName: 'bytes' }) + ); + + expect(suggestions.length).toBeGreaterThan(0); + + suggestions.forEach((suggestion) => { + expect(suggestion).toHaveProperty('description'); + }); + }); + + test('should handle nested paths', async () => { + const suggestions = await getSuggestions( + querySuggestionsArgs, + mockKueryNode({ + fieldName: 'child', + nestedPath: 'nestedField', + }) + ); + + expect(suggestions.length).toBeGreaterThan(0); + }); +}); diff --git a/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/operator.tsx b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/operator.tsx new file mode 100644 index 0000000000000..3ef36221235f5 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/operator.tsx @@ -0,0 +1,191 @@ +/* + * 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 React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import type { $Keys } from 'utility-types'; +import { flatten } from 'lodash'; + +import type { KqlQuerySuggestionProvider } from './types'; +import { QuerySuggestionTypes } from '../query_suggestion_provider'; + +const equalsText = ( + +); +const lessThanOrEqualToText = ( + +); +const greaterThanOrEqualToText = ( + +); +const lessThanText = ( + +); +const greaterThanText = ( + +); +const existsText = ( + +); + +const operators = { + ':': { + description: ( + {equalsText} }} + description="Full text: 'equals some value'. See + 'unifiedSearch.kueryAutocomplete.equalOperatorDescription.equalsText' for 'equals' part." + /> + ), + fieldTypes: [ + 'string', + 'number', + 'number_range', + 'date', + 'date_range', + 'ip', + 'ip_range', + 'geo_point', + 'geo_shape', + 'boolean', + ], + }, + '<=': { + description: ( + {lessThanOrEqualToText} + ), + }} + description="Full text: 'is less than or equal to some value'. See + 'unifiedSearch.kueryAutocomplete.lessThanOrEqualOperatorDescription.lessThanOrEqualToText' for 'less than or equal to' part." + /> + ), + fieldTypes: ['number', 'number_range', 'date', 'date_range', 'ip', 'ip_range'], + }, + '>=': { + description: ( + {greaterThanOrEqualToText} + ), + }} + description="Full text: 'is greater than or equal to some value'. See + 'unifiedSearch.kueryAutocomplete.greaterThanOrEqualOperatorDescription.greaterThanOrEqualToText' for 'greater than or equal to' part." + /> + ), + fieldTypes: ['number', 'number_range', 'date', 'date_range', 'ip', 'ip_range'], + }, + '<': { + description: ( + {lessThanText} }} + description="Full text: 'is less than some value'. See + 'unifiedSearch.kueryAutocomplete.lessThanOperatorDescription.lessThanText' for 'less than' part." + /> + ), + fieldTypes: ['number', 'number_range', 'date', 'date_range', 'ip', 'ip_range'], + }, + '>': { + description: ( + {greaterThanText}, + }} + description="Full text: 'is greater than some value'. See + 'unifiedSearch.kueryAutocomplete.greaterThanOperatorDescription.greaterThanText' for 'greater than' part." + /> + ), + fieldTypes: ['number', 'number_range', 'date', 'date_range', 'ip', 'ip_range'], + }, + ': *': { + description: ( + {existsText} }} + description="Full text: 'exists in any form'. See + 'unifiedSearch.kueryAutocomplete.existOperatorDescription.existsText' for 'exists' part." + /> + ), + fieldTypes: undefined, + }, +}; + +type Operators = $Keys; + +const getOperatorByName = (operator: string) => operators[operator as Operators]; +const getDescription = (operator: string) =>

{getOperatorByName(operator).description}

; + +export const setupGetOperatorSuggestions: KqlQuerySuggestionProvider = () => { + return ({ indexPatterns }, { end, fieldName, nestedPath }) => { + const allFields = flatten( + indexPatterns.map((indexPattern) => { + return indexPattern.fields.slice(); + }) + ); + const fullFieldName = nestedPath ? `${nestedPath}.${fieldName}` : fieldName; + const fields = allFields + .filter((field) => field.name === fullFieldName) + .map((field) => { + const matchingOperators = Object.keys(operators).filter((operator) => { + const { fieldTypes } = getOperatorByName(operator); + + return !fieldTypes || fieldTypes.includes(field.type); + }); + + const suggestions = matchingOperators.map((operator) => ({ + type: QuerySuggestionTypes.Operator, + text: operator + ' ', + description: getDescription(operator), + start: end, + end, + })); + return suggestions; + }); + + return Promise.resolve(flatten(fields)); + }; +}; diff --git a/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.test.ts b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.test.ts new file mode 100644 index 0000000000000..4f32823743940 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.test.ts @@ -0,0 +1,82 @@ +/* + * 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 { sortPrefixFirst } from './sort_prefix_first'; + +describe('sortPrefixFirst', () => { + test('should return the original unmodified array if no prefix is provided', () => { + const array = ['foo', 'bar', 'baz']; + const result = sortPrefixFirst(array); + + expect(result).toBe(array); + expect(result).toEqual(['foo', 'bar', 'baz']); + }); + + test('should sort items that match the prefix first without modifying the original array', () => { + const array = ['foo', 'bar', 'baz']; + const result = sortPrefixFirst(array, 'b'); + + expect(result).not.toBe(array); + expect(result).toEqual(['bar', 'baz', 'foo']); + expect(array).toEqual(['foo', 'bar', 'baz']); + }); + + test('should not modify the order of the array other than matching prefix without modifying the original array', () => { + const array = ['foo', 'bar', 'baz', 'qux', 'quux']; + const result = sortPrefixFirst(array, 'b'); + + expect(result).not.toBe(array); + expect(result).toEqual(['bar', 'baz', 'foo', 'qux', 'quux']); + expect(array).toEqual(['foo', 'bar', 'baz', 'qux', 'quux']); + }); + + test('should sort objects by property if provided', () => { + const array = [ + { name: 'foo' }, + { name: 'bar' }, + { name: 'baz' }, + { name: 'qux' }, + { name: 'quux' }, + ]; + const result = sortPrefixFirst(array, 'b', 'name'); + + expect(result).not.toBe(array); + expect(result).toEqual([ + { name: 'bar' }, + { name: 'baz' }, + { name: 'foo' }, + { name: 'qux' }, + { name: 'quux' }, + ]); + expect(array).toEqual([ + { name: 'foo' }, + { name: 'bar' }, + { name: 'baz' }, + { name: 'qux' }, + { name: 'quux' }, + ]); + }); + + test('should handle numbers', () => { + const array = [1, 50, 5]; + const result = sortPrefixFirst(array, 5); + + expect(result).not.toBe(array); + expect(result).toEqual([50, 5, 1]); + }); + + test('should handle mixed case', () => { + const array = ['Date Histogram', 'Histogram']; + const prefix = 'histo'; + const result = sortPrefixFirst(array, prefix); + + expect(result).not.toBe(array); + expect(result).toEqual(['Histogram', 'Date Histogram']); + }); +}); diff --git a/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.ts b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.ts new file mode 100644 index 0000000000000..f5c29103f49a6 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.ts @@ -0,0 +1,25 @@ +/* + * 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 { partition } from 'lodash'; + +export function sortPrefixFirst(array: any[], prefix?: string | number, property?: string): any[] { + if (!prefix) { + return array; + } + const lowerCasePrefix = ('' + prefix).toLowerCase(); + + const partitions = partition(array, (entry) => { + const value = ('' + (property ? entry[property] : entry)).toLowerCase(); + + return value.startsWith(lowerCasePrefix); + }); + + return [...partitions[0], ...partitions[1]]; +} diff --git a/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/types.ts b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/types.ts new file mode 100644 index 0000000000000..9423af3bb4de1 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/types.ts @@ -0,0 +1,17 @@ +/* + * 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 type { KueryNode } from '@kbn/es-query'; +import type { CoreSetup } from '@kbn/core/public'; +import type { UnifiedSearchPublicPluginStart } from '../../../types'; +import type { QuerySuggestionBasic, QuerySuggestionGetFnArgs } from '../query_suggestion_provider'; + +export type KqlQuerySuggestionProvider = ( + core: CoreSetup +) => (querySuggestionsGetFnArgs: QuerySuggestionGetFnArgs, kueryNode: KueryNode) => Promise; diff --git a/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/value.test.ts b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/value.test.ts new file mode 100644 index 0000000000000..3bbbb4d7e4290 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/value.test.ts @@ -0,0 +1,200 @@ +/* + * 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 { setupGetValueSuggestions } from './value'; +import indexPatternResponse from './__fixtures__/index_pattern_response.json'; + +import { coreMock } from '@kbn/core/public/mocks'; +import type { KueryNode } from '@kbn/es-query'; +import type { QuerySuggestionGetFnArgs } from '../query_suggestion_provider'; + +const mockKueryNode = (kueryNode: Partial) => kueryNode as unknown as KueryNode; + +describe('Kuery value suggestions', () => { + let getSuggestions: ReturnType; + let querySuggestionsArgs: QuerySuggestionGetFnArgs; + let autocompleteServiceMock: any; + + beforeEach(() => { + autocompleteServiceMock = { + getValueSuggestions: jest.fn(({ field }) => { + let res: any[]; + + if (field.type === 'boolean') { + res = [true, false]; + } else if (field.name === 'machine.os') { + res = ['Windo"ws', "Mac'", 'Linux']; + } else if (field.name === 'nestedField.child') { + res = ['foo']; + } else { + res = []; + } + return Promise.resolve(res); + }), + }; + + const coreSetup = coreMock.createSetup({ + pluginStartContract: { + autocomplete: autocompleteServiceMock, + }, + }); + getSuggestions = setupGetValueSuggestions(coreSetup); + querySuggestionsArgs = { + indexPatterns: [indexPatternResponse], + } as unknown as QuerySuggestionGetFnArgs; + + jest.clearAllMocks(); + }); + + test('should return a function', () => { + expect(typeof getSuggestions).toBe('function'); + }); + + test('should not search for non existing field', async () => { + const fieldName = 'i_dont_exist'; + const prefix = ''; + const suffix = ''; + + const suggestions = await getSuggestions( + querySuggestionsArgs, + mockKueryNode({ fieldName, prefix, suffix }) + ); + + expect(suggestions.map(({ text }) => text)).toEqual([]); + expect(autocompleteServiceMock.getValueSuggestions).toHaveBeenCalledTimes(0); + }); + + test('should format suggestions', async () => { + const start = 1; + const end = 5; + const suggestions = await getSuggestions( + querySuggestionsArgs, + mockKueryNode({ + fieldName: 'ssl', + prefix: '', + suffix: '', + start, + end, + }) + ); + + expect(suggestions[0].type).toEqual('value'); + expect(suggestions[0].start).toEqual(start); + expect(suggestions[0].end).toEqual(end); + }); + + test('should handle nested paths', async () => { + const suggestions = await getSuggestions( + querySuggestionsArgs, + mockKueryNode({ + fieldName: 'child', + nestedPath: 'nestedField', + prefix: '', + suffix: '', + }) + ); + + expect(suggestions.length).toEqual(1); + expect(suggestions[0].text).toEqual('"foo" '); + }); + + describe('Boolean suggestions', () => { + test('should stringify boolean fields', async () => { + const suggestions = await getSuggestions( + querySuggestionsArgs, + mockKueryNode({ + fieldName: 'ssl', + prefix: '', + suffix: '', + }) + ); + + expect(suggestions.map(({ text }) => text)).toEqual(['true ', 'false ']); + expect(autocompleteServiceMock.getValueSuggestions).toHaveBeenCalledTimes(1); + }); + + test('should filter out boolean suggestions', async () => { + const suggestions = await getSuggestions( + querySuggestionsArgs, + mockKueryNode({ + fieldName: 'ssl', + prefix: 'fa', + suffix: '', + }) + ); + + expect(suggestions.length).toEqual(1); + }); + }); + + describe('String suggestions', () => { + test('should merge prefix and suffix', async () => { + const prefix = 'he'; + const suffix = 'llo'; + + await getSuggestions( + querySuggestionsArgs, + mockKueryNode({ + fieldName: 'machine.os.raw', + prefix, + suffix, + }) + ); + + expect(autocompleteServiceMock.getValueSuggestions).toHaveBeenCalledTimes(1); + expect(autocompleteServiceMock.getValueSuggestions).toBeCalledWith( + expect.objectContaining({ + field: expect.any(Object), + query: prefix + suffix, + }) + ); + }); + + test('should escape quotes in suggestions', async () => { + const suggestions = await getSuggestions( + querySuggestionsArgs, + mockKueryNode({ + fieldName: 'machine.os', + prefix: '', + suffix: '', + }) + ); + + expect(suggestions[0].text).toEqual('"Windo\\"ws" '); + expect(suggestions[1].text).toEqual('"Mac\'" '); + expect(suggestions[2].text).toEqual('"Linux" '); + }); + + test('should filter out string suggestions', async () => { + const suggestions = await getSuggestions( + querySuggestionsArgs, + mockKueryNode({ + fieldName: 'machine.os', + prefix: 'banana', + suffix: '', + }) + ); + + expect(suggestions.length).toEqual(0); + }); + + test('should partially filter out string suggestions - case insensitive', async () => { + const suggestions = await getSuggestions( + querySuggestionsArgs, + mockKueryNode({ + fieldName: 'machine.os', + prefix: 'ma', + suffix: '', + }) + ); + + expect(suggestions.length).toEqual(1); + }); + }); +}); diff --git a/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/value.ts b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/value.ts new file mode 100644 index 0000000000000..d2c0cc4e79eeb --- /dev/null +++ b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/value.ts @@ -0,0 +1,74 @@ +/* + * 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 { flatten } from 'lodash'; +import type { CoreSetup } from '@kbn/core/public'; +import type { DataView, DataViewField } from '@kbn/data-views-plugin/common'; +import { escapeQuotes } from '@kbn/es-query'; +import type { KqlQuerySuggestionProvider } from './types'; +import type { UnifiedSearchPublicPluginStart } from '../../../types'; +import type { QuerySuggestion } from '../query_suggestion_provider'; +import { QuerySuggestionTypes } from '../query_suggestion_provider'; + +const wrapAsSuggestions = (start: number, end: number, query: string, values: string[]) => + values + .filter((value) => value.toLowerCase().includes(query.toLowerCase())) + .map((value) => ({ + type: QuerySuggestionTypes.Value, + text: `${value} `, + start, + end, + })); + +export const setupGetValueSuggestions: KqlQuerySuggestionProvider = ( + core: CoreSetup +) => { + const autoCompleteServicePromise = core + .getStartServices() + .then(([_, __, dataStart]) => dataStart.autocomplete); + return async ( + { indexPatterns, boolFilter, useTimeRange, signal, method, suggestionsAbstraction }, + { start, end, prefix, suffix, fieldName, nestedPath } + ): Promise => { + const fullFieldName = nestedPath ? `${nestedPath}.${fieldName}` : fieldName; + + const indexPatternFieldEntries: Array<[DataView, DataViewField]> = []; + indexPatterns.forEach((indexPattern) => { + indexPattern.fields + .filter((field) => field.name === fullFieldName) + .forEach((field) => indexPatternFieldEntries.push([indexPattern, field])); + }); + + const query = `${prefix}${suffix}`.trim(); + const { getValueSuggestions } = await autoCompleteServicePromise; + + const data = await Promise.all( + indexPatternFieldEntries.map(([indexPattern, field]) => + getValueSuggestions({ + indexPattern, + field, + query, + boolFilter, + useTimeRange, + signal, + method, + querySuggestionKey: suggestionsAbstraction?.type, + }).then((valueSuggestions) => { + const quotedValues = valueSuggestions.map((value) => + typeof value === 'string' ? `"${escapeQuotes(value)}"` : `${value}` + ); + + return wrapAsSuggestions(start, end, query, quotedValues); + }) + ) + ); + + return flatten(data); + }; +}; diff --git a/src/platform/plugins/shared/kql/public/autocomplete/providers/query_suggestion_provider.ts b/src/platform/plugins/shared/kql/public/autocomplete/providers/query_suggestion_provider.ts new file mode 100644 index 0000000000000..9131d118f19d0 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/autocomplete/providers/query_suggestion_provider.ts @@ -0,0 +1,58 @@ +/* + * 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 type { ValueSuggestionsMethod } from '@kbn/data-plugin/common'; +// for replace IIndexPattern => DataView need to fix the issue https://github.com/elastic/kibana/issues/131292 +import type { DataViewField, DataView } from '@kbn/data-views-plugin/common'; +import type { SuggestionsAbstraction } from '../../typeahead/suggestions_component'; + +export enum QuerySuggestionTypes { + Field = 'field', + Value = 'value', + Operator = 'operator', + Conjunction = 'conjunction', + RecentSearch = 'recentSearch', +} + +export type QuerySuggestionGetFn = ( + args: QuerySuggestionGetFnArgs +) => Promise | undefined; + +/** @public **/ +export interface QuerySuggestionGetFnArgs { + language: string; + indexPatterns: DataView[]; + query: string; + selectionStart: number; + selectionEnd: number; + signal?: AbortSignal; + useTimeRange?: boolean; + boolFilter?: any; + method?: ValueSuggestionsMethod; + suggestionsAbstraction?: SuggestionsAbstraction; +} + +/** @public **/ +export interface QuerySuggestionBasic { + type: QuerySuggestionTypes; + description?: string | JSX.Element; + end: number; + start: number; + text: string; + cursorIndex?: number; +} + +/** @public **/ +export interface QuerySuggestionField extends QuerySuggestionBasic { + type: QuerySuggestionTypes.Field; + field: DataViewField; +} + +/** @public **/ +export type QuerySuggestion = QuerySuggestionBasic | QuerySuggestionField; diff --git a/src/platform/plugins/shared/kql/public/autocomplete/providers/value_suggestion_provider.test.ts b/src/platform/plugins/shared/kql/public/autocomplete/providers/value_suggestion_provider.test.ts new file mode 100644 index 0000000000000..5983e12080a9f --- /dev/null +++ b/src/platform/plugins/shared/kql/public/autocomplete/providers/value_suggestion_provider.test.ts @@ -0,0 +1,334 @@ +/* + * 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 type { IUiSettingsClient, CoreSetup } from '@kbn/core/public'; +import { stubIndexPattern, stubFields } from '@kbn/data-plugin/public/stubs'; +import type { TimefilterSetup } from '@kbn/data-plugin/public'; +import { UI_SETTINGS } from '@kbn/data-plugin/common'; +import { setupValueSuggestionProvider } from './value_suggestion_provider'; +import type { ValueSuggestionsGetFn } from './value_suggestion_provider'; +import type { DataView } from '@kbn/data-views-plugin/public'; + +describe('FieldSuggestions', () => { + let getValueSuggestions: ValueSuggestionsGetFn; + let http: any; + let uiConfig: Record = {}; + const uiSettings = { + get: (key: string) => uiConfig[key], + } as IUiSettingsClient; + let getTimeMock: jest.Mock; + let createFilterMock: jest.Mock; + + beforeEach(() => { + getTimeMock = jest.fn().mockReturnValue({ to: 'now', from: 'now-15m' }); + createFilterMock = jest.fn().mockReturnValue({ time: 'fake' }); + http = { fetch: jest.fn().mockResolvedValue([]) }; + + getValueSuggestions = setupValueSuggestionProvider({ http, uiSettings } as CoreSetup, { + timefilter: { + timefilter: { + createFilter: createFilterMock, + getTime: getTimeMock, + }, + } as unknown as TimefilterSetup, + }); + }); + + describe('with value suggestions disabled', () => { + uiConfig = { [UI_SETTINGS.FILTERS_EDITOR_SUGGEST_VALUES]: false }; + + it('should return an empty array', async () => { + const suggestions = await getValueSuggestions({ + indexPattern: stubIndexPattern, + field: stubFields[0], + query: '', + }); + + expect(suggestions).toEqual([]); + expect(http.fetch).not.toHaveBeenCalled(); + }); + }); + + describe('with value suggestions enabled', () => { + uiConfig = { [UI_SETTINGS.FILTERS_EDITOR_SUGGEST_VALUES]: true }; + + it('should return true/false for boolean fields', async () => { + const [field] = stubFields.filter(({ type }) => type === 'boolean'); + const suggestions = await getValueSuggestions({ + indexPattern: stubIndexPattern, + field, + query: '', + }); + + expect(suggestions).toEqual([true, false]); + expect(http.fetch).not.toHaveBeenCalled(); + }); + + it('should return an empty array if the field type is not a string, boolean, or IP', async () => { + const fields = stubFields.filter( + ({ type }) => type !== 'string' && type !== 'boolean' && type !== 'ip' + ); + await Promise.all( + fields.map(async (field) => { + const suggestions = await getValueSuggestions({ + indexPattern: stubIndexPattern, + field, + query: '', + }); + expect(suggestions).toEqual([]); + }) + ); + expect(http.fetch).not.toHaveBeenCalled(); + }); + + it('should return an empty array if the field is not aggregatable', async () => { + const [field] = stubFields.filter(({ aggregatable }) => !aggregatable); + const suggestions = await getValueSuggestions({ + indexPattern: stubIndexPattern, + field, + query: '', + }); + + expect(suggestions).toEqual([]); + expect(http.fetch).not.toHaveBeenCalled(); + }); + + it('should request suggestions for strings', async () => { + const [field] = stubFields.filter( + ({ type, aggregatable }) => type === 'string' && aggregatable + ); + + await getValueSuggestions({ + indexPattern: stubIndexPattern, + field, + query: '', + useTimeRange: false, + }); + + expect(http.fetch).toHaveBeenCalled(); + }); + + it('should request suggestions for ips', async () => { + const [field] = stubFields.filter(({ type, aggregatable }) => type === 'ip' && aggregatable); + + await getValueSuggestions({ + indexPattern: stubIndexPattern, + field, + query: '', + useTimeRange: false, + }); + + expect(http.fetch).toHaveBeenCalled(); + }); + + it('should cache results if using the same index/field/query/filter', async () => { + const [field] = stubFields.filter( + ({ type, aggregatable }) => type === 'string' && aggregatable + ); + const args = { + indexPattern: stubIndexPattern, + field, + query: '', + useTimeRange: false, + }; + + await getValueSuggestions(args); + await getValueSuggestions(args); + + expect(http.fetch).toHaveBeenCalledTimes(1); + }); + + it('should cache results for only one minute', async () => { + const [field] = stubFields.filter( + ({ type, aggregatable }) => type === 'string' && aggregatable + ); + const args = { + indexPattern: stubIndexPattern, + field, + query: '', + useTimeRange: false, + }; + + const { now } = Date; + Date.now = jest.fn(() => 0); + + await getValueSuggestions(args); + + Date.now = jest.fn(() => 60 * 1000); + await getValueSuggestions(args); + Date.now = now; + + expect(http.fetch).toHaveBeenCalledTimes(2); + }); + + it('should not cache results if using a different index/field/query', async () => { + const fields = stubFields.filter( + ({ type, aggregatable }) => type === 'string' && aggregatable + ); + + await getValueSuggestions({ + indexPattern: stubIndexPattern, + field: fields[0], + query: '', + useTimeRange: false, + }); + await getValueSuggestions({ + indexPattern: stubIndexPattern, + field: fields[0], + query: 'query', + useTimeRange: false, + }); + await getValueSuggestions({ + indexPattern: stubIndexPattern, + field: fields[1], + query: '', + useTimeRange: false, + }); + await getValueSuggestions({ + indexPattern: stubIndexPattern, + field: fields[1], + query: 'query', + useTimeRange: false, + }); + + const customIndexPattern = { + ...stubIndexPattern, + title: 'customIndexPattern', + useTimeRange: false, + } as unknown as DataView; + + await getValueSuggestions({ + indexPattern: customIndexPattern, + field: fields[0], + query: '', + useTimeRange: false, + }); + await getValueSuggestions({ + indexPattern: customIndexPattern as unknown as DataView, + field: fields[0], + query: 'query', + useTimeRange: false, + }); + await getValueSuggestions({ + indexPattern: customIndexPattern, + field: fields[1], + query: '', + useTimeRange: false, + }); + await getValueSuggestions({ + indexPattern: customIndexPattern, + field: fields[1], + query: 'query', + useTimeRange: false, + }); + + expect(http.fetch).toHaveBeenCalledTimes(8); + }); + + it('should apply timefilter', async () => { + const [field] = stubFields.filter( + ({ type, aggregatable }) => type === 'string' && aggregatable + ); + + await getValueSuggestions({ + indexPattern: stubIndexPattern, + field, + query: '', + useTimeRange: true, + }); + const callParams = http.fetch.mock.calls[0][1]; + + expect(JSON.parse(callParams.body).filters).toHaveLength(1); + expect(http.fetch).toHaveBeenCalled(); + }); + + it('should round timefilter `to` value', async () => { + getTimeMock.mockReturnValue({ from: '2022-10-27||/d', to: '2022-10-27||/d' }); + + const [field] = stubFields.filter( + ({ type, aggregatable }) => type === 'string' && aggregatable + ); + + await getValueSuggestions({ + indexPattern: stubIndexPattern, + field, + query: '', + useTimeRange: true, + }); + + expect(createFilterMock.mock.calls[0][1]).toMatchInlineSnapshot(` + Object { + "from": "2022-10-27T04:00:00.000Z", + "to": "2022-10-28T03:59:59.999Z", + } + `); + }); + + it('should use terms_enum', async () => { + uiConfig = { + [UI_SETTINGS.FILTERS_EDITOR_SUGGEST_VALUES]: true, + [UI_SETTINGS.AUTOCOMPLETE_VALUE_SUGGESTION_METHOD]: 'terms_enum', + }; + const [field] = stubFields.filter( + ({ type, aggregatable }) => type === 'string' && aggregatable + ); + + await getValueSuggestions({ + indexPattern: stubIndexPattern, + field, + query: '', + useTimeRange: true, + }); + const callParams = http.fetch.mock.calls[0][1]; + + expect(JSON.parse(callParams.body)).toHaveProperty('method', 'terms_enum'); + }); + + it('should use terms_agg', async () => { + uiConfig = { + [UI_SETTINGS.FILTERS_EDITOR_SUGGEST_VALUES]: true, + [UI_SETTINGS.AUTOCOMPLETE_VALUE_SUGGESTION_METHOD]: 'terms_agg', + }; + const [field] = stubFields.filter( + ({ type, aggregatable }) => type === 'string' && aggregatable + ); + + await getValueSuggestions({ + indexPattern: stubIndexPattern, + field, + query: '', + useTimeRange: true, + }); + const callParams = http.fetch.mock.calls[0][1]; + + expect(JSON.parse(callParams.body)).toHaveProperty('method', 'terms_agg'); + }); + + it('should use method passed in', async () => { + uiConfig = { + [UI_SETTINGS.FILTERS_EDITOR_SUGGEST_VALUES]: true, + [UI_SETTINGS.AUTOCOMPLETE_VALUE_SUGGESTION_METHOD]: 'terms_agg', + }; + const [field] = stubFields.filter( + ({ type, aggregatable }) => type === 'string' && aggregatable + ); + + await getValueSuggestions({ + indexPattern: stubIndexPattern, + field, + query: '', + useTimeRange: true, + method: 'terms_agg', + }); + const callParams = http.fetch.mock.calls[0][1]; + + expect(JSON.parse(callParams.body)).toHaveProperty('method', 'terms_agg'); + }); + }); +}); diff --git a/src/platform/plugins/shared/kql/public/autocomplete/providers/value_suggestion_provider.ts b/src/platform/plugins/shared/kql/public/autocomplete/providers/value_suggestion_provider.ts new file mode 100644 index 0000000000000..24d524cbca4c7 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/autocomplete/providers/value_suggestion_provider.ts @@ -0,0 +1,156 @@ +/* + * 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 type { CoreSetup } from '@kbn/core/public'; +import dateMath from '@kbn/datemath'; +import { memoize } from 'lodash'; +import type { ValueSuggestionsMethod } from '@kbn/data-plugin/common'; +import { UI_SETTINGS } from '@kbn/data-plugin/common'; +import type { DataView, DataViewField } from '@kbn/data-views-plugin/common'; +import type { TimefilterSetup } from '@kbn/data-plugin/public'; +import { buildQueryFromFilters } from '@kbn/es-query'; +import type { AutocompleteUsageCollector } from '../collectors'; + +export type ValueSuggestionsGetFn = (args: ValueSuggestionsGetFnArgs) => Promise; + +interface ValueSuggestionsGetFnArgs { + indexPattern: DataView; + field: DataViewField; + query: string; + useTimeRange?: boolean; + boolFilter?: any[]; + signal?: AbortSignal; + method?: ValueSuggestionsMethod; + querySuggestionKey?: 'rules' | 'cases' | 'alerts' | 'endpoints'; +} + +const getAutocompleteTimefilter = ({ timefilter }: TimefilterSetup, indexPattern: DataView) => { + const timeRange = timefilter.getTime(); + + // Use a rounded timerange so that memoizing works properly + const roundedTimerange = { + from: dateMath.parse(timeRange.from)!.startOf('minute').toISOString(), + to: dateMath.parse(timeRange.to, { roundUp: true })!.endOf('minute').toISOString(), + }; + return timefilter.createFilter(indexPattern, roundedTimerange); +}; + +export const getEmptyValueSuggestions = (() => Promise.resolve([])) as ValueSuggestionsGetFn; + +export const setupValueSuggestionProvider = ( + core: CoreSetup, + { + timefilter, + usageCollector, + }: { timefilter: TimefilterSetup; usageCollector?: AutocompleteUsageCollector } +): ValueSuggestionsGetFn => { + function resolver(title: string, field: DataViewField, query: string, filters: any[]) { + // Only cache results for a minute + const ttl = Math.floor(Date.now() / 1000 / 60); + return [ttl, query, title, field.name, JSON.stringify(filters)].join('|'); + } + + const requestSuggestions = memoize( + ( + index: string, + field: DataViewField, + query: string, + filters: any = [], + signal?: AbortSignal, + method: ValueSuggestionsMethod = core.uiSettings.get( + UI_SETTINGS.AUTOCOMPLETE_VALUE_SUGGESTION_METHOD + ), + querySuggestionKey?: string + ) => { + usageCollector?.trackRequest(); + let path = `/internal/kibana/suggestions/values/${index}`; + if (querySuggestionKey) { + if (querySuggestionKey === 'endpoints') { + path = '/internal/api/endpoint/suggestions/endpoints'; + } else { + path = `/internal/${querySuggestionKey}/suggestions/values`; + } + } + return core.http + .fetch(path, { + method: 'POST', + body: JSON.stringify({ + query, + field: field.name, + fieldMeta: field.toSpec?.() ?? field, + filters, + ...(querySuggestionKey === undefined ? { method } : {}), + }), + signal, + version: '1', + }) + .then((r) => { + usageCollector?.trackResult(); + return r; + }); + }, + resolver + ); + + return async ({ + indexPattern, + field, + query, + useTimeRange, + boolFilter, + signal, + method, + querySuggestionKey, + }: ValueSuggestionsGetFnArgs): Promise => { + const shouldSuggestValues = core!.uiSettings.get( + UI_SETTINGS.FILTERS_EDITOR_SUGGEST_VALUES + ); + useTimeRange = + useTimeRange ?? core!.uiSettings.get(UI_SETTINGS.AUTOCOMPLETE_USE_TIMERANGE); + const { title } = indexPattern; + + const isVersionFieldType = field.type === 'string' && field.esTypes?.includes('version'); + + if (field.type === 'boolean') { + return [true, false]; + } else if ( + !shouldSuggestValues || + !field.aggregatable || + (field.type !== 'string' && field.type !== 'ip') || + isVersionFieldType // suggestions don't work for version fields + ) { + return []; + } + + const timeFilter = useTimeRange + ? getAutocompleteTimefilter(timefilter, indexPattern) + : undefined; + const filterQuery = timeFilter ? buildQueryFromFilters([timeFilter], indexPattern).filter : []; + const filters = [...(boolFilter ? boolFilter : []), ...filterQuery]; + try { + usageCollector?.trackCall(); + return await requestSuggestions( + title, + field, + query, + filters, + signal, + method, + querySuggestionKey + ); + } catch (e) { + if (!signal?.aborted) { + usageCollector?.trackError(); + } + // Remove rejected results from memoize cache + requestSuggestions.cache.delete(resolver(title, field, query, filters)); + return []; + } + }; +}; diff --git a/src/platform/plugins/shared/kql/public/components/query_string_input/fetch_index_patterns.ts b/src/platform/plugins/shared/kql/public/components/query_string_input/fetch_index_patterns.ts new file mode 100644 index 0000000000000..c3704fc58bf86 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/components/query_string_input/fetch_index_patterns.ts @@ -0,0 +1,54 @@ +/* + * 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 { isEmpty } from 'lodash'; +import type { DataViewsContract, DataView } from '@kbn/data-views-plugin/public'; + +export interface DataViewByIdOrTitle { + type: 'title' | 'id'; + value: string; +} + +export async function fetchIndexPatterns( + indexPatternsService: DataViewsContract, + indexPatternStrings: DataViewByIdOrTitle[] +): Promise { + if (!indexPatternStrings || isEmpty(indexPatternStrings)) { + return []; + } + + const searchStringList: string[] = []; + const searchIdsList: string[] = []; + for (const { type, value } of indexPatternStrings) { + if (type === 'title') { + searchStringList.push(value); + } else { + searchIdsList.push(value); + } + } + + const searchString = searchStringList.map((value) => `"${value}"`).join(' | '); + + const [searchMatches, ...matchesById] = await Promise.all([ + indexPatternsService.find(searchString), + ...searchIdsList.map((id) => indexPatternsService.get(id)), + ]); + + const exactMatches = [ + ...searchMatches.filter((ip) => searchStringList.includes(ip.title)), + ...matchesById, + ]; + + const allMatches = + exactMatches.length === indexPatternStrings.length + ? exactMatches + : [...exactMatches, await indexPatternsService.getDefault()]; + + return allMatches.filter((d: DataView | null): d is DataView => d != null); +} diff --git a/src/platform/plugins/shared/kql/public/components/query_string_input/filter_button_group.tsx b/src/platform/plugins/shared/kql/public/components/query_string_input/filter_button_group.tsx new file mode 100644 index 0000000000000..eaa7c35154c87 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/components/query_string_input/filter_button_group.tsx @@ -0,0 +1,84 @@ +/* + * 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 type { FC, ReactNode } from 'react'; +import React from 'react'; +import classNames from 'classnames'; +import type { UseEuiTheme } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { css } from '@emotion/react'; +import { useMemoCss } from './use_memo_css'; + +interface Props { + items: ReactNode[]; + /** + * Displays the last item without a border radius as if attached to the next DOM node + */ + attached?: boolean; + /** + * Matches overall height with standard form/button sizes + */ + size?: 'm' | 's'; +} + +export const FilterButtonGroup: FC = ({ items, attached, size = 's', ...rest }: Props) => { + const styles = useMemoCss(filterButtonStyles); + return ( + + {items.map((item, i) => + item == null ? undefined : ( + + {item} + + ) + )} + + ); +}; + +const filterButtonStyles = { + wrapper: ({ euiTheme }: UseEuiTheme) => + css({ + position: 'relative', + height: euiTheme.size.xl, + backgroundColor: euiTheme.colors.backgroundBaseFormsPrepend, + borderRadius: euiTheme.border.radius.medium, + '&::after': { + content: "''", + position: 'absolute', + inset: 0, + border: `${euiTheme.border.width.thin} solid ${euiTheme.colors.borderBasePlain}`, + borderRadius: 'inherit', + pointerEvents: 'none', + }, + // Targets any interactable elements + '*:enabled': { + transform: 'none !important', + }, + '&.kbnFilterButtonGroup--s': { + height: euiTheme.size.xl, + }, + ' &.kbnFilterButtonGroup--attached': { + borderTopRightRadius: 0, + borderBottomRightRadius: 0, + }, + '> *:not(:last-of-type)': { + borderRight: `1px solid ${euiTheme.colors.borderBasePlain}`, + }, + }), +}; diff --git a/src/platform/plugins/shared/kql/public/components/query_string_input/from_user.test.ts b/src/platform/plugins/shared/kql/public/components/query_string_input/from_user.test.ts new file mode 100644 index 0000000000000..797abe4816c53 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/components/query_string_input/from_user.test.ts @@ -0,0 +1,35 @@ +/* + * 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 { fromUser } from './from_user'; + +describe('user input helpers', function () { + describe('user input parser', function () { + it('should return the input if passed an object', function () { + expect(fromUser({ foo: 'bar' })).toEqual({ foo: 'bar' }); + }); + + it('unless the object is empty, then convert it to an empty string', function () { + expect(fromUser({})).toEqual(''); + }); + + it('should pass through input strings that not start with {', function () { + expect(fromUser('foo')).toEqual('foo'); + expect(fromUser('400')).toEqual('400'); + expect(fromUser('true')).toEqual('true'); + }); + + it('should parse valid JSON and return the object instead of a string', function () { + expect(fromUser('{}')).toEqual({}); + + // invalid json remains a string + expect(fromUser('{a:b}')).toEqual('{a:b}'); + }); + }); +}); diff --git a/src/platform/plugins/shared/kql/public/components/query_string_input/from_user.ts b/src/platform/plugins/shared/kql/public/components/query_string_input/from_user.ts new file mode 100644 index 0000000000000..a86b756d23704 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/components/query_string_input/from_user.ts @@ -0,0 +1,46 @@ +/* + * 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 _ from 'lodash'; + +/** + * Take userInput from the user and make it into a query object + * @returns {object} + * @param userInput + */ + +export function fromUser(userInput: object | string) { + const matchAll = ''; + + if (_.isEmpty(userInput)) { + return ''; + } + + if (_.isObject(userInput)) { + return userInput; + } + + userInput = userInput || ''; + if (typeof userInput === 'string') { + const trimmedUserInput = userInput.trim(); + if (trimmedUserInput.length === 0) { + return matchAll; + } + + if (trimmedUserInput[0] === '{') { + try { + return JSON.parse(trimmedUserInput); + } catch (e) { + return userInput; + } + } else { + return userInput; + } + } +} diff --git a/src/platform/plugins/shared/kql/public/components/query_string_input/get_query_string_input.tsx b/src/platform/plugins/shared/kql/public/components/query_string_input/get_query_string_input.tsx new file mode 100644 index 0000000000000..14e2d596c9d9a --- /dev/null +++ b/src/platform/plugins/shared/kql/public/components/query_string_input/get_query_string_input.tsx @@ -0,0 +1,18 @@ +/* + * 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 React from 'react'; +import type { QueryStringInputProps } from './query_string_input'; +import { QueryStringInput } from './query_string_input'; + +export function createQueryStringInput(deps: QueryStringInputProps['deps']) { + return (props: Omit) => { + return ; + }; +} diff --git a/src/platform/plugins/shared/kql/public/components/query_string_input/language_switcher.test.tsx b/src/platform/plugins/shared/kql/public/components/query_string_input/language_switcher.test.tsx new file mode 100644 index 0000000000000..410a501d27411 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/components/query_string_input/language_switcher.test.tsx @@ -0,0 +1,118 @@ +/* + * 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 React from 'react'; +import type { QueryLanguageSwitcherProps } from './language_switcher'; +import { QueryLanguageSwitcher } from './language_switcher'; +import { coreMock } from '@kbn/core/public/mocks'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; +import { fireEvent, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +const startMock = coreMock.createStart(); + +async function renderSwitcher(testProps: Omit) { + const result = renderWithI18n( + + ); + // Wait for component to be fully rendered + await waitFor(() => expect(screen.getByRole('button')).toBeInTheDocument()); + return result; +} + +describe('LanguageSwitcher', () => { + it('should select the lucene context menu if language is lucene', async () => { + await renderSwitcher({ language: 'lucene', onSelectLanguage: jest.fn() }); + + await userEvent.click(screen.getByRole('button')); + expect( + screen.getByTestId('luceneLanguageMenuItem').querySelector('[data-euiicon-type="check"]') + ).toBeTruthy(); + }); + + it('should select the kql context menu if language is kuery', async () => { + await renderSwitcher({ language: 'kuery', onSelectLanguage: jest.fn() }); + await userEvent.click(screen.getByRole('button')); + expect( + screen.getByTestId('kqlLanguageMenuItem').querySelector('[data-euiicon-type="check"]') + ).toBeTruthy(); + }); + + it('should select the lucene context menu if language is text', async () => { + await renderSwitcher({ language: 'text', onSelectLanguage: jest.fn() }); + + await userEvent.click(screen.getByRole('button')); + expect( + screen.getByTestId('luceneLanguageMenuItem').querySelector('[data-euiicon-type="check"]') + ).toBeTruthy(); + }); + it('it set language on nonKql mode text', async () => { + const onSelectLanguage = jest.fn(); + await renderSwitcher({ + language: 'kuery', + nonKqlMode: 'text', + onSelectLanguage, + }); + await userEvent.click(screen.getByRole('button')); + expect( + screen.getByTestId('kqlLanguageMenuItem').querySelector('[data-euiicon-type="check"]') + ).toBeTruthy(); + expect( + screen.getByTestId('luceneLanguageMenuItem').querySelector('[data-euiicon-type="check"]') + ).toBeFalsy(); + fireEvent.click(screen.getByTestId('luceneLanguageMenuItem')); + + expect(onSelectLanguage).toHaveBeenCalledWith('text'); + }); + it('it set language on nonKql mode lucene', async () => { + const onSelectLanguage = jest.fn(); + + await renderSwitcher({ + language: 'kuery', + nonKqlMode: 'lucene', + onSelectLanguage, + }); + await userEvent.click(screen.getByRole('button')); + fireEvent.click(screen.getByTestId('luceneLanguageMenuItem')); + expect(onSelectLanguage).toHaveBeenCalledWith('lucene'); + }); + + it('it set language on kuery mode with nonKqlMode text', async () => { + const onSelectLanguage = jest.fn(); + + await renderSwitcher({ + language: 'text', + nonKqlMode: 'text', + onSelectLanguage, + }); + + await userEvent.click(screen.getByRole('button')); + fireEvent.click(screen.getByTestId('kqlLanguageMenuItem')); + expect(onSelectLanguage).toHaveBeenCalledWith('kuery'); + }); + + it('it set language on kuery mode with nonKqlMode lucene', async () => { + const onSelectLanguage = jest.fn(); + + await renderSwitcher({ + language: 'lucene', + nonKqlMode: 'lucene', + onSelectLanguage, + }); + + await userEvent.click(screen.getByRole('button')); + fireEvent.click(screen.getByTestId('luceneLanguageMenuItem')); + expect( + screen.getByTestId('luceneLanguageMenuItem').querySelector('[data-euiicon-type="check"]') + ).toBeTruthy(); + + fireEvent.click(screen.getByTestId('kqlLanguageMenuItem')); + + expect(onSelectLanguage).toHaveBeenCalledWith('kuery'); + }); +}); diff --git a/src/platform/plugins/shared/kql/public/components/query_string_input/language_switcher.tsx b/src/platform/plugins/shared/kql/public/components/query_string_input/language_switcher.tsx new file mode 100644 index 0000000000000..a8f31744401bf --- /dev/null +++ b/src/platform/plugins/shared/kql/public/components/query_string_input/language_switcher.tsx @@ -0,0 +1,141 @@ +/* + * 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 type { PopoverAnchorPosition } from '@elastic/eui'; +import { + EuiPopover, + EuiPopoverTitle, + EuiContextMenuItem, + toSentenceCase, + EuiHorizontalRule, + EuiButtonIcon, + EuiSelectable, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React, { useState } from 'react'; +import type { DocLinksStart } from '@kbn/core/public'; +import { i18n } from '@kbn/i18n'; + +export const strings = { + getSwitchLanguageButtonText: () => + i18n.translate('unifiedSearch.switchLanguage.buttonText', { + defaultMessage: 'Switch language button.', + }), + getFilterLanguageLabel: () => + i18n.translate('unifiedSearch.switchLanguage.filterLanguageLabel', { + defaultMessage: 'Filter language', + }), + documentationLabel: () => + i18n.translate('unifiedSearch.switchLanguage.documentationLabel', { + defaultMessage: 'Documentation', + }), +}; + +export interface QueryLanguageSwitcherProps { + language: string; + onSelectLanguage: (newLanguage: string) => void; + anchorPosition?: PopoverAnchorPosition; + nonKqlMode?: 'lucene' | 'text'; + isOnTopBarMenu?: boolean; + isDisabled?: boolean; + deps: { + docLinks: DocLinksStart; + }; +} + +export const QueryLanguageSwitcher = React.memo(function QueryLanguageSwitcher({ + language, + anchorPosition, + onSelectLanguage, + nonKqlMode = 'lucene', + isOnTopBarMenu, + isDisabled, + deps: { docLinks }, +}: QueryLanguageSwitcherProps) { + const kueryQuerySyntaxDocs = docLinks.links.query.kueryQuerySyntax; + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + const button = ( + setIsPopoverOpen(!isPopoverOpen)} + className="kqlQueryBar__languageSwitcherButton" + data-test-subj={'switchQueryLanguageButton'} + aria-label={strings.getSwitchLanguageButtonText()} + disabled={isDisabled} + /> + ); + + const isKqlSelected = language === 'kuery'; + + const languageMenuItem = ( + <> + { + const selectedOptions = newOptions.find((option) => option.checked === 'on'); + + if (selectedOptions) { + onSelectLanguage(selectedOptions.key); + } + }} + singleSelection={true} + listProps={{ bordered: false }} + > + {(list) => list} + + + + {strings.documentationLabel()} + + + ); + + const languageQueryStringComponent = ( + setIsPopoverOpen(false)} + repositionOnScroll + panelPaddingSize="none" + > + + + + {languageMenuItem} + + ); + + return Boolean(isOnTopBarMenu) ? languageMenuItem : languageQueryStringComponent; +}); diff --git a/src/platform/plugins/shared/kql/public/components/query_string_input/match_pairs.ts b/src/platform/plugins/shared/kql/public/components/query_string_input/match_pairs.ts new file mode 100644 index 0000000000000..d0d5ebd96d3c3 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/components/query_string_input/match_pairs.ts @@ -0,0 +1,136 @@ +/* + * 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". + */ + +/** + * This helper automatically handles matching pairs. + * Specifically, it does the following: + * + * 1. If the key is a closer, and the character in front of the cursor is the + * same, simply move the cursor forward. + * 2. If the key is an opener, insert the opener at the beginning of the + * selection, and the closer at the end of the selection, and move the + * selection forward. + * 3. If the backspace is hit, and the characters before and after the cursor + * are a pair, remove both characters and move the cursor backward. + */ + +const pairs = ['()', '[]', '{}', `''`, '""']; +const openers = pairs.map((pair) => pair[0]); +const closers = pairs.map((pair) => pair[1]); + +interface MatchPairsOptions { + value: string; + selectionStart: number; + selectionEnd: number; + key: string; + metaKey: boolean; + updateQuery: (query: string, selectionStart: number, selectionEnd: number) => void; + preventDefault: () => void; +} + +export function matchPairs({ + value, + selectionStart, + selectionEnd, + key, + metaKey, + updateQuery, + preventDefault, +}: MatchPairsOptions) { + if (shouldMoveCursorForward(key, value, selectionStart, selectionEnd)) { + preventDefault(); + updateQuery(value, selectionStart + 1, selectionEnd + 1); + } else if (shouldInsertMatchingCloser(key, value, selectionStart, selectionEnd)) { + preventDefault(); + const newValue = + value.substr(0, selectionStart) + + key + + value.substring(selectionStart, selectionEnd) + + closers[openers.indexOf(key)] + + value.substr(selectionEnd); + updateQuery(newValue, selectionStart + 1, selectionEnd + 1); + } else if (shouldRemovePair(key, metaKey, value, selectionStart, selectionEnd)) { + preventDefault(); + const newValue = value.substr(0, selectionEnd - 1) + value.substr(selectionEnd + 1); + updateQuery(newValue, selectionStart - 1, selectionEnd - 1); + } +} + +function shouldMoveCursorForward( + key: string, + value: string, + selectionStart: number, + selectionEnd: number +) { + if (!closers.includes(key)) { + return false; + } + + // Never move selection forward for multi-character selections + if (selectionStart !== selectionEnd) { + return false; + } + + // Move selection forward if the key is the same as the closer in front of the selection + return value.charAt(selectionEnd) === key; +} + +function shouldInsertMatchingCloser( + key: string, + value: string, + selectionStart: number, + selectionEnd: number +) { + if (!openers.includes(key)) { + return false; + } + + // Always insert for multi-character selections + if (selectionStart !== selectionEnd) { + return true; + } + + const precedingCharacter = value.charAt(selectionStart - 1); + const followingCharacter = value.charAt(selectionStart + 1); + + // Don't insert if the preceding character is a backslash + if (precedingCharacter === '\\') { + return false; + } + + // Don't insert if it's a quote and the either of the preceding/following characters is alphanumeric + return !( + ['"', `'`].includes(key) && + (isAlphanumeric(precedingCharacter) || isAlphanumeric(followingCharacter)) + ); +} + +function shouldRemovePair( + key: string, + metaKey: boolean, + value: string, + selectionStart: number, + selectionEnd: number +) { + if (key !== 'Backspace' || metaKey) { + return false; + } + + // Never remove for multi-character selections + if (selectionStart !== selectionEnd) { + return false; + } + + // Remove if the preceding/following characters are a pair + return pairs.includes(value.substr(selectionEnd - 1, 2)); +} + +function isAlphanumeric(value = '') { + return value.match(/[a-zA-Z0-9_]/); +} diff --git a/src/platform/plugins/shared/kql/public/components/query_string_input/query_string_input.styles.tsx b/src/platform/plugins/shared/kql/public/components/query_string_input/query_string_input.styles.tsx new file mode 100644 index 0000000000000..3454d42c97576 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/components/query_string_input/query_string_input.styles.tsx @@ -0,0 +1,101 @@ +/* + * 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 React from 'react'; +import type { UseEuiTheme } from '@elastic/eui'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { css } from '@emotion/react'; +import { useMemoCss } from './use_memo_css'; + +const queryStringInputStyles = { + container: ({ euiTheme }: UseEuiTheme) => + css({ + width: '100%', + zIndex: euiThemeVars.euiZContentMenu, + height: euiTheme.size.xl, + display: 'flex', + '> [aria-expanded="true"]': { + // Using filter allows it to adhere the children's bounds + filter: `drop-shadow(0 ${euiTheme.size.s} ${euiTheme.size.base} rgba(${euiTheme.colors.shadow}, 0.05))`, + }, + '.kbnQueryBar__textareaWrapOuter': { + position: 'relative', + width: '100%', + zIndex: euiTheme.levels.flyout, + }, + '.kbnQueryBar__textareaWrap': { + position: 'relative', + overflow: 'visible !important', // Override EUI form control + display: 'flex', + flex: '1 1 100%', + '&.kbnQueryBar__textareaWrap--withSuggestionVisible .kbnQueryBar__textarea': { + borderBottomRightRadius: 0, + borderBottomLeftRadius: 0, + }, + '> .euiFormControlLayoutIcons': { + maxHeight: euiTheme.size.xxl, + }, + }, + '.kbnQueryBar__textarea': { + zIndex: euiTheme.levels.content, + height: euiTheme.size.xl, + // Unlike most inputs within layout control groups, the text area still needs a border + // for multi-line content. These adjusts help it sit above the control groups + // shadow to line up correctly. + padding: euiTheme.size.xs, + paddingTop: `calc(${euiTheme.size.xs} + 2px)`, + paddingLeft: euiTheme.size.xxl, // Account for search icon + // Firefox adds margin to textarea + margin: 0, + + '&.kbnQueryBar__textarea--isClearable': { + paddingRight: euiTheme.size.xxl, // Account for clear button + }, + + '&:not(.kbnQueryBar__textarea--autoHeight)': { + overflowY: 'hidden', + overflowX: 'hidden', + }, + + // When focused, let it scroll + '&.kbnQueryBar__textarea--autoHeight': { + overflowX: 'auto', + overflowY: 'auto', + whiteSpace: `pre-wrap`, + maxHeight: `calc(35vh - 100px)`, + minHeight: euiTheme.size.xl, + }, + + '~.euiFormControlLayoutIcons': { + // By default form control layout icon is vertically centered, but our textarea + // can expand to be multi-line, so we position it with padding that matches + // the parent textarea padding + zIndex: euiTheme.levels.flyout, + top: euiTheme.size.m, + bottom: 'unset', + }, + + '&.kbnQueryBar__textarea--withPrepend': { + borderTopLeftRadius: 0, + borderBottomLeftRadius: 0, + marginLeft: '-1px', + width: 'calc(100% + 1px)', + }, + }, + }), +}; + +export const StyledDiv = ({ children, ...props }: React.HTMLAttributes) => { + const styles = useMemoCss(queryStringInputStyles); + return ( +
+ {children} +
+ ); +}; diff --git a/src/platform/plugins/shared/kql/public/components/query_string_input/query_string_input.test.mocks.ts b/src/platform/plugins/shared/kql/public/components/query_string_input/query_string_input.test.mocks.ts new file mode 100644 index 0000000000000..60ba2e3f45bd2 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/components/query_string_input/query_string_input.test.mocks.ts @@ -0,0 +1,38 @@ +/* + * 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 { stubIndexPattern } from '@kbn/data-plugin/public/stubs'; + +export const mockPersistedLog = { + add: jest.fn(), + get: jest.fn(() => ['response:200']), +}; + +export const mockPersistedLogFactory = jest.fn, any>(() => { + return mockPersistedLog; +}); + +export const mockFetchIndexPatterns = jest + .fn() + .mockReturnValue(Promise.resolve([stubIndexPattern])); + +jest.mock('@kbn/data-plugin/public/query/persisted_log', () => ({ + PersistedLog: mockPersistedLogFactory, +})); + +jest.mock('./fetch_index_patterns', () => ({ + fetchIndexPatterns: mockFetchIndexPatterns, +})); + +import _ from 'lodash'; +// Using doMock to avoid hoisting so that I can override only the debounce method in lodash +jest.doMock('lodash', () => ({ + ..._, + debounce: (func: () => any) => func, +})); diff --git a/src/platform/plugins/shared/kql/public/components/query_string_input/query_string_input.test.tsx b/src/platform/plugins/shared/kql/public/components/query_string_input/query_string_input.test.tsx new file mode 100644 index 0000000000000..fb811a25fb5b5 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/components/query_string_input/query_string_input.test.tsx @@ -0,0 +1,449 @@ +/* + * 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 { + mockFetchIndexPatterns, + mockPersistedLog, + mockPersistedLogFactory, +} from './query_string_input.test.mocks'; + +import React from 'react'; +import { I18nProvider } from '@kbn/i18n-react'; +import { waitFor, render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { coreMock } from '@kbn/core/public/mocks'; +import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import { stubIndexPattern } from '@kbn/data-plugin/public/stubs'; +import { QueryStringInput } from './query_string_input'; +import { autocompleteStartMock } from '../../autocomplete/mocks'; + +jest.useFakeTimers({ legacyFakeTimers: true }); + +const startMock = coreMock.createStart(); + +const noop = () => { + return; +}; + +const kqlQuery = { + query: 'response:200', + language: 'kuery', +}; + +const luceneQuery = { + query: 'response:200', + language: 'lucene', +}; + +const createMockWebStorage = () => ({ + clear: jest.fn(), + getItem: jest.fn(), + key: jest.fn(), + removeItem: jest.fn(), + setItem: jest.fn(), + length: 0, +}); + +const createMockStorage = () => ({ + storage: createMockWebStorage(), + get: jest.fn(), + set: jest.fn(), + remove: jest.fn(), + clear: jest.fn(), +}); + +function wrapQueryStringInputInContext(testProps: any, storage?: any) { + const mockDataPlugin = dataPluginMock.createStartContract(); + + const defaultOptions = { + screenTitle: 'Another Screen', + intl: null as any, + deps: { + autocomplete: autocompleteStartMock, + data: mockDataPlugin, + appName: testProps.appName || 'test', + storage: storage || createMockStorage(), + usageCollection: { reportUiCounter: () => {} }, + uiSettings: startMock.uiSettings, + http: startMock.http, + docLinks: startMock.docLinks, + }, + }; + + return ( + + + + ); +} + +describe('QueryStringInput', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('Should render the given query', async () => { + const { getByText } = render( + wrapQueryStringInputInContext({ + query: kqlQuery, + onSubmit: noop, + indexPatterns: [stubIndexPattern], + }) + ); + + await waitFor(() => getByText(kqlQuery.query), { timeout: 3000 }); + }); + + it('Should pass the query language to the language switcher', async () => { + render( + wrapQueryStringInputInContext({ + query: luceneQuery, + onSubmit: noop, + indexPatterns: [stubIndexPattern], + }) + ); + + await waitFor(() => { + expect(screen.getByText(luceneQuery.query)).toBeInTheDocument(); + }); + }); + + it('Should disable autoFocus on EuiTextArea when disableAutoFocus prop is true', async () => { + render( + wrapQueryStringInputInContext({ + query: kqlQuery, + onSubmit: noop, + indexPatterns: [stubIndexPattern], + disableAutoFocus: true, + }) + ); + + await waitFor(() => { + expect(screen.getByDisplayValue(kqlQuery.query)).toBeInTheDocument(); + const textarea = document.querySelector('textarea'); + expect(textarea).not.toHaveAttribute('autofocus'); + }); + }); + + it('Should create a unique PersistedLog based on the appName and query language', async () => { + mockPersistedLogFactory.mockClear(); + + render( + wrapQueryStringInputInContext({ + query: kqlQuery, + onSubmit: noop, + indexPatterns: [stubIndexPattern], + disableAutoFocus: true, + appName: 'discover', + }) + ); + + await waitFor(() => { + expect(mockPersistedLogFactory.mock.calls[0][0]).toBe('typeahead:discover-kuery'); + }); + }); + + it("On language selection, should store the user's preference in localstorage and reset the query", async () => { + const mockStorage = createMockStorage(); + const mockCallback = jest.fn(); + + render( + wrapQueryStringInputInContext( + { + query: kqlQuery, + onSubmit: mockCallback, + indexPatterns: [stubIndexPattern], + disableAutoFocus: true, + appName: 'discover', + }, + mockStorage + ) + ); + + await waitFor(() => { + expect(screen.getByTestId('switchQueryLanguageButton')).toBeInTheDocument(); + expect(screen.getByDisplayValue(kqlQuery.query)).toBeInTheDocument(); + }); + + expect(mockStorage).toBeDefined(); + expect(screen.getByTestId('switchQueryLanguageButton')).toBeInTheDocument(); + }); + + it('Should not show the language switcher when disabled', async () => { + render( + wrapQueryStringInputInContext({ + query: luceneQuery, + onSubmit: noop, + indexPatterns: [stubIndexPattern], + disableLanguageSwitcher: true, + }) + ); + + await waitFor(() => { + expect(screen.getByDisplayValue(luceneQuery.query)).toBeInTheDocument(); + expect(screen.queryByTestId('switchQueryLanguageButton')).not.toBeInTheDocument(); + }); + }); + + it('Should show an icon when an iconType is specified', async () => { + render( + wrapQueryStringInputInContext({ + query: luceneQuery, + onSubmit: noop, + indexPatterns: [stubIndexPattern], + iconType: 'search', + }) + ); + + await waitFor(() => { + expect(screen.getByDisplayValue(luceneQuery.query)).toBeInTheDocument(); + const icon = document.querySelector('[data-euiicon-type="search"]'); + expect(icon).toBeInTheDocument(); + }); + }); + + it('Should call onSubmit when the user hits enter inside the query bar', async () => { + const mockCallback = jest.fn(); + + render( + wrapQueryStringInputInContext({ + query: kqlQuery, + onSubmit: mockCallback, + indexPatterns: [stubIndexPattern], + disableAutoFocus: true, + }) + ); + + const textarea = await screen.findByDisplayValue(kqlQuery.query); + expect(textarea).toBeInTheDocument(); + expect(mockCallback).toBeDefined(); + }); + + it('Should fire onBlur callback on input blur', async () => { + const mockCallback = jest.fn(); + const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); + + render( + wrapQueryStringInputInContext({ + query: kqlQuery, + onBlur: mockCallback, + indexPatterns: [stubIndexPattern], + disableAutoFocus: true, + }) + ); + + const textarea = screen.getByDisplayValue(kqlQuery.query); + await user.click(textarea); + await user.tab(); + + expect(mockCallback).toHaveBeenCalledTimes(1); + expect(mockCallback).toHaveBeenCalledWith(); + }); + + it('Should fire onChangeQueryInputFocus after a delay', async () => { + const mockCallback = jest.fn(); + + render( + wrapQueryStringInputInContext({ + query: kqlQuery, + onChangeQueryInputFocus: mockCallback, + indexPatterns: [stubIndexPattern], + disableAutoFocus: true, + }) + ); + + const textarea = await screen.findByDisplayValue(kqlQuery.query); + expect(mockCallback).toBeDefined(); + expect(textarea).toBeInTheDocument(); + }); + + it('Should not fire onChangeQueryInputFocus if input is focused back', async () => { + const mockCallback = jest.fn(); + const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); + + render( + wrapQueryStringInputInContext({ + query: kqlQuery, + onChangeQueryInputFocus: mockCallback, + indexPatterns: [stubIndexPattern], + disableAutoFocus: true, + }) + ); + + mockCallback.mockClear(); + + const textarea = screen.getByDisplayValue(kqlQuery.query); + await user.click(textarea); + await user.tab(); + + jest.advanceTimersByTime(5); + const callCountAfterBlur = mockCallback.mock.calls.length; + + await user.click(textarea); + expect(mockCallback).toHaveBeenCalledWith(true); + + jest.advanceTimersByTime(100); + const finalCallCount = mockCallback.mock.calls.length; + expect(finalCallCount).toBeGreaterThanOrEqual(callCountAfterBlur + 1); + }); + + it('Should call onSubmit after a delay when submitOnBlur is on and blurs input', async () => { + const mockCallback = jest.fn(); + + render( + wrapQueryStringInputInContext({ + query: kqlQuery, + onSubmit: mockCallback, + indexPatterns: [stubIndexPattern], + disableAutoFocus: true, + submitOnBlur: true, + }) + ); + const textarea = await screen.findByDisplayValue(kqlQuery.query); + expect(textarea).toBeInTheDocument(); + expect(mockCallback).toBeDefined(); + }); + + it("Shouldn't call onSubmit on blur by default", async () => { + const mockCallback = jest.fn(); + const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); + + render( + wrapQueryStringInputInContext({ + query: kqlQuery, + onSubmit: mockCallback, + indexPatterns: [stubIndexPattern], + disableAutoFocus: true, + }) + ); + + const textarea = screen.getByDisplayValue(kqlQuery.query); + + await user.click(textarea); + await user.tab(); + + jest.advanceTimersByTime(100); + expect(mockCallback).toHaveBeenCalledTimes(0); + }); + + it('Should use PersistedLog for recent search suggestions', async () => { + const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); + + render( + wrapQueryStringInputInContext({ + query: kqlQuery, + onSubmit: noop, + indexPatterns: [stubIndexPattern], + disableAutoFocus: true, + persistedLog: mockPersistedLog, + }) + ); + + const textarea = await screen.findByDisplayValue(kqlQuery.query); + expect(mockPersistedLog.get).toHaveBeenCalled(); + + mockPersistedLog.get.mockClear(); + + await user.clear(textarea); + await user.type(textarea, 'extensi'); + + await waitFor(() => { + expect(mockPersistedLog.get).toHaveBeenCalled(); + }); + }); + + it('Should accept index pattern strings and fetch the full object', async () => { + const patternStrings = ['logstash-*']; + mockFetchIndexPatterns.mockClear(); + + render( + wrapQueryStringInputInContext({ + query: kqlQuery, + onSubmit: noop, + indexPatterns: patternStrings, + disableAutoFocus: true, + }) + ); + + await waitFor(() => { + expect(mockFetchIndexPatterns.mock.calls[0][1]).toEqual( + patternStrings.map((value) => ({ type: 'title', value })) + ); + }); + }); + + it('Should accept index pattern ids and fetch the full object', async () => { + const idStrings = [{ type: 'id', value: '1' }]; + mockFetchIndexPatterns.mockClear(); + + render( + wrapQueryStringInputInContext({ + query: kqlQuery, + onSubmit: noop, + indexPatterns: idStrings, + disableAutoFocus: true, + }) + ); + + await waitFor(() => { + expect(mockFetchIndexPatterns.mock.calls[0][1]).toEqual(idStrings); + }); + }); + + it('Should accept a mix of full objects, title and ids and fetch only missing index pattern objects', async () => { + const patternStrings = [ + 'logstash-*', + { type: 'id', value: '1' }, + { type: 'title', value: 'my-fake-index-pattern' }, + stubIndexPattern, + ]; + mockFetchIndexPatterns.mockClear(); + + render( + wrapQueryStringInputInContext({ + query: kqlQuery, + onSubmit: noop, + indexPatterns: patternStrings, + disableAutoFocus: true, + }) + ); + + await waitFor(() => { + expect(mockFetchIndexPatterns.mock.calls[0][1]).toEqual([ + { type: 'title', value: 'logstash-*' }, + { type: 'id', value: '1' }, + { type: 'title', value: 'my-fake-index-pattern' }, + ]); + }); + }); + + it('Should convert non-breaking spaces into regular spaces', async () => { + const mockCallback = jest.fn(); + const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); + + render( + wrapQueryStringInputInContext({ + query: { query: '', language: 'kuery' }, + onChange: mockCallback, + indexPatterns: [stubIndexPattern], + disableAutoFocus: true, + }) + ); + + const textarea = screen.getByRole('textbox'); + expect(textarea).toBeInTheDocument(); + + await user.type(textarea, 'test'); + + await waitFor(() => { + expect(mockCallback).toHaveBeenCalled(); + }); + }); +}); diff --git a/src/platform/plugins/shared/kql/public/components/query_string_input/query_string_input.tsx b/src/platform/plugins/shared/kql/public/components/query_string_input/query_string_input.tsx new file mode 100644 index 0000000000000..caddc8cf97ac0 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/components/query_string_input/query_string_input.tsx @@ -0,0 +1,969 @@ +/* + * 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 React, { PureComponent } from 'react'; +import classNames from 'classnames'; +import { METRIC_TYPE } from '@kbn/analytics'; + +import type { EuiIconProps, PopoverAnchorPosition } from '@elastic/eui'; +import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiFormControlLayoutIcons, + EuiLink, + EuiOutsideClickDetector, + EuiPortal, + EuiTextArea, + htmlIdGenerator, + toSentenceCase, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { compact, debounce, isEmpty, isEqual, isFunction, partition } from 'lodash'; +import type { CoreStart, DocLinksStart, Toast } from '@kbn/core/public'; +import type { Query, Filter } from '@kbn/es-query'; +import { getQueryLog } from '@kbn/data-plugin/public'; +import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import { type DataView } from '@kbn/data-views-plugin/public'; +import type { PersistedLog, DataPublicPluginStart } from '@kbn/data-plugin/public'; +import { + getFieldSubtypeNested, + KIBANA_USER_QUERY_LANGUAGE_KEY, + KQL_TELEMETRY_ROUTE_LATEST_VERSION, +} from '@kbn/data-plugin/common'; +import { toMountPoint } from '@kbn/react-kibana-mount'; +import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; +import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; +import { buildQueryFromFilters } from '@kbn/es-query'; +import { matchPairs } from './match_pairs'; +import { toUser } from './to_user'; +import { fromUser } from './from_user'; +import { type DataViewByIdOrTitle, fetchIndexPatterns } from './fetch_index_patterns'; +import { QueryLanguageSwitcher } from './language_switcher'; +import type { + SuggestionsAbstraction, + SuggestionsListSize, +} from '../typeahead/suggestions_component'; +import { SuggestionsComponent } from '../typeahead'; +import { onRaf } from '../utils'; +import { FilterButtonGroup } from './filter_button_group'; +import type { AutocompleteService, QuerySuggestion } from '../../autocomplete'; +import { QuerySuggestionTypes } from '../../autocomplete'; +import { StyledDiv } from './query_string_input.styles'; + +export const strings = { + getSearchInputPlaceholderForText: () => + i18n.translate('unifiedSearch.query.queryBar.searchInputPlaceholderForText', { + defaultMessage: 'Filter your data', + }), + getSearchInputPlaceholder: (language: string) => + i18n.translate('unifiedSearch.query.queryBar.searchInputPlaceholder', { + defaultMessage: 'Filter your data using {language} syntax', + values: { language }, + }), + getQueryBarComboboxAriaLabel: (pageType: string) => + i18n.translate('unifiedSearch.query.queryBar.comboboxAriaLabel', { + defaultMessage: 'Search and filter the {pageType} page', + values: { pageType }, + }), + getQueryBarSearchInputAriaLabel: (pageType: string) => + i18n.translate('unifiedSearch.query.queryBar.searchInputAriaLabel', { + defaultMessage: 'Start typing to search and filter the {pageType} page', + values: { pageType }, + }), + getQueryBarClearInputLabel: () => + i18n.translate('unifiedSearch.query.queryBar.clearInputLabel', { + defaultMessage: 'Clear input', + }), + getKQLNestedQuerySyntaxInfoTitle: () => + i18n.translate('unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoTitle', { + defaultMessage: 'KQL nested query syntax', + }), +}; + +export interface QueryStringInputDependencies { + autocomplete: ReturnType; + usageCollection?: UsageCollectionStart; + data: DataPublicPluginStart; + storage: IStorageWrapper; + notifications: CoreStart['notifications']; + http: CoreStart['http']; + docLinks: DocLinksStart; + uiSettings: CoreStart['uiSettings']; + dataViews: DataViewsPublicPluginStart; + core: CoreStart; +} + +export interface QueryStringInputProps { + indexPatterns: Array; + query: Query; + disableAutoFocus?: boolean; + screenTitle?: string; + prepend?: any; + persistedLog?: PersistedLog; + bubbleSubmitEvent?: boolean; + placeholder?: string; + disableLanguageSwitcher?: boolean; + languageSwitcherPopoverAnchorPosition?: PopoverAnchorPosition; + onBlur?: () => void; + onChange?: (query: Query) => void; + onChangeQueryInputFocus?: (isFocused: boolean) => void; + onSubmit?: (query: Query) => void; + submitOnBlur?: boolean; + dataTestSubj?: string; + size?: SuggestionsListSize; + suggestionsAbstraction?: SuggestionsAbstraction; + className?: string; + isInvalid?: boolean; + isClearable?: boolean; + iconType?: EuiIconProps['type']; + isDisabled?: boolean; + appName: string; + deps: QueryStringInputDependencies; + + /** + * @param nonKqlMode by default if language switch is enabled, user can switch between kql and lucene syntax mode + * this params add another option text, which is just a simple keyword search mode, the way a simple search box works + */ + nonKqlMode?: 'lucene' | 'text'; + /** + * @param autoSubmit if user selects a value, in that case kuery will be auto submitted + */ + autoSubmit?: boolean; + /** + * @param storageKey this key is used to use user preference between kql and non-kql mode + */ + storageKey?: string; + + /** + * Override whether autocomplete suggestions are restricted by time range. + */ + timeRangeForSuggestionsOverride?: boolean; + + /** + * Add additional filters used for suggestions + */ + filtersForSuggestions?: Filter[]; +} + +interface State { + isSuggestionsVisible: boolean; + index: number | null; + suggestions: QuerySuggestion[]; + suggestionLimit: number; + selectionStart: number | null; + selectionEnd: number | null; + indexPatterns: DataView[]; + + /** + * Part of state because passed down to child components + */ + queryBarInputDiv: HTMLDivElement | null; +} + +const KEY_CODES = { + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40, + ENTER: 13, + ESC: 27, + TAB: 9, + HOME: 36, + END: 35, +}; + +export class QueryStringInput extends PureComponent { + static defaultProps = { + storageKey: KIBANA_USER_QUERY_LANGUAGE_KEY, + iconType: 'search', + isClearable: true, + }; + + public state: State = { + isSuggestionsVisible: false, + index: null, + suggestions: [], + suggestionLimit: 50, + selectionStart: null, + selectionEnd: null, + indexPatterns: [], + queryBarInputDiv: null, + }; + + public inputRef: HTMLTextAreaElement | null = null; + + private persistedLog: PersistedLog | undefined; + private abortController?: AbortController; + private fetchIndexPatternsAbortController?: AbortController; + + private reportUiCounter = this.props.deps.usageCollection?.reportUiCounter.bind( + this.props.deps.usageCollection, + this.props.appName + ); + private componentIsUnmounting = false; + private hasScrollListener = false; + + /** + * If any element within the container is currently focused + * @internal + */ + private isFocusWithin = false; + + private getQueryString = () => { + return toUser(this.props.query.query); + }; + + private fetchIndexPatterns = debounce(async () => { + const [objectPatterns = [], stringPatterns = []] = partition< + QueryStringInputProps['indexPatterns'][number], + DataView + >(this.props.indexPatterns || [], (indexPattern): indexPattern is DataView => { + return ( + typeof indexPattern === 'object' && + Object.hasOwn(indexPattern, 'fields') && + Object.hasOwn(indexPattern, 'title') + ); + }); + const idOrTitlePatterns = stringPatterns.map((sp) => + typeof sp === 'string' ? { type: 'title', value: sp } : sp + ) as DataViewByIdOrTitle[]; + + // abort the previous fetch to avoid overriding with outdated data + // issue https://github.com/elastic/kibana/issues/80831 + if (this.fetchIndexPatternsAbortController) this.fetchIndexPatternsAbortController.abort(); + this.fetchIndexPatternsAbortController = new AbortController(); + const currentAbortController = this.fetchIndexPatternsAbortController; + + const objectPatternsFromStrings = await fetchIndexPatterns( + this.props.deps.data.dataViews, + idOrTitlePatterns + ); + + if (!currentAbortController.signal.aborted) { + this.setState({ + indexPatterns: [...objectPatterns, ...objectPatternsFromStrings], + }); + + this.updateSuggestions(); + } + }, 200); + + private getSuggestions = async () => { + if (!this.inputRef) { + return; + } + + const language = this.props.query.language; + const queryString = this.getQueryString(); + + const recentSearchSuggestions = this.getRecentSearchSuggestions(queryString); + + const hasQuerySuggestions = this.props.deps.autocomplete.hasQuerySuggestions(language); + + if ( + !hasQuerySuggestions || + !Array.isArray(this.state.indexPatterns) || + compact(this.state.indexPatterns).length === 0 + ) { + return recentSearchSuggestions; + } + + const indexPatterns = this.state.indexPatterns; + + const { selectionStart, selectionEnd } = this.inputRef; + if (selectionStart === null || selectionEnd === null) { + return; + } + + try { + if (this.abortController) this.abortController.abort(); + this.abortController = new AbortController(); + const suggestions = + (await this.props.deps.autocomplete.getQuerySuggestions({ + language, + indexPatterns, + query: queryString, + selectionStart, + selectionEnd, + signal: this.abortController.signal, + useTimeRange: this.props.timeRangeForSuggestionsOverride, + boolFilter: buildQueryFromFilters(this.props.filtersForSuggestions, undefined).filter, + method: this.props.filtersForSuggestions?.length ? 'terms_agg' : undefined, + suggestionsAbstraction: this.props.suggestionsAbstraction, + })) || []; + return [...suggestions, ...recentSearchSuggestions]; + } catch (e) { + // TODO: Waiting on https://github.com/elastic/kibana/issues/51406 for a properly typed error + // Ignore aborted requests + if (e.message === 'The user aborted a request.') return; + + this.reportUiCounter?.(METRIC_TYPE.LOADED, `query_string:suggestions_error`); + + throw e; + } + }; + + private getRecentSearchSuggestions = (query: string) => { + if (!this.persistedLog) { + return []; + } + const recentSearches = this.persistedLog.get(); + const matchingRecentSearches = recentSearches.filter((recentQuery) => { + const recentQueryString = typeof recentQuery === 'object' ? toUser(recentQuery) : recentQuery; + return recentQueryString !== '' && recentQueryString.includes(query); + }); + return matchingRecentSearches.map((recentSearch) => { + const text = toUser(recentSearch); + const start = 0; + const end = query.length; + return { type: QuerySuggestionTypes.RecentSearch, text, start, end }; + }); + }; + + private updateSuggestions = debounce(async () => { + const suggestions = (await this.getSuggestions()) || []; + if (!this.componentIsUnmounting) { + this.setState({ suggestions }); + } + }, 100); + + private onSubmit = (query: Query) => { + if (this.props.onSubmit) { + if (this.persistedLog) { + this.persistedLog.add(query.query); + } + + this.props.onSubmit({ query: fromUser(query.query), language: query.language }); + } + }; + + private onChange = (query: Query) => { + this.updateSuggestions(); + + if (this.props.onChange) { + this.props.onChange({ query: fromUser(query.query), language: query.language }); + } + }; + + private onQueryStringChange = (value: string) => { + this.setState({ + isSuggestionsVisible: true, + index: null, + suggestionLimit: 50, + }); + + if (this.props.query.query !== value) { + this.onChange({ query: value, language: this.props.query.language }); + } + }; + + private onInputChange = (event: React.ChangeEvent) => { + const value = this.formatTextAreaValue(event.target.value); + this.onQueryStringChange(value); + if (event.target.value === '') { + this.handleRemoveHeight(); + } else { + this.handleAutoHeight(); + } + }; + + private onClickInput = (event: React.MouseEvent) => { + if (event.target instanceof HTMLTextAreaElement) { + const value = this.formatTextAreaValue(event.target.value); + this.onQueryStringChange(value); + } + }; + + private onKeyUp = (event: React.KeyboardEvent) => { + if ([KEY_CODES.LEFT, KEY_CODES.RIGHT, KEY_CODES.HOME, KEY_CODES.END].includes(event.keyCode)) { + this.setState({ isSuggestionsVisible: true }); + if (event.target instanceof HTMLTextAreaElement) { + const value = this.formatTextAreaValue(event.target.value); + this.onQueryStringChange(value); + } + } + }; + + private onKeyDown = (event: React.KeyboardEvent) => { + if (event.target instanceof HTMLTextAreaElement) { + const { isSuggestionsVisible, index } = this.state; + const preventDefault = event.preventDefault.bind(event); + const { target, key, metaKey } = event; + const { value, selectionStart, selectionEnd } = target; + const updateQuery = (query: string, newSelectionStart: number, newSelectionEnd: number) => { + this.onQueryStringChange(query); + + if ( + this.inputRef?.selectionStart !== newSelectionStart || + this.inputRef?.selectionEnd !== newSelectionEnd + ) { + this.setState({ + selectionStart: newSelectionStart, + selectionEnd: newSelectionEnd, + }); + } + }; + + switch (event.keyCode) { + case KEY_CODES.DOWN: + if (isSuggestionsVisible && index !== null) { + event.preventDefault(); + this.incrementIndex(index); + // Note to engineers. `isSuggestionVisible` does not mean the suggestions are visible. + // This should likely be fixed, it's more that suggestions can be shown. + } else if ((isSuggestionsVisible && index == null) || this.getQueryString() === '') { + event.preventDefault(); + this.setState({ isSuggestionsVisible: true, index: 0 }); + } + break; + case KEY_CODES.UP: + if (isSuggestionsVisible && index !== null) { + event.preventDefault(); + this.decrementIndex(index); + } + break; + case KEY_CODES.ENTER: + if (!this.props.bubbleSubmitEvent) { + event.preventDefault(); + } + if (isSuggestionsVisible && index !== null && this.state.suggestions[index]) { + event.preventDefault(); + this.selectSuggestion(this.state.suggestions[index], index); + } else { + this.onSubmit(this.props.query); + this.setState({ + isSuggestionsVisible: false, + }); + } + break; + case KEY_CODES.ESC: + if (isSuggestionsVisible) { + event.preventDefault(); + } + this.setState({ isSuggestionsVisible: false, index: null }); + break; + case KEY_CODES.TAB: + this.setState({ isSuggestionsVisible: false, index: null }); + break; + default: + if (selectionStart !== null && selectionEnd !== null) { + matchPairs({ + value, + selectionStart, + selectionEnd, + key, + metaKey, + updateQuery, + preventDefault, + }); + } + + break; + } + } + }; + + private selectSuggestion = (suggestion: QuerySuggestion, listIndex: number) => { + if (!this.inputRef) { + return; + } + const { type, text, start, end, cursorIndex } = suggestion; + + this.handleNestedFieldSyntaxNotification(suggestion); + + const query = this.getQueryString(); + const { selectionStart, selectionEnd } = this.inputRef; + if (selectionStart === null || selectionEnd === null) { + return; + } + + const value = query.substr(0, selectionStart) + query.substr(selectionEnd); + const newQueryString = value.substr(0, start) + text + value.substr(end); + + this.reportUiCounter?.( + METRIC_TYPE.CLICK, + `query_string:${type}:suggestions_select_position_${listIndex}` + ); + this.reportUiCounter?.( + METRIC_TYPE.CLICK, + `query_string:${type}:suggestions_select_q_length_${end - start}` + ); + + this.onQueryStringChange(newQueryString); + + this.setState({ + selectionStart: start + (cursorIndex ? cursorIndex : text.length), + selectionEnd: start + (cursorIndex ? cursorIndex : text.length), + }); + const isTypeRecentSearch = type === QuerySuggestionTypes.RecentSearch; + + const isAutoSubmitAndValid = + this.props.autoSubmit && + (type === QuerySuggestionTypes.Value || [':*', ': *'].includes(value.trim())); + + if (isTypeRecentSearch || isAutoSubmitAndValid) { + this.setState({ isSuggestionsVisible: false, index: null }); + this.onSubmit({ query: newQueryString, language: this.props.query.language }); + } + }; + + private handleNestedFieldSyntaxNotification = (suggestion: QuerySuggestion) => { + const subTypeNested = 'field' in suggestion && getFieldSubtypeNested(suggestion.field); + if ( + subTypeNested && + subTypeNested.nested && + !this.props.deps.storage.get('kibana.KQLNestedQuerySyntaxInfoOptOut') + ) { + const { notifications, docLinks } = this.props.deps; + + const onKQLNestedQuerySyntaxInfoOptOut = (toast: Toast) => { + if (!this.props.deps.storage) return; + this.props.deps.storage.set('kibana.KQLNestedQuerySyntaxInfoOptOut', true); + notifications!.toasts.remove(toast); + }; + + if (notifications && docLinks) { + const toast = notifications.toasts.add({ + title: strings.getKQLNestedQuerySyntaxInfoTitle(), + text: toMountPoint( +
+

+ + + + ), + }} + /> +

+ + + onKQLNestedQuerySyntaxInfoOptOut(toast)}> + + + + +
, + this.props.deps.core + ), + }); + } + } + }; + + private increaseLimit = () => { + this.setState({ + suggestionLimit: this.state.suggestionLimit + 50, + }); + }; + + private incrementIndex = (currentIndex: number) => { + let nextIndex = currentIndex + 1; + if (currentIndex === null || nextIndex >= this.state.suggestions.length) { + nextIndex = 0; + } + this.setState({ index: nextIndex }); + }; + + private decrementIndex = (currentIndex: number) => { + const previousIndex = currentIndex - 1; + if (previousIndex < 0) { + this.setState({ index: this.state.suggestions.length - 1 }); + } else { + this.setState({ index: previousIndex }); + } + }; + + private onSelectLanguage = (language: string) => { + // Send telemetry info every time the user opts in or out of kuery + // As a result it is important this function only ever gets called in the + // UI component's change handler. + this.props.deps.http.post('/internal/kql_opt_in_stats', { + version: KQL_TELEMETRY_ROUTE_LATEST_VERSION, + body: JSON.stringify({ opt_in: language === 'kuery' }), + }); + + const storageKey = this.props.storageKey; + this.props.deps.storage.set(storageKey!, language); + + const newQuery = { query: '', language }; + this.onChange(newQuery); + this.onSubmit(newQuery); + this.reportUiCounter?.( + METRIC_TYPE.LOADED, + storageKey ? `${storageKey}:language:${language}` : `query_string:language:${language}` + ); + }; + + private onOutsideClick = () => { + if (this.state.isSuggestionsVisible) { + this.setState({ isSuggestionsVisible: false, index: null }); + this.scheduleOnInputBlur(); + } + }; + + private blurTimeoutHandle: number | undefined; + /** + * Notify parent about input's blur after a delay only + * if the focus didn't get back inside the input container + * and if suggestions were closed + * https://github.com/elastic/kibana/issues/92040 + */ + private scheduleOnInputBlur = () => { + clearTimeout(this.blurTimeoutHandle); + this.blurTimeoutHandle = window.setTimeout(() => { + if (!this.isFocusWithin && !this.state.isSuggestionsVisible && !this.componentIsUnmounting) { + this.handleBlurHeight(); + if (this.props.onChangeQueryInputFocus) { + this.props.onChangeQueryInputFocus(false); + } + + if (this.props.submitOnBlur) { + this.onSubmit(this.props.query); + } + } + }, 50); + }; + + private onInputBlur = () => { + if (isFunction(this.props.onBlur)) { + this.props.onBlur(); + } + }; + + private handleResize = () => { + this.handleAutoHeight(); + this.handleBlurOnScroll(); + }; + + private onClickSuggestion = (suggestion: QuerySuggestion, index: number) => { + if (!this.inputRef) { + return; + } + this.selectSuggestion(suggestion, index); + this.inputRef.focus(); + }; + + private initPersistedLog = () => { + const { uiSettings } = this.props.deps; + const { appName } = this.props; + this.persistedLog = this.props.persistedLog + ? this.props.persistedLog + : getQueryLog(uiSettings, this.props.deps.storage, appName, this.props.query.language); + }; + + public onMouseEnterSuggestion = (_suggestion: QuerySuggestion, index: number) => { + this.setState({ index }); + }; + + textareaId = htmlIdGenerator()(); + + public componentDidMount() { + const parsedQuery = fromUser(toUser(this.props.query.query)); + if (!isEqual(this.props.query.query, parsedQuery)) { + this.onChange({ ...this.props.query, query: parsedQuery }); + } + + this.initPersistedLog(); + this.fetchIndexPatterns(); + this.handleAutoHeight(); + + window.addEventListener('resize', this.handleResize); + + this.handleBlurOnScroll(); + } + + public componentDidUpdate(prevProps: QueryStringInputProps) { + const parsedQuery = fromUser(toUser(this.props.query.query)); + if (!isEqual(this.props.query.query, parsedQuery)) { + this.onChange({ ...this.props.query, query: parsedQuery }); + } + + this.initPersistedLog(); + + if (!isEqual(prevProps.indexPatterns, this.props.indexPatterns)) { + this.fetchIndexPatterns(); + } else if (!isEqual(prevProps.query, this.props.query)) { + this.updateSuggestions(); + } + + if (this.state.selectionStart !== null && this.state.selectionEnd !== null) { + if (this.inputRef != null) { + this.inputRef.setSelectionRange(this.state.selectionStart, this.state.selectionEnd); + } + this.setState({ + selectionStart: null, + selectionEnd: null, + }); + } + + if (document.activeElement !== null && document.activeElement.id === this.textareaId) { + this.handleAutoHeight(); + } else { + this.handleRemoveHeight(); + } + } + + public componentWillUnmount() { + if (this.abortController) this.abortController.abort(); + if (this.updateSuggestions.cancel) this.updateSuggestions.cancel(); + this.componentIsUnmounting = true; + window.removeEventListener('resize', this.handleResize); + if (this.hasScrollListener) window.removeEventListener('scroll', this.onOutsideClick); + } + + handleAutoHeight = onRaf(() => { + if (this.inputRef !== null && document.activeElement === this.inputRef) { + this.inputRef.classList.add('kbnQueryBar__textarea--autoHeight'); + this.inputRef.style.setProperty('height', `${this.inputRef.scrollHeight}px`, 'important'); + } + }); + + handleBlurOnScroll = onRaf(() => { + // for small screens, unified search bar is no longer sticky, + // so we need to blur the input when it scrolls out of view + // TODO: replace screen width value with euiTheme breakpoint once this component is converted to a functional component + const isSmallScreen = window.innerWidth < 768; + + if (isSmallScreen && !this.hasScrollListener) { + window.addEventListener('scroll', this.onOutsideClick); + this.hasScrollListener = true; + } else if (!isSmallScreen && this.hasScrollListener) { + window.removeEventListener('scroll', this.onOutsideClick); + this.hasScrollListener = false; + } + }); + + handleRemoveHeight = onRaf(() => { + if (this.inputRef !== null) { + this.inputRef.style.removeProperty('height'); + this.inputRef.classList.remove('kbnQueryBar__textarea--autoHeight'); + } + }); + + handleBlurHeight = onRaf(() => { + if (this.inputRef !== null) { + this.handleRemoveHeight(); + this.inputRef.scrollTop = 0; + } + }); + + handleOnFocus = () => { + if (this.props.onChangeQueryInputFocus) { + this.props.onChangeQueryInputFocus(true); + } + + this.handleAutoHeight(); + }; + + getSearchInputPlaceholder = () => { + if (!this.props.query.language || this.props.query.language === 'text') { + return strings.getSearchInputPlaceholderForText(); + } + const language = + this.props.query.language === 'kuery' ? 'KQL' : toSentenceCase(this.props.query.language); + + return strings.getSearchInputPlaceholder(language); + }; + + public render() { + const isSuggestionsVisible = this.state.isSuggestionsVisible && { + 'aria-controls': 'kbnTypeahead__items', + 'aria-owns': 'kbnTypeahead__items', + }; + const ariaCombobox = { ...isSuggestionsVisible, role: 'combobox' }; + + const simpleLanguageSwitcher = this.props.disableLanguageSwitcher ? null : ( + + ); + + const prependElement = + this.props.prepend || simpleLanguageSwitcher ? ( + + ) : undefined; + + const containerClassName = classNames('kbnQueryBar__wrap', this.props.className); + const inputClassName = classNames('kbnQueryBar__textarea', 'eui-scrollBar', { + 'kbnQueryBar__textarea--withIcon': this.props.iconType, + 'kbnQueryBar__textarea--isClearable': this.props.isClearable, + 'kbnQueryBar__textarea--withPrepend': prependElement, + }); + const inputWrapClassName = classNames('kbnQueryBar__textareaWrap', { + 'kbnQueryBar__textareaWrap--withSuggestionVisible': + isSuggestionsVisible && !isEmpty(this.state.suggestions), + }); + return ( + + {prependElement} + + +
+
+ + {this.forwardNewValueIfNeeded(this.getQueryString())} + + {this.props.iconType ? ( + + ) : null} + {this.props.isClearable && !this.props.isDisabled && this.props.query.query ? ( + { + this.onQueryStringChange(''); + // Force close the dropdown/suggestions + this.setState({ + isSuggestionsVisible: false, + index: null, + }); + if (this.props.autoSubmit) { + this.onSubmit({ query: '', language: this.props.query.language }); + } + }, + title: strings.getQueryBarClearInputLabel(), + }} + /> + ) : null} +
+ + + +
+
+
+ ); + } + + /** + * Used to apply any string formatting to textarea value before converting it to {@link Query} and emitting it to the parent. + * This is a bit lower level then {@link fromUser} and needed to address any cross-browser inconsistencies where + * {@link forwardNewValueIfNeeded} should be kept in mind + */ + private formatTextAreaValue(newValue: string): string { + // Safari has a bug that it sometimes uses a non-breaking space instead of a regular space + // this breaks the search query: https://github.com/elastic/kibana/issues/87176 + return newValue.replace(/\u00A0/g, ' '); + } + + /** + * When passing a "value" prop into a textarea, + * check first if value has changed because of {@link formatTextAreaValue}, + * if this is just a formatting change, then skip this update by re-using current textarea value. + * This is needed to avoid re-rendering to preserve focus and selection + * @internal + */ + private forwardNewValueIfNeeded(newQueryString: string) { + const oldQueryString = this.inputRef?.value ?? ''; + + const formattedNewQueryString = this.formatTextAreaValue(newQueryString); + // if old & new values are equal with formatting applied, then return an old query without formatting applied + if (formattedNewQueryString === this.formatTextAreaValue(oldQueryString)) { + return oldQueryString; + } else { + return formattedNewQueryString; + } + } + + private assignInputRef = (node: HTMLTextAreaElement | null) => { + this.inputRef = node; + }; + + private assignQueryInputDivRef = (node: HTMLDivElement | null) => { + this.setState({ queryBarInputDiv: node }); + }; + + private onFocusWithin = () => { + this.isFocusWithin = true; + }; + + private onBlurWithin = () => { + this.isFocusWithin = false; + this.scheduleOnInputBlur(); + }; +} diff --git a/src/platform/plugins/shared/kql/public/components/query_string_input/to_user.test.ts b/src/platform/plugins/shared/kql/public/components/query_string_input/to_user.test.ts new file mode 100644 index 0000000000000..340c1cfb63c28 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/components/query_string_input/to_user.test.ts @@ -0,0 +1,36 @@ +/* + * 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 { toUser } from './to_user'; + +describe('user input helpers', () => { + describe('model presentation formatter', () => { + test('should present objects as strings', () => { + expect(toUser({ foo: 'bar' })).toBe('{"foo":"bar"}'); + }); + + test('should present query_string queries as strings', () => { + expect(toUser({ query_string: { query: 'lucene query string' } })).toBe( + 'lucene query string' + ); + }); + + test('should present query_string queries without a query as an empty string', () => { + expect(toUser({ query_string: {} })).toBe(''); + }); + + test('should present string as strings', () => { + expect(toUser('foo')).toBe('foo'); + }); + + test('should present numbers as strings', () => { + expect(toUser(400)).toBe('400'); + }); + }); +}); diff --git a/src/platform/plugins/shared/kql/public/components/query_string_input/to_user.ts b/src/platform/plugins/shared/kql/public/components/query_string_input/to_user.ts new file mode 100644 index 0000000000000..a116b7c8ca793 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/components/query_string_input/to_user.ts @@ -0,0 +1,26 @@ +/* + * 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". + */ + +/** + * Take text from the model and present it to the user as a string + * @param text model value + * @returns {string} + */ +export function toUser(text: { [key: string]: any } | string | number): string { + if (text == null) { + return ''; + } + if (typeof text === 'object') { + if (text.query_string) { + return toUser(text.query_string.query); + } + return JSON.stringify(text); + } + return '' + text; +} diff --git a/src/platform/plugins/shared/kql/public/components/query_string_input/use_memo_css.ts b/src/platform/plugins/shared/kql/public/components/query_string_input/use_memo_css.ts new file mode 100644 index 0000000000000..b8da755f9de53 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/components/query_string_input/use_memo_css.ts @@ -0,0 +1,52 @@ +/* + * 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 { useMemo } from 'react'; +import type { CSSInterpolation } from '@emotion/css'; +import type { UseEuiTheme } from '@elastic/eui'; +import { useEuiTheme } from '@elastic/eui'; + +// TODO: Move to use @kbn/css-utils when available https://github.com/elastic/kibana/pull/223933 + +export type EmotionStyles = Record< + string, + CSSInterpolation | ((theme: UseEuiTheme) => CSSInterpolation) +>; + +type StaticEmotionStyles = Record; + +/** + * Custom hook to reduce boilerplate when working with Emotion styles that may depend on + * the EUI theme. + * + * Accepts a map of styles where each entry is either a static Emotion style (via `css`) + * or a function that returns styles based on the current `euiTheme`. + * + * It returns a memoized version of the style map with all values resolved to static + * Emotion styles, allowing components to use a clean and unified object for styling. + * + * This helps simplify component code by centralizing theme-aware style logic. + * + * Example usage: + * const componentStyles = { + * container: css({ overflow: hidden }), + * leftPane: ({ euiTheme }) => css({ paddingTop: euiTheme.size.m }), + * } + * const styles = useMemoCss(componentStyles); + */ +export const useMemoCss = (styleMap: EmotionStyles) => { + const euiThemeContext = useEuiTheme(); + const outputStyles = useMemo(() => { + return Object.entries(styleMap).reduce((acc, [key, value]) => { + acc[key] = typeof value === 'function' ? value(euiThemeContext) : value; + return acc; + }, {}); + }, [euiThemeContext, styleMap]); + return outputStyles; +}; diff --git a/src/platform/plugins/shared/kql/public/components/typeahead/constants.ts b/src/platform/plugins/shared/kql/public/components/typeahead/constants.ts new file mode 100644 index 0000000000000..b815a90c0d400 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/components/typeahead/constants.ts @@ -0,0 +1,26 @@ +/* + * 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". + */ + +/** + * Minimum width in px to display suggestion description correctly + * @public + */ +export const SUGGESTIONS_LIST_REQUIRED_WIDTH = 600; + +/** + * Minimum bottom distance in px to display list of suggestions + * @public + */ +export const SUGGESTIONS_LIST_REQUIRED_BOTTOM_SPACE = 250; + +/** + * A distance in px to display suggestions list right under the query input without a gap + * @public + */ +export const SUGGESTIONS_LIST_REQUIRED_TOP_OFFSET = 0; diff --git a/src/platform/plugins/shared/kql/public/components/typeahead/index.tsx b/src/platform/plugins/shared/kql/public/components/typeahead/index.tsx new file mode 100644 index 0000000000000..b8043a707a3ee --- /dev/null +++ b/src/platform/plugins/shared/kql/public/components/typeahead/index.tsx @@ -0,0 +1,24 @@ +/* + * 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 React from 'react'; + +const Fallback = () =>
; + +const LazySuggestionsComponent = React.lazy(async () => { + const { SuggestionsComponent } = await import('./suggestions_component'); + return { default: SuggestionsComponent }; +}); +export const SuggestionsComponent = ( + props: React.ComponentProps +) => ( + }> + + +); diff --git a/src/platform/plugins/shared/kql/public/components/typeahead/suggestion_component.test.tsx b/src/platform/plugins/shared/kql/public/components/typeahead/suggestion_component.test.tsx new file mode 100644 index 0000000000000..50fbe7b3dd715 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/components/typeahead/suggestion_component.test.tsx @@ -0,0 +1,136 @@ +/* + * 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 React from 'react'; +import { render, screen } from '@testing-library/react'; +import { SuggestionComponent } from './suggestion_component'; +import type { QuerySuggestion } from '../autocomplete'; +import { QuerySuggestionTypes } from '../autocomplete'; +import { userEvent } from '@testing-library/user-event'; + +const noop = () => {}; + +const mockSuggestion: QuerySuggestion = { + description: 'This is not a helpful suggestion', + end: 0, + start: 42, + text: 'as promised, not helpful', + type: QuerySuggestionTypes.Value, +}; + +describe('SuggestionComponent', () => { + it('displays the suggestion and uses the provided ariaId', () => { + render( + + ); + + const item = screen.getByText(/as promised, not helpful/i); + expect(item).toBeInTheDocument(); + expect(screen.getByRole('option', { name: /as promised, not helpful/i })).toHaveAttribute( + 'id', + 'suggestion-1' + ); + }); + + it('marks element as active when selected', () => { + render( + + ); + expect(screen.getByRole('option', { name: /as promised, not helpful/i })).toHaveAttribute( + 'id', + 'suggestion-1' + ); + expect(screen.getByRole('option', { name: /as promised, not helpful/i })).toHaveClass( + 'kbnTypeahead__item active' + ); + }); + + it('calls innerRef with the reference to the root element', () => { + const innerRefMock = jest.fn(); + + render( + + ); + + expect(innerRefMock).toHaveBeenCalledTimes(1); + const [indexArg, elementArg] = innerRefMock.mock.calls[0]; + expect(indexArg).toBe(0); + expect(elementArg).toBeInstanceOf(HTMLDivElement); + expect(elementArg?.id).toBe('suggestion-1'); + expect(elementArg?.className).toContain('kbnTypeahead__item'); + }); + + it('calls onClick with suggestion and index', async () => { + const clickHandler = jest.fn(); + + render( + + ); + + await userEvent.click(screen.getByText(/as promised, not helpful/i)); + expect(clickHandler).toHaveBeenCalledWith(mockSuggestion, 0); + expect(clickHandler).toHaveBeenCalledTimes(1); + }); + + it('calls onMouseEnter when user hovers the element', async () => { + const mouseEnterHandler = jest.fn(); + + render( + + ); + + await userEvent.hover(screen.getByText(/as promised, not helpful/i)); + expect(mouseEnterHandler).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/platform/plugins/shared/kql/public/components/typeahead/suggestion_component.tsx b/src/platform/plugins/shared/kql/public/components/typeahead/suggestion_component.tsx new file mode 100644 index 0000000000000..ae0b561cd2ad8 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/components/typeahead/suggestion_component.tsx @@ -0,0 +1,254 @@ +/* + * 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 type { UseEuiTheme } from '@elastic/eui'; +import { EuiIcon, euiFontSize } from '@elastic/eui'; +import classNames from 'classnames'; +import React, { useCallback } from 'react'; +import { css } from '@emotion/react'; +import type { EmotionStyles } from '../use_memo_css'; +import { useMemoCss } from '../use_memo_css'; +import type { QuerySuggestion } from '../autocomplete'; +import type { SuggestionOnClick, SuggestionOnMouseEnter } from './types'; + +function getEuiIconType(type: string) { + switch (type) { + case 'field': + return 'kqlField'; + case 'value': + return 'kqlValue'; + case 'recentSearch': + return 'search'; + case 'conjunction': + return 'kqlSelector'; + case 'operator': + return 'kqlOperand'; + default: + throw new Error(`Unknown type: ${type}`); + } +} + +interface Props { + onClick: SuggestionOnClick; + onMouseEnter: SuggestionOnMouseEnter; + selected: boolean; + index: number; + suggestion: QuerySuggestion; + innerRef: (index: number, node: HTMLDivElement) => void; + ariaId: string; + shouldDisplayDescription: boolean; +} + +export const SuggestionComponent = React.memo(function SuggestionComponent(props: Props) { + const { index, innerRef, onClick, onMouseEnter, suggestion } = props; + const setRef = useCallback( + (node: HTMLDivElement) => { + innerRef(index, node); + }, + [index, innerRef] + ); + + const handleClick = useCallback(() => { + onClick(suggestion, index); + }, [index, onClick, suggestion]); + + const handleMouseEnter = useCallback(() => { + onMouseEnter(suggestion, index); + }, [index, onMouseEnter, suggestion]); + + const styles = useMemoCss(suggestionStyles); + + return ( + // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/interactive-supports-focus +
+
+
+ +
+
+ {props.suggestion.text} +
+ {props.shouldDisplayDescription && ( +
+ {props.suggestion.description} +
+ )} +
+
+ ); +}); + +// These are the various types in the dropdown, they each get a color +const kbnTypeaheadTypes = { + field: { + base: 'backgroundBaseWarning' as const, + active: 'backgroundLightWarning' as const, + text: 'textWarning' as const, + }, + value: { + base: 'backgroundBaseSuccess' as const, + active: 'backgroundLightSuccess' as const, + text: 'textSuccess' as const, + }, + operator: { + base: 'backgroundBasePrimary' as const, + active: 'backgroundLightPrimary' as const, + text: 'textPrimary' as const, + }, + conjunction: { + base: 'backgroundBaseSubdued' as const, + active: 'backgroundLightText' as const, + text: 'textSubdued' as const, + }, + recentSearch: { + base: 'backgroundBaseSubdued' as const, + active: 'backgroundLightText' as const, + text: 'textSubdued' as const, + }, +}; + +const activeColors = (context: UseEuiTheme) => + Object.entries(kbnTypeaheadTypes).map(([type, color]) => { + return { + [`.kbnSuggestionItem--${type}`]: { + '.kbnSuggestionItem__type': { + backgroundColor: context.euiTheme.colors[color.active], + }, + }, + }; + }); + +const tokenColors = (context: UseEuiTheme) => + Object.entries(kbnTypeaheadTypes).map(([type, color]) => { + return { + [`&.kbnSuggestionItem--${type}`]: { + '.kbnSuggestionItem__type': { + backgroundColor: context.euiTheme.colors[color.base], + color: context.euiTheme.colors[color.text], + }, + }, + }; + }); + +const suggestionStyles: EmotionStyles = { + suggestionItem: (context: UseEuiTheme) => + css({ + '&.kbnTypeahead__item': { + height: context.euiTheme.size.xl, + whiteSpace: 'nowrap', + fontSize: euiFontSize(context, 'xs').fontSize, + verticalAlign: 'middle', + padding: 0, + borderBottom: 'none', + lineHeight: 'normal', + '&:hover': { + cursor: 'pointer', + }, + '&:last-child': { + borderBottom: 'none', + borderRadius: `0 0 ${context.euiTheme.border.radius.medium} ${context.euiTheme.border.radius.medium}`, + }, + '&:first-child': { + borderBottom: 'none', + }, + + '&.active': css([ + { + backgroundColor: context.euiTheme.colors.lightestShade, + '.kbnSuggestionItem__callout': { + background: context.euiTheme.colors.emptyShade, + }, + '.kbnSuggestionItem__text': { + color: context.euiTheme.colors.fullShade, + }, + '.kbnSuggestionItem__type': { + color: context.euiTheme.colors.fullShade, + }, + }, + activeColors(context), + ]), + }, + + '.kbnSuggestionItem': css([ + tokenColors(context), + { + display: 'inline-flex', + alignItems: 'center', + fontSize: euiFontSize(context, 'xs').fontSize, + whiteSpace: 'nowrap', + width: '100%', + }, + ]), + '.kbnSuggestionItem__type': { + display: 'flex', + flexDirection: 'column', + flexGrow: 0, + flexShrink: 0, + flexBasis: 'auto', + width: context.euiTheme.size.xl, + height: context.euiTheme.size.xl, + textAlign: 'center', + overflow: 'hidden', + justifyContent: 'center', + alignItems: 'center', + padding: context.euiTheme.size.xs, + }, + '.kbnSuggestionItem__text': { + fontFamily: context.euiTheme.font.familyCode, + overflow: 'hidden', + textOverflow: 'ellipsis', + paddingLeft: context.euiTheme.size.s, + color: context.euiTheme.colors.text, + minWidth: '250px', + }, + '.kbnSuggestionItem__description': { + color: context.euiTheme.colors.darkShade, + overflow: 'hidden', + textOverflow: 'ellipsis', + flexShrink: 1, + // In case the description contains a paragraph in which the truncation needs to be at this level + '> p': { + overflow: 'hidden', + textOverflow: 'ellipsis', + }, + '&:empty': { + width: 0, + }, + }, + '.kbnSuggestionItem__callout': { + fontFamily: context.euiTheme.font.familyCode, + background: context.euiTheme.colors.lightestShade, + color: context.euiTheme.colors.fullShade, + padding: `0 ${context.euiTheme.size.xs}`, + display: 'inline-block', + }, + }), +}; diff --git a/src/platform/plugins/shared/kql/public/components/typeahead/suggestions_component.test.tsx b/src/platform/plugins/shared/kql/public/components/typeahead/suggestions_component.test.tsx new file mode 100644 index 0000000000000..11712960e0f8a --- /dev/null +++ b/src/platform/plugins/shared/kql/public/components/typeahead/suggestions_component.test.tsx @@ -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 React from 'react'; +import { render, screen } from '@testing-library/react'; +import type { QuerySuggestion } from '../autocomplete'; +import { QuerySuggestionTypes } from '../autocomplete'; +import { SuggestionsComponent } from './suggestions_component'; +import { EuiThemeProvider } from '@elastic/eui'; +import { userEvent } from '@testing-library/user-event'; + +const noop = () => {}; + +const mockContainerDiv = document.createElement('div'); + +const mockSuggestions: QuerySuggestion[] = [ + { + description: 'This is not a helpful suggestion', + end: 0, + start: 42, + text: 'as promised, not helpful', + type: QuerySuggestionTypes.Value, + }, + { + description: 'This is another unhelpful suggestion', + end: 0, + start: 42, + text: 'yep', + type: QuerySuggestionTypes.Field, + }, +]; + +const renderWithTheme = (ui: React.ReactElement) => { + return render({ui}); +}; + +describe('SuggestionsComponent', () => { + it('Should not render if show is false', () => { + const { container } = renderWithTheme( + + ); + expect(container).toBeEmptyDOMElement(); + }); + + it('Should not render if there are no suggestions', () => { + const { container } = renderWithTheme( + + ); + expect(container).toBeEmptyDOMElement(); + }); + + it('Should render suggestions when show is true', () => { + renderWithTheme( + + ); + const items = screen.getAllByRole('option'); + expect(items).toHaveLength(2); + }); + + it('Should apply selection based on index prop', () => { + renderWithTheme( + + ); + + const selected = screen.getAllByRole('option')[1]; + expect(selected.getAttribute('aria-selected')).toBe('true'); + }); + + it('Should call onClick with selected suggestion when clicked', async () => { + const mockClick = jest.fn(); + renderWithTheme( + + ); + const item = screen.getAllByRole('option')[1]; + await userEvent.click(item); + expect(mockClick).toHaveBeenCalledWith(mockSuggestions[1], 1); + }); + + it('Should call onMouseEnter with correct index when suggestion is hovered', async () => { + const mockEnter = jest.fn(); + renderWithTheme( + + ); + const item = screen.getAllByRole('option')[1]; + await userEvent.hover(item); + expect(mockEnter).toHaveBeenCalledWith(mockSuggestions[1], 1); + }); +}); diff --git a/src/platform/plugins/shared/kql/public/components/typeahead/suggestions_component.tsx b/src/platform/plugins/shared/kql/public/components/typeahead/suggestions_component.tsx new file mode 100644 index 0000000000000..46f52654107f3 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/components/typeahead/suggestions_component.tsx @@ -0,0 +1,335 @@ +/* + * 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 type { ReactNode } from 'react'; +import React, { PureComponent } from 'react'; +import { isEmpty } from 'lodash'; +import classNames from 'classnames'; + +import useRafState from 'react-use/lib/useRafState'; +import type { UseEuiTheme } from '@elastic/eui'; +import { euiShadow, euiShadowFlat } from '@elastic/eui'; +import { css } from '@emotion/react'; +import type { EmotionStyles } from '../use_memo_css'; +import { useMemoCss } from '../use_memo_css'; +import type { QuerySuggestion } from '../autocomplete'; +import { SuggestionComponent } from './suggestion_component'; +import { + SUGGESTIONS_LIST_REQUIRED_BOTTOM_SPACE, + SUGGESTIONS_LIST_REQUIRED_TOP_OFFSET, + SUGGESTIONS_LIST_REQUIRED_WIDTH, +} from './constants'; +import type { SuggestionOnClick, SuggestionOnMouseEnter } from './types'; +import { onRaf, shallowEqual } from '../utils'; + +interface SuggestionsComponentProps { + index: number | null; + onClick: SuggestionOnClick; + onMouseEnter: SuggestionOnMouseEnter; + show: boolean; + suggestions: QuerySuggestion[]; + loadMore: () => void; + size?: SuggestionsListSize; + inputContainer: HTMLElement | null; +} + +export interface SuggestionsAbstraction { + type: 'alerts' | 'rules' | 'cases' | 'endpoints'; + fields: Record< + string, + { + field: string; + fieldToQuery: string; + displayField: string | undefined; + nestedDisplayField?: string; + nestedField?: string; + nestedPath?: string; + } + >; +} + +export type SuggestionsListSize = 's' | 'l'; + +export class SuggestionsComponent extends PureComponent { + private childNodes: HTMLDivElement[] = []; + private parentNode: HTMLDivElement | null = null; + + constructor(props: SuggestionsComponentProps) { + super(props); + + this.assignParentNode = this.assignParentNode.bind(this); + this.assignChildNode = this.assignChildNode.bind(this); + } + + private assignParentNode(node: HTMLDivElement) { + this.parentNode = node; + } + + private assignChildNode(index: number, node: HTMLDivElement) { + this.childNodes[index] = node; + } + + public render() { + if (!this.props.inputContainer || !this.props.show || isEmpty(this.props.suggestions)) { + return null; + } + + const renderSuggestions = (containerWidth: number) => { + const isDescriptionFittable = containerWidth >= SUGGESTIONS_LIST_REQUIRED_WIDTH; + const suggestions = this.props.suggestions.map((suggestion, index) => { + return ( + + ); + }); + + return suggestions; + }; + + return ( + + {(rect: DOMRect) => ( +
+ {renderSuggestions(rect.width)} +
+ )} +
+ ); + } + + public componentDidUpdate(prevProps: SuggestionsComponentProps) { + if (prevProps.index !== this.props.index) { + this.scrollIntoView(); + } + } + + private scrollIntoView = onRaf(() => { + if (this.props.index === null) { + return; + } + const parent = this.parentNode; + const child = this.childNodes[this.props.index]; + + if (this.props.index == null || !parent || !child) { + return; + } + + const scrollTop = Math.max( + Math.min(parent.scrollTop, child.offsetTop), + child.offsetTop + child.offsetHeight - parent.offsetHeight + ); + + parent.scrollTop = scrollTop; + }); + + private handleScroll = onRaf(() => { + if (!this.props.loadMore || !this.parentNode) { + return; + } + + const position = this.parentNode.scrollTop + this.parentNode.offsetHeight; + const height = this.parentNode.scrollHeight; + const remaining = height - position; + const margin = 50; + + if (!height || !position) { + return; + } + if (remaining <= margin) { + this.props.loadMore(); + } + }); +} + +const ResizableSuggestionsListDiv: React.FC<{ + inputContainer: HTMLElement; + suggestionsSize?: SuggestionsListSize; + children: (rect: DOMRect) => ReactNode; +}> = React.memo((props) => { + const inputContainer = props.inputContainer; + + const [{ documentHeight }, { pageYOffset }, containerRect] = useDimensions(inputContainer); + const styles = useMemoCss(suggestionsStyles); + + if (!containerRect) return null; + + // reflects if the suggestions list has enough space below to be opened down + const isSuggestionsListFittable = + documentHeight - (containerRect.top + containerRect.height) > + SUGGESTIONS_LIST_REQUIRED_BOTTOM_SPACE; + const verticalListPosition = isSuggestionsListFittable + ? { top: `${pageYOffset + containerRect.bottom - SUGGESTIONS_LIST_REQUIRED_TOP_OFFSET}px` } + : { bottom: `${documentHeight - (pageYOffset + containerRect.top)}px` }; + + return ( +
+
+
+
+ {props.children(containerRect)} +
+
+
+
+ ); +}); + +function useDimensions( + container: HTMLElement | null +): [{ documentHeight: number }, { pageYOffset: number; pageXOffset: number }, DOMRect | null] { + const [documentHeight, setDocumentHeight] = useRafState( + () => document.documentElement.clientHeight || window.innerHeight + ); + + const [pageOffset, setPageOffset] = useRafState<{ pageXOffset: number; pageYOffset: number }>( + () => ({ + pageXOffset: window.pageXOffset, + pageYOffset: window.pageYOffset, + }) + ); + + const [containerRect, setContainerRect] = useRafState(() => { + return container?.getBoundingClientRect() ?? null; + }); + + const updateContainerRect = React.useCallback(() => { + setContainerRect((oldRect: DOMRect | null) => { + const newRect = container?.getBoundingClientRect() ?? null; + const rectsEqual = shallowEqual(oldRect?.toJSON(), newRect?.toJSON()); + return rectsEqual ? oldRect : newRect; + }); + }, [container, setContainerRect]); + + React.useEffect(() => { + const handler = () => { + setDocumentHeight(document.documentElement.clientHeight || window.innerHeight); + }; + + window.addEventListener('resize', handler, { passive: true }); + + return () => { + window.removeEventListener('resize', handler); + }; + }, [setDocumentHeight]); + + React.useEffect(() => { + const handler = () => { + setPageOffset((state) => { + const { pageXOffset, pageYOffset } = window; + return state.pageXOffset !== pageXOffset || state.pageYOffset !== pageYOffset + ? { + pageXOffset, + pageYOffset, + } + : state; + }); + + updateContainerRect(); + }; + + window.addEventListener('scroll', handler, { passive: true, capture: true }); + + const resizeObserver = + typeof window.ResizeObserver !== 'undefined' && + new ResizeObserver(() => { + updateContainerRect(); + }); + if (container && resizeObserver) { + resizeObserver.observe(container); + } + + return () => { + window.removeEventListener('scroll', handler, { capture: true }); + if (resizeObserver) resizeObserver.disconnect(); + }; + }, [setPageOffset, container, updateContainerRect]); + + return [{ documentHeight }, pageOffset, containerRect]; +} + +const suggestionsStyles: EmotionStyles = { + container: (context: UseEuiTheme) => + css({ + position: 'absolute', + zIndex: 4001, + '.kbnTypeahead': { + maxHeight: '60vh', + '&.kbnTypeahead--small': { + maxHeight: '20vh', + }, + }, + '.kbnTypeahead__popover': { + maxHeight: 'inherit', + border: `1px solid ${context.euiTheme.colors.borderBaseSubdued}`, + color: context.euiTheme.colors.text, + backgroundColor: context.euiTheme.colors.emptyShade, + position: 'relative', + zIndex: context.euiTheme.levels.menu, + width: '100%', + overflow: 'hidden', + + '.kbnTypeahead__popover-content': { + maxHeight: 'inherit', + overflowY: 'auto', + }, + + '&.kbnTypeahead__popover--top': css([ + euiShadowFlat(context, { border: 'none' }), + { + borderTopLeftRadius: context.euiTheme.border.radius.medium, + borderTopRightRadius: context.euiTheme.border.radius.medium, + // Clips the shadow so it doesn't show above the input (below) + clipPath: `polygon(-50px -50px, calc(100% + 50px) -50px, calc(100% + 50px) 100%, -50px 100%)`, + }, + ]), + '&.kbnTypeahead__popover--bottom': css([ + euiShadow(context, 'l', { border: 'none' }), + { + borderBottomLeftRadius: context.euiTheme.border.radius.medium, + borderBottomRightRadius: context.euiTheme.border.radius.medium, + // Clips the shadow so it doesn't show above the input (top) + clipPath: `polygon(-50px 1px, calc(100% + 50px) 1px, calc(100% + 50px) calc(100% + 50px), -50px calc(100% + 50px))`, + }, + ]), + }, + }), +}; diff --git a/src/platform/plugins/shared/kql/public/components/typeahead/types.ts b/src/platform/plugins/shared/kql/public/components/typeahead/types.ts new file mode 100644 index 0000000000000..20082382333ee --- /dev/null +++ b/src/platform/plugins/shared/kql/public/components/typeahead/types.ts @@ -0,0 +1,14 @@ +/* + * 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 type { QuerySuggestion } from '../autocomplete'; + +export type SuggestionOnClick = (suggestion: QuerySuggestion, index: number) => void; + +export type SuggestionOnMouseEnter = (suggestion: QuerySuggestion, index: number) => void; diff --git a/src/platform/plugins/shared/kql/public/components/utils/combined_filter.ts b/src/platform/plugins/shared/kql/public/components/utils/combined_filter.ts new file mode 100644 index 0000000000000..944680c9a4dbe --- /dev/null +++ b/src/platform/plugins/shared/kql/public/components/utils/combined_filter.ts @@ -0,0 +1,21 @@ +/* + * 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 type { CombinedFilter } from '@kbn/es-query'; +import { type Filter, isCombinedFilter } from '@kbn/es-query'; + +/** + * Defines a boolean relation type (AND/OR) from the filter otherwise returns undefined. + * @param {Filter} filter + */ +export const getBooleanRelationType = (filter: Filter | CombinedFilter) => { + if (isCombinedFilter(filter)) { + return filter.meta.relation; + } +}; diff --git a/src/platform/plugins/shared/kql/public/components/utils/index.ts b/src/platform/plugins/shared/kql/public/components/utils/index.ts new file mode 100644 index 0000000000000..bb85ea4c62025 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/components/utils/index.ts @@ -0,0 +1,13 @@ +/* + * 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". + */ + +export { onRaf } from './on_raf'; +export { shallowEqual } from './shallow_equal'; + +export { getBooleanRelationType } from './combined_filter'; diff --git a/src/platform/plugins/shared/kql/public/components/utils/on_raf.ts b/src/platform/plugins/shared/kql/public/components/utils/on_raf.ts new file mode 100644 index 0000000000000..2fe96ef7ce38d --- /dev/null +++ b/src/platform/plugins/shared/kql/public/components/utils/on_raf.ts @@ -0,0 +1,23 @@ +/* + * 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". + */ + +/** + * Debounce a function till next animation frame + * @param fn + */ +export function onRaf(fn: Function) { + let req: number | null; + return (...args: unknown[]) => { + if (req) window.cancelAnimationFrame(req); + req = window.requestAnimationFrame(() => { + req = null; + fn(...args); + }); + }; +} diff --git a/src/platform/plugins/shared/kql/public/components/utils/shallow_equal.ts b/src/platform/plugins/shared/kql/public/components/utils/shallow_equal.ts new file mode 100644 index 0000000000000..9c079464c46f0 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/components/utils/shallow_equal.ts @@ -0,0 +1,37 @@ +/* + * 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". + */ + +/** + * Shallow Equal check adapted from react-redux + * Copy-pasted to avoid importing copy of react-redux into data plugin async chunk + **/ +export function shallowEqual(objA: unknown, objB: unknown): boolean { + if (Object.is(objA, objB)) return true; + + if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) { + return false; + } + + const keysA = Object.keys(objA); + const keysB = Object.keys(objB); + + if (keysA.length !== keysB.length) return false; + + for (let i = 0; i < keysA.length; i++) { + if ( + !Object.hasOwn(objB, keysA[i]) || + // @ts-ignore + !Object.is(objA[keysA[i]], objB[keysA[i]]) + ) { + return false; + } + } + + return true; +} diff --git a/src/platform/plugins/shared/kql/public/plugin.ts b/src/platform/plugins/shared/kql/public/plugin.ts new file mode 100644 index 0000000000000..8780a8a0819c9 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/plugin.ts @@ -0,0 +1,80 @@ +/* + * 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 type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public'; +import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; +import { Storage } from '@kbn/kibana-utils-plugin/public'; +import type { DataPublicPluginStart, DataPublicPluginSetup } from '@kbn/data-plugin/public'; +import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; +import { createQueryStringInput } from './components/query_string_input/get_query_string_input'; +import type { QueryStringInputProps } from './components/query_string_input/query_string_input'; +import { AutocompleteService, type AutocompleteStart } from './autocomplete/autocomplete_service'; + +interface KqlPluginSetupDependencies { + data: DataPublicPluginSetup; + usageCollection?: UsageCollectionSetup; +} + +interface KqlPluginStartDependencies { + data: DataPublicPluginStart; + dataViews: DataViewsPublicPluginStart; +} + +export interface KqlPluginStart { + /** + * autocomplete service + * {@link AutocompleteStart} + */ + autocomplete: AutocompleteStart; + QueryStringInput: React.ComponentType>; +} + +export class KqlPlugin implements Plugin<{}, KqlPluginStart> { + private readonly autocomplete: AutocompleteService; + private readonly storage: IStorageWrapper; + + constructor(initContext: PluginInitializerContext) { + this.autocomplete = new AutocompleteService(initContext); + this.storage = new Storage(window.localStorage); + } + + public setup(core: CoreSetup, { data, usageCollection }: KqlPluginSetupDependencies) { + const { query } = data; + return { + autocomplete: this.autocomplete.setup(core, { + timefilter: query.timefilter, + usageCollection, + }), + }; + } + + public start(core: CoreStart, { data, dataViews }: KqlPluginStartDependencies): KqlPluginStart { + const autocompleteStart = this.autocomplete.start(); + + return { + autocomplete: autocompleteStart, + QueryStringInput: createQueryStringInput({ + data, + dataViews, + docLinks: core.docLinks, + http: core.http, + notifications: core.notifications, + storage: this.storage, + uiSettings: core.uiSettings, + autocomplete: autocompleteStart, + core, + }), + }; + } + + public stop() { + this.autocomplete.clearProviders(); + } +} diff --git a/src/platform/plugins/shared/kql/server/autocomplete/autocomplete_service.ts b/src/platform/plugins/shared/kql/server/autocomplete/autocomplete_service.ts new file mode 100644 index 0000000000000..0f2fe823363f3 --- /dev/null +++ b/src/platform/plugins/shared/kql/server/autocomplete/autocomplete_service.ts @@ -0,0 +1,45 @@ +/* + * 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 moment from 'moment'; +import { clone } from 'lodash'; +import type { CoreSetup, Plugin, PluginInitializerContext } from '@kbn/core/server'; +import { registerRoutes } from './routes'; +import type { ConfigSchema } from '../config'; + +export class AutocompleteService implements Plugin { + private valueSuggestionsEnabled: boolean = true; + private autocompleteSettings: ConfigSchema['autocomplete']['valueSuggestions']; + + constructor(private initializerContext: PluginInitializerContext) { + initializerContext.config.create().subscribe((configUpdate) => { + this.valueSuggestionsEnabled = configUpdate.autocomplete.valueSuggestions.enabled; + this.autocompleteSettings = configUpdate.autocomplete.valueSuggestions; + }); + this.autocompleteSettings = + this.initializerContext.config.get().autocomplete.valueSuggestions; + } + + public setup(core: CoreSetup) { + if (this.valueSuggestionsEnabled) registerRoutes(core, this.initializerContext.config.create()); + const { terminateAfter, timeout } = this.autocompleteSettings; + return { + getAutocompleteSettings: () => ({ + terminateAfter: moment.duration(terminateAfter).asMilliseconds(), + timeout: moment.duration(timeout).asMilliseconds(), + }), + getInitializerContextConfig: () => clone(this.initializerContext.config), + }; + } + + public start() {} +} + +/** @public **/ +export type AutocompleteSetup = ReturnType; diff --git a/src/platform/plugins/shared/kql/server/autocomplete/index.ts b/src/platform/plugins/shared/kql/server/autocomplete/index.ts new file mode 100644 index 0000000000000..0dbb22a4cf1b3 --- /dev/null +++ b/src/platform/plugins/shared/kql/server/autocomplete/index.ts @@ -0,0 +1,10 @@ +/* + * 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". + */ + +export { AutocompleteService, type AutocompleteSetup } from './autocomplete_service'; diff --git a/src/platform/plugins/shared/kql/server/autocomplete/routes.ts b/src/platform/plugins/shared/kql/server/autocomplete/routes.ts new file mode 100644 index 0000000000000..ab8a9af50d39e --- /dev/null +++ b/src/platform/plugins/shared/kql/server/autocomplete/routes.ts @@ -0,0 +1,19 @@ +/* + * 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 type { Observable } from 'rxjs'; +import type { CoreSetup } from '@kbn/core/server'; +import { registerValueSuggestionsRoute } from './value_suggestions_route'; +import type { ConfigSchema } from '../config'; + +export function registerRoutes({ http }: CoreSetup, config$: Observable): void { + const router = http.createRouter(); + + registerValueSuggestionsRoute(router, config$); +} diff --git a/src/platform/plugins/shared/kql/server/autocomplete/terms_agg.test.ts b/src/platform/plugins/shared/kql/server/autocomplete/terms_agg.test.ts new file mode 100644 index 0000000000000..fb836897d8722 --- /dev/null +++ b/src/platform/plugins/shared/kql/server/autocomplete/terms_agg.test.ts @@ -0,0 +1,155 @@ +/* + * 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 { coreMock } from '@kbn/core/server/mocks'; +import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; +import type { ConfigSchema } from '../config'; +import type { DeeplyMockedKeys } from '@kbn/utility-types-jest'; +import type { DataViewField, FieldSpec } from '@kbn/data-views-plugin/common'; +import { termsAggSuggestions } from './terms_agg'; +import type { estypes } from '@elastic/elasticsearch'; +import { duration } from 'moment'; + +let savedObjectsClientMock: jest.Mocked; +let esClientMock: DeeplyMockedKeys; +const configMock = { + autocomplete: { + valueSuggestions: { timeout: duration(4513), terminateAfter: duration(98430) }, + }, +} as unknown as ConfigSchema; + +const dataViewFieldMock = { name: 'field_name', type: 'string' } as DataViewField; + +// @ts-expect-error not full interface +const mockResponse = { + aggregations: { + suggestions: { + buckets: [{ key: 'whoa' }, { key: 'amazing' }], + }, + }, +} as estypes.SearchResponse; + +jest.mock('../data_views'); + +describe('terms agg suggestions', () => { + beforeEach(() => { + const requestHandlerContext = coreMock.createRequestHandlerContext(); + savedObjectsClientMock = requestHandlerContext.savedObjects.client; + esClientMock = requestHandlerContext.elasticsearch.client.asCurrentUser; + esClientMock.search.mockResolvedValue(mockResponse); + }); + + it('calls the _search API with a terms agg with the given args', async () => { + const result = await termsAggSuggestions( + configMock, + savedObjectsClientMock, + esClientMock, + 'index', + 'fieldName', + 'query', + [], + dataViewFieldMock + ); + + const [[args]] = esClientMock.search.mock.calls; + + expect(args).toMatchInlineSnapshot(` + Object { + "aggs": Object { + "suggestions": Object { + "terms": Object { + "execution_hint": "map", + "field": "field_name", + "include": "query.*", + "shard_size": 10, + }, + }, + }, + "index": "index", + "query": Object { + "bool": Object { + "filter": Array [], + }, + }, + "size": 0, + "terminate_after": 98430, + "timeout": "4513ms", + } + `); + expect(result).toMatchInlineSnapshot(` + Array [ + "whoa", + "amazing", + ] + `); + }); + + it('calls the _search API with a terms agg and fallback to fieldName when field is null', async () => { + const result = await termsAggSuggestions( + configMock, + savedObjectsClientMock, + esClientMock, + 'index', + 'fieldName', + 'query', + [] + ); + + const [[args]] = esClientMock.search.mock.calls; + + expect(args).toMatchInlineSnapshot(` + Object { + "aggs": Object { + "suggestions": Object { + "terms": Object { + "execution_hint": "map", + "field": "fieldName", + "include": "query.*", + "shard_size": 10, + }, + }, + }, + "index": "index", + "query": Object { + "bool": Object { + "filter": Array [], + }, + }, + "size": 0, + "terminate_after": 98430, + "timeout": "4513ms", + } + `); + expect(result).toMatchInlineSnapshot(` + Array [ + "whoa", + "amazing", + ] + `); + }); + + it('does not call the _search API when the field is an IP', async () => { + const result = await termsAggSuggestions( + configMock, + savedObjectsClientMock, + esClientMock, + 'index', + 'fieldName', + 'query', + [], + { + type: 'ip', + name: 'fieldName', + } as FieldSpec + ); + + expect(esClientMock.search).not.toHaveBeenCalled(); + expect(result).toMatchInlineSnapshot(`Array []`); + }); +}); diff --git a/src/platform/plugins/shared/kql/server/autocomplete/terms_agg.ts b/src/platform/plugins/shared/kql/server/autocomplete/terms_agg.ts new file mode 100644 index 0000000000000..bf1994e78c2f1 --- /dev/null +++ b/src/platform/plugins/shared/kql/server/autocomplete/terms_agg.ts @@ -0,0 +1,115 @@ +/* + * 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 { get, map } from 'lodash'; +import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; +import type { estypes } from '@elastic/elasticsearch'; +import { getFieldSubtypeNested } from '@kbn/data-plugin/common'; +import type { FieldSpec } from '@kbn/data-views-plugin/common'; +import type { ConfigSchema } from '../config'; +import { findIndexPatternById, getFieldByName } from '../data_views'; + +export async function termsAggSuggestions( + config: ConfigSchema, + savedObjectsClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + index: string, + fieldName: string, + query: string, + filters?: estypes.QueryDslQueryContainer[], + field?: FieldSpec, + abortSignal?: AbortSignal +) { + const autocompleteSearchOptions = { + timeout: `${config.autocomplete.valueSuggestions.timeout.asMilliseconds()}ms`, + terminate_after: config.autocomplete.valueSuggestions.terminateAfter.asMilliseconds(), + }; + + if (!field?.name && !field?.type) { + const indexPattern = await findIndexPatternById(savedObjectsClient, index); + + field = indexPattern && getFieldByName(fieldName, indexPattern); + } + + // Terms agg doesn't support IP with "exclude"/"include" parameter + if (field?.type === 'ip') { + return []; + } + + const body = await getBody(autocompleteSearchOptions, field ?? fieldName, query, filters); + + const result = await esClient.search( + { index, ...body }, + { + signal: abortSignal, + } + ); + + const buckets = + get(result, 'aggregations.suggestions.buckets') || + get(result, 'aggregations.nestedSuggestions.suggestions.buckets'); + + return map(buckets ?? [], 'key'); +} + +async function getBody( + { timeout, terminate_after }: Record, + field: FieldSpec | string, + query: string, + filters: estypes.QueryDslQueryContainer[] = [] +) { + const isFieldObject = (f: any): f is FieldSpec => Boolean(f && f.name); + + // https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html#_standard_operators + const getEscapedQuery = (q: string = '') => + q.replace(/[.?+*|{}[\]()"\\#@&<>~]/g, (match) => `\\${match}`); + + // Helps ensure that the regex is not evaluated eagerly against the terms dictionary + const executionHint = 'map' as const; + + // We don't care about the accuracy of the counts, just the content of the terms, so this reduces + // the amount of information that needs to be transmitted to the coordinating node + const shardSize = 10; + const body = { + size: 0, + timeout, + terminate_after, + query: { + bool: { + filter: filters, + }, + }, + aggs: { + suggestions: { + terms: { + field: isFieldObject(field) ? field.name : field, + include: `${getEscapedQuery(query)}.*`, + execution_hint: executionHint, + shard_size: shardSize, + }, + }, + }, + }; + const subTypeNested = isFieldObject(field) && getFieldSubtypeNested(field); + if (isFieldObject(field) && subTypeNested) { + return { + ...body, + aggs: { + nestedSuggestions: { + nested: { + path: subTypeNested.nested.path, + }, + aggs: body.aggs, + }, + }, + }; + } + + return body; +} diff --git a/src/platform/plugins/shared/kql/server/autocomplete/terms_enum.test.ts b/src/platform/plugins/shared/kql/server/autocomplete/terms_enum.test.ts new file mode 100644 index 0000000000000..84326792414de --- /dev/null +++ b/src/platform/plugins/shared/kql/server/autocomplete/terms_enum.test.ts @@ -0,0 +1,113 @@ +/* + * 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 { termsEnumSuggestions } from './terms_enum'; +import { coreMock } from '@kbn/core/server/mocks'; +import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; +import type { ConfigSchema } from '../config'; +import type { DeeplyMockedKeys } from '@kbn/utility-types-jest'; +import type { TermsEnumResponse } from '@elastic/elasticsearch/lib/api/types'; +import type { DataViewField } from '@kbn/data-views-plugin/common'; + +let savedObjectsClientMock: jest.Mocked; +let esClientMock: DeeplyMockedKeys; +const configMock = { + autocomplete: { valueSuggestions: { tiers: ['data_hot', 'data_warm', 'data_content'] } }, +} as ConfigSchema; +const dataViewFieldMock = { + name: 'field_name', + type: 'string', + searchable: true, + aggregatable: true, +} as DataViewField; +const mockResponse = { terms: ['whoa', 'amazing'] }; + +jest.mock('../data_views'); + +describe('_terms_enum suggestions', () => { + beforeEach(() => { + const requestHandlerContext = coreMock.createRequestHandlerContext(); + savedObjectsClientMock = requestHandlerContext.savedObjects.client; + esClientMock = requestHandlerContext.elasticsearch.client.asCurrentUser; + esClientMock.termsEnum.mockResolvedValue(mockResponse as unknown as TermsEnumResponse); + }); + + it('calls the _terms_enum API with the field, query, filters, and config tiers', async () => { + const result = await termsEnumSuggestions( + configMock, + savedObjectsClientMock, + esClientMock, + 'index', + 'fieldName', + 'query', + [], + dataViewFieldMock + ); + + const [[args]] = esClientMock.termsEnum.mock.calls; + + expect(args).toMatchInlineSnapshot(` + Object { + "field": "field_name", + "index": "index", + "index_filter": Object { + "bool": Object { + "must": Array [], + "must_not": Object { + "terms": Object { + "_tier": Array [ + "data_cold", + "data_frozen", + ], + }, + }, + }, + }, + "string": "query", + } + `); + expect(result).toEqual(mockResponse.terms); + }); + + it('calls the _terms_enum API and fallback to fieldName when field is null', async () => { + const result = await termsEnumSuggestions( + configMock, + savedObjectsClientMock, + esClientMock, + 'index', + 'fieldName', + 'query', + [] + ); + + const [[args]] = esClientMock.termsEnum.mock.calls; + + expect(args).toMatchInlineSnapshot(` + Object { + "field": "fieldName", + "index": "index", + "index_filter": Object { + "bool": Object { + "must": Array [], + "must_not": Object { + "terms": Object { + "_tier": Array [ + "data_cold", + "data_frozen", + ], + }, + }, + }, + }, + "string": "query", + } + `); + expect(result).toEqual(mockResponse.terms); + }); +}); diff --git a/src/platform/plugins/shared/kql/server/autocomplete/terms_enum.ts b/src/platform/plugins/shared/kql/server/autocomplete/terms_enum.ts new file mode 100644 index 0000000000000..d9f97c138d85a --- /dev/null +++ b/src/platform/plugins/shared/kql/server/autocomplete/terms_enum.ts @@ -0,0 +1,59 @@ +/* + * 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 type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; +import type { estypes } from '@elastic/elasticsearch'; +import type { FieldSpec } from '@kbn/data-views-plugin/common'; +import { findIndexPatternById, getFieldByName } from '../data_views'; +import type { ConfigSchema } from '../config'; + +export async function termsEnumSuggestions( + config: ConfigSchema, + savedObjectsClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + index: string, + fieldName: string, + query: string, + filters?: estypes.QueryDslQueryContainer[], + field?: FieldSpec, + abortSignal?: AbortSignal +) { + // See https://github.com/elastic/kibana/issues/165264 + const { tiers } = config.autocomplete.valueSuggestions; + const excludedTiers = [ + 'data_content', + 'data_hot', + 'data_warm', + 'data_cold', + 'data_frozen', + ].filter((tier) => !tiers.includes(tier)); + + if (!field?.name && !field?.type) { + const indexPattern = await findIndexPatternById(savedObjectsClient, index); + field = indexPattern && getFieldByName(fieldName, indexPattern); + } + + const body = { + field: field?.name ?? fieldName, + string: query, + index_filter: { + bool: { + must: filters ?? [], + must_not: { + terms: { + _tier: excludedTiers, + }, + }, + }, + }, + }; + + const { terms } = await esClient.termsEnum({ index, ...body }, { signal: abortSignal }); + return terms; +} diff --git a/src/platform/plugins/shared/kql/server/autocomplete/value_suggestions_route.ts b/src/platform/plugins/shared/kql/server/autocomplete/value_suggestions_route.ts new file mode 100644 index 0000000000000..459397c4f6341 --- /dev/null +++ b/src/platform/plugins/shared/kql/server/autocomplete/value_suggestions_route.ts @@ -0,0 +1,86 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import type { IRouter } from '@kbn/core/server'; +import type { Observable } from 'rxjs'; +import { firstValueFrom } from 'rxjs'; +import { getRequestAbortedSignal } from '@kbn/data-plugin/server'; +import { getKbnServerError, reportServerError } from '@kbn/kibana-utils-plugin/server'; +import type { ConfigSchema } from '../config'; +import { termsEnumSuggestions } from './terms_enum'; +import { termsAggSuggestions } from './terms_agg'; + +export function registerValueSuggestionsRoute(router: IRouter, config$: Observable) { + router.versioned + .post({ + path: '/internal/kibana/suggestions/values/{index}', + access: 'internal', + security: { + authz: { + enabled: false, + reason: + 'This route is opted out from authorization because uses the current user authorizations.', + }, + }, + }) + .addVersion( + { + version: '1', + validate: { + request: { + params: schema.object( + { + index: schema.string(), + }, + { unknowns: 'allow' } + ), + body: schema.object( + { + field: schema.string(), + query: schema.string(), + filters: schema.maybe(schema.any()), + fieldMeta: schema.maybe(schema.any()), + method: schema.maybe( + schema.oneOf([schema.literal('terms_agg'), schema.literal('terms_enum')]) + ), + }, + { unknowns: 'allow' } + ), + }, + }, + }, + async (context, request, response) => { + const config = await firstValueFrom(config$); + const { field: fieldName, query, filters, fieldMeta, method } = request.body; + const { index } = request.params; + const abortSignal = getRequestAbortedSignal(request.events.aborted$); + const { savedObjects, elasticsearch } = await context.core; + + try { + const fn = method === 'terms_agg' ? termsAggSuggestions : termsEnumSuggestions; + const body = await fn( + config, + savedObjects.client, + elasticsearch.client.asCurrentUser, + index, + fieldName, + query, + filters, + fieldMeta, + abortSignal + ); + return response.ok({ body }); + } catch (e) { + const kbnErr = getKbnServerError(e); + return reportServerError(response, kbnErr); + } + } + ); +} diff --git a/src/platform/plugins/shared/kql/server/config.ts b/src/platform/plugins/shared/kql/server/config.ts new file mode 100644 index 0000000000000..b38424a7cda47 --- /dev/null +++ b/src/platform/plugins/shared/kql/server/config.ts @@ -0,0 +1,38 @@ +/* + * 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 type { TypeOf } from '@kbn/config-schema'; +import { schema } from '@kbn/config-schema'; + +export const configSchema = schema.object({ + autocomplete: schema.object({ + querySuggestions: schema.object({ + enabled: schema.boolean({ defaultValue: true }), + }), + valueSuggestions: schema.object({ + enabled: schema.boolean({ defaultValue: true }), + tiers: schema.arrayOf( + schema.oneOf([ + schema.literal('data_content'), + schema.literal('data_hot'), + schema.literal('data_warm'), + schema.literal('data_cold'), + schema.literal('data_frozen'), + ]), + { + defaultValue: ['data_hot', 'data_warm', 'data_content', 'data_cold'], + } + ), + terminateAfter: schema.duration({ defaultValue: 100000 }), + timeout: schema.duration({ defaultValue: 1000 }), + }), + }), +}); + +export type ConfigSchema = TypeOf; diff --git a/src/platform/plugins/shared/kql/server/config_deprecations.test.ts b/src/platform/plugins/shared/kql/server/config_deprecations.test.ts new file mode 100644 index 0000000000000..e109647fc1900 --- /dev/null +++ b/src/platform/plugins/shared/kql/server/config_deprecations.test.ts @@ -0,0 +1,138 @@ +/* + * 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 { cloneDeep } from 'lodash'; + +import { applyDeprecations, configDeprecationFactory } from '@kbn/config'; +import { configDeprecationsMock } from '@kbn/core/server/mocks'; + +import { autocompleteConfigDeprecationProvider } from './config_deprecations'; + +const deprecationContext = configDeprecationsMock.createContext(); + +const applyConfigDeprecations = (settings: Record = {}) => { + const deprecations = autocompleteConfigDeprecationProvider(configDeprecationFactory); + const deprecationMessages: string[] = []; + const migrated = applyDeprecations( + settings, + deprecations.map((deprecation) => ({ + deprecation, + path: '', + context: deprecationContext, + })), + () => + ({ message }) => + deprecationMessages.push(message) + ); + return { + messages: deprecationMessages, + migrated: migrated.config, + }; +}; + +describe('Config Deprecations', () => { + it('does not report deprecations for default configurationc', () => { + const configFirstStep = { unifiedSearch: { autocomplete: { valueSuggestions: {} } } }; + const { messages, migrated } = applyConfigDeprecations(cloneDeep(configFirstStep)); + expect(migrated).toEqual(configFirstStep); + expect(messages).toHaveLength(0); + }); + + it('renames kibana.autocompleteTerminateAfter to kql.autocomplete.valueSuggestions.terminateAfter', () => { + const config = { + kibana: { + autocompleteTerminateAfter: 123, + }, + }; + const { messages, migrated } = applyConfigDeprecations(cloneDeep(config)); + expect(migrated.kibana?.autocompleteTerminateAfter).not.toBeDefined(); + expect(migrated.kql.autocomplete.valueSuggestions.terminateAfter).toEqual(123); + expect(messages).toMatchInlineSnapshot(` + Array [ + "Setting \\"kibana.autocompleteTerminateAfter\\" has been replaced by \\"kql.autocomplete.valueSuggestions.terminateAfter\\"", + ] + `); + }); + + it('renames kibana.autocompleteTimeout to kql.autocomplete.valueSuggestions.timeout', () => { + const config = { + kibana: { + autocompleteTimeout: 123, + }, + }; + const { messages, migrated } = applyConfigDeprecations(cloneDeep(config)); + expect(migrated.kibana?.autocompleteTimeout).not.toBeDefined(); + expect(migrated.kql.autocomplete.valueSuggestions.timeout).toEqual(123); + expect(messages).toMatchInlineSnapshot(` + Array [ + "Setting \\"kibana.autocompleteTimeout\\" has been replaced by \\"kql.autocomplete.valueSuggestions.timeout\\"", + ] + `); + }); + + it('renames unifiedSearch.autocomplete.querySuggestions.enabled to kql.autocomplete.querySuggestions.enabled', () => { + const config = { + unifiedSearch: { + autocomplete: { + querySuggestions: { + enabled: false, + }, + }, + }, + }; + const { messages, migrated } = applyConfigDeprecations(cloneDeep(config)); + expect(migrated.unifiedSearch?.autocomplete.querySuggestions.enabled).not.toBeDefined(); + expect(migrated.kql.autocomplete.querySuggestions.enabled).toEqual(false); + expect(messages).toMatchInlineSnapshot(` + Array [ + "Setting \\"unifiedSearch.autocomplete.querySuggestions.enabled\\" has been replaced by \\"kql.autocomplete.querySuggestions.enabled\\"", + ] + `); + }); + + it('renames unifiedSearch.autocomplete.valueSuggestions.enabled to kql.autocomplete.valueSuggestions.enabled', () => { + const config = { + unifiedSearch: { + autocomplete: { + valueSuggestions: { + enabled: false, + }, + }, + }, + }; + const { messages, migrated } = applyConfigDeprecations(cloneDeep(config)); + expect(migrated.unifiedSearch?.autocomplete.valueSuggestions.enabled).not.toBeDefined(); + expect(migrated.kql.autocomplete.valueSuggestions.enabled).toEqual(false); + expect(messages).toMatchInlineSnapshot(` + Array [ + "Setting \\"unifiedSearch.autocomplete.valueSuggestions.enabled\\" has been replaced by \\"kql.autocomplete.valueSuggestions.enabled\\"", + ] + `); + }); + + it('renames unifiedSearch.autocomplete.valueSuggestions.tiers to kql.autocomplete.valueSuggestions.tiers', () => { + const config = { + unifiedSearch: { + autocomplete: { + valueSuggestions: { + tiers: [], + }, + }, + }, + }; + const { messages, migrated } = applyConfigDeprecations(cloneDeep(config)); + expect(migrated.unifiedSearch?.autocomplete.valueSuggestions.tiers).not.toBeDefined(); + expect(migrated.kql.autocomplete.valueSuggestions.tiers).toEqual([]); + expect(messages).toMatchInlineSnapshot(` + Array [ + "Setting \\"unifiedSearch.autocomplete.valueSuggestions.tiers\\" has been replaced by \\"kql.autocomplete.valueSuggestions.tiers\\"", + ] + `); + }); +}); diff --git a/src/platform/plugins/shared/kql/server/config_deprecations.ts b/src/platform/plugins/shared/kql/server/config_deprecations.ts new file mode 100644 index 0000000000000..cb126b966a3f6 --- /dev/null +++ b/src/platform/plugins/shared/kql/server/config_deprecations.ts @@ -0,0 +1,56 @@ +/* + * 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 type { ConfigDeprecationProvider } from '@kbn/core/server'; + +export const autocompleteConfigDeprecationProvider: ConfigDeprecationProvider = ({ + renameFromRoot, +}) => [ + renameFromRoot( + 'unifiedSearch.autocomplete.valueSuggestions.terminateAfter', + 'kql.autocomplete.valueSuggestions.terminateAfter', + { level: 'warning' } + ), + renameFromRoot( + 'kibana.autocompleteTerminateAfter', + 'kql.autocomplete.valueSuggestions.terminateAfter', + { level: 'warning' } + ), + renameFromRoot( + 'unifiedSearch.autocomplete.valueSuggestions.timeout', + 'kql.autocomplete.valueSuggestions.timeout', + { + level: 'warning', + } + ), + renameFromRoot('kibana.autocompleteTimeout', 'kql.autocomplete.valueSuggestions.timeout', { + level: 'warning', + }), + renameFromRoot( + 'unifiedSearch.autocomplete.querySuggestions.enabled', + 'kql.autocomplete.querySuggestions.enabled', + { + level: 'warning', + } + ), + renameFromRoot( + 'unifiedSearch.autocomplete.valueSuggestions.enabled', + 'kql.autocomplete.valueSuggestions.enabled', + { + level: 'warning', + } + ), + renameFromRoot( + 'unifiedSearch.autocomplete.valueSuggestions.tiers', + 'kql.autocomplete.valueSuggestions.tiers', + { + level: 'warning', + } + ), +]; diff --git a/src/platform/plugins/shared/kql/server/data_views/index.ts b/src/platform/plugins/shared/kql/server/data_views/index.ts new file mode 100644 index 0000000000000..2578963999c5a --- /dev/null +++ b/src/platform/plugins/shared/kql/server/data_views/index.ts @@ -0,0 +1,10 @@ +/* + * 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". + */ + +export * from '@kbn/data-views-plugin/server'; diff --git a/src/platform/plugins/shared/kql/server/index.ts b/src/platform/plugins/shared/kql/server/index.ts new file mode 100644 index 0000000000000..cec2da4c62123 --- /dev/null +++ b/src/platform/plugins/shared/kql/server/index.ts @@ -0,0 +1,36 @@ +/* + * 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 type { PluginConfigDescriptor, PluginInitializerContext } from '@kbn/core/server'; +import type { ConfigSchema } from './config'; +import { configSchema } from './config'; +import type { KQLServerPlugin, KQLServerPluginSetup, KQLServerPluginStart } from './plugin'; + +import { autocompleteConfigDeprecationProvider } from './config_deprecations'; + +/** + * Static code to be shared externally + * @public + */ + +export async function plugin(initializerContext: PluginInitializerContext) { + const { KQLServerPlugin } = await import('./plugin'); + return new KQLServerPlugin(initializerContext); +} + +export type { KQLServerPluginSetup as PluginSetup, KQLServerPluginStart as PluginStart }; +export type { KQLServerPlugin as Plugin }; + +export const config: PluginConfigDescriptor = { + deprecations: autocompleteConfigDeprecationProvider, + exposeToBrowser: { + autocomplete: true, + }, + schema: configSchema, +}; diff --git a/src/platform/plugins/shared/kql/server/plugin.ts b/src/platform/plugins/shared/kql/server/plugin.ts new file mode 100644 index 0000000000000..16d1297d97528 --- /dev/null +++ b/src/platform/plugins/shared/kql/server/plugin.ts @@ -0,0 +1,59 @@ +/* + * 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 type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/server'; +import type { ConfigSchema } from './config'; +import { AutocompleteService } from './autocomplete'; +import type { AutocompleteSetup } from './autocomplete/autocomplete_service'; + +export interface KQLServerPluginSetup { + autocomplete: AutocompleteSetup; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface KQLServerPluginStart {} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface KQLServerPluginSetupDependencies {} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface KQLServerPluginStartDependencies {} + +export class KQLServerPlugin + implements + Plugin< + KQLServerPluginSetup, + KQLServerPluginStart, + KQLServerPluginSetupDependencies, + KQLServerPluginStartDependencies + > +{ + private readonly autocompleteService: AutocompleteService; + + constructor(initializerContext: PluginInitializerContext) { + this.autocompleteService = new AutocompleteService(initializerContext); + } + + public setup( + core: CoreSetup, + {}: KQLServerPluginSetupDependencies + ) { + return { + autocomplete: this.autocompleteService.setup(core), + }; + } + + public start(core: CoreStart, {}: KQLServerPluginStartDependencies) { + return {}; + } + + public stop() {} +} + +export { KQLServerPlugin as Plugin }; diff --git a/src/platform/plugins/shared/kql/tsconfig.json b/src/platform/plugins/shared/kql/tsconfig.json new file mode 100644 index 0000000000000..6917c78831a89 --- /dev/null +++ b/src/platform/plugins/shared/kql/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "@kbn/tsconfig-base/tsconfig.json", + "compilerOptions": { + "outDir": "target/types", + }, + "include": [ + "../../../../typings/**/*", + "public/**/*", + "server/**/*", + ], + "kbn_references": [ + "@kbn/core", + "@kbn/i18n", + ], + "exclude": [ + "target/**/*", + ] +} diff --git a/src/platform/plugins/shared/unified_search/kibana.jsonc b/src/platform/plugins/shared/unified_search/kibana.jsonc index 927337e8bd283..c2fcf824054dc 100644 --- a/src/platform/plugins/shared/unified_search/kibana.jsonc +++ b/src/platform/plugins/shared/unified_search/kibana.jsonc @@ -23,7 +23,7 @@ "uiActions", "screenshotMode", "savedObjectsManagement", - "cps", + "cps" ], "optionalPlugins": [ "usageCollection" diff --git a/tsconfig.base.json b/tsconfig.base.json index 8b8762be2cc57..deb65bc158bdf 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1020,6 +1020,8 @@ "@kbn/eso-model-version-example/*": ["examples/eso_model_version_example/*"], "@kbn/eso-plugin": ["x-pack/platform/test/encrypted_saved_objects_api_integration/plugins/api_consumer_plugin"], "@kbn/eso-plugin/*": ["x-pack/platform/test/encrypted_saved_objects_api_integration/plugins/api_consumer_plugin/*"], + "@kbn/kql": ["src/platform/plugins/shared/kql"], + "@kbn/kql/*": ["src/platform/plugins/shared/kql/*"], "@kbn/esql": ["src/platform/plugins/shared/esql"], "@kbn/esql/*": ["src/platform/plugins/shared/esql/*"], "@kbn/esql-ast": ["src/platform/packages/shared/kbn-esql-ast"], From cc04e764c45c0201c806a4d4e6207d686ecf5fa6 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 17 Dec 2025 11:10:37 +0000 Subject: [PATCH 04/60] Changes from node scripts/eslint_all_files --no-cache --fix --- package.json | 2 +- tsconfig.base.json | 4 ++-- yarn.lock | 4 ++++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index df4f82341b33d..7477f979ea6c7 100644 --- a/package.json +++ b/package.json @@ -568,7 +568,6 @@ "@kbn/eso-model-version-example": "link:examples/eso_model_version_example", "@kbn/eso-plugin": "link:x-pack/platform/test/encrypted_saved_objects_api_integration/plugins/api_consumer_plugin", "@kbn/esql": "link:src/platform/plugins/shared/esql", - "@kbn/kql": "link:src/platform/plugins/shared/kql", "@kbn/esql-ast": "link:src/platform/packages/shared/kbn-esql-ast", "@kbn/esql-ast-inspector-plugin": "link:examples/esql_ast_inspector", "@kbn/esql-composer": "link:src/platform/packages/shared/kbn-esql-composer", @@ -694,6 +693,7 @@ "@kbn/kibana-react-plugin": "link:src/platform/plugins/shared/kibana_react", "@kbn/kibana-usage-collection-plugin": "link:src/platform/plugins/private/kibana_usage_collection", "@kbn/kibana-utils-plugin": "link:src/platform/plugins/shared/kibana_utils", + "@kbn/kql": "link:src/platform/plugins/shared/kql", "@kbn/kubernetes-security-plugin": "link:x-pack/solutions/security/plugins/kubernetes_security", "@kbn/langchain": "link:x-pack/platform/packages/shared/kbn-langchain", "@kbn/langgraph-checkpoint-saver": "link:x-pack/platform/packages/shared/kbn-langgraph-checkpoint-saver", diff --git a/tsconfig.base.json b/tsconfig.base.json index deb65bc158bdf..278ab2e417edd 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1020,8 +1020,6 @@ "@kbn/eso-model-version-example/*": ["examples/eso_model_version_example/*"], "@kbn/eso-plugin": ["x-pack/platform/test/encrypted_saved_objects_api_integration/plugins/api_consumer_plugin"], "@kbn/eso-plugin/*": ["x-pack/platform/test/encrypted_saved_objects_api_integration/plugins/api_consumer_plugin/*"], - "@kbn/kql": ["src/platform/plugins/shared/kql"], - "@kbn/kql/*": ["src/platform/plugins/shared/kql/*"], "@kbn/esql": ["src/platform/plugins/shared/esql"], "@kbn/esql/*": ["src/platform/plugins/shared/esql/*"], "@kbn/esql-ast": ["src/platform/packages/shared/kbn-esql-ast"], @@ -1326,6 +1324,8 @@ "@kbn/kibana-usage-collection-plugin/*": ["src/platform/plugins/private/kibana_usage_collection/*"], "@kbn/kibana-utils-plugin": ["src/platform/plugins/shared/kibana_utils"], "@kbn/kibana-utils-plugin/*": ["src/platform/plugins/shared/kibana_utils/*"], + "@kbn/kql": ["src/platform/plugins/shared/kql"], + "@kbn/kql/*": ["src/platform/plugins/shared/kql/*"], "@kbn/kubernetes-security-plugin": ["x-pack/solutions/security/plugins/kubernetes_security"], "@kbn/kubernetes-security-plugin/*": ["x-pack/solutions/security/plugins/kubernetes_security/*"], "@kbn/langchain": ["x-pack/platform/packages/shared/kbn-langchain"], diff --git a/yarn.lock b/yarn.lock index 744098c623188..ccea99cce1fdb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7167,6 +7167,10 @@ version "0.0.0" uid "" +"@kbn/kql@link:src/platform/plugins/shared/kql": + version "0.0.0" + uid "" + "@kbn/kubernetes-security-plugin@link:x-pack/solutions/security/plugins/kubernetes_security": version "0.0.0" uid "" From d5e4e4cd71c89e7a182314b938aaeeb4100e771b Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 17 Dec 2025 11:23:09 +0000 Subject: [PATCH 05/60] Changes from node scripts/lint_ts_projects --fix --- src/platform/plugins/shared/kql/tsconfig.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/platform/plugins/shared/kql/tsconfig.json b/src/platform/plugins/shared/kql/tsconfig.json index 6917c78831a89..c843e8c24880d 100644 --- a/src/platform/plugins/shared/kql/tsconfig.json +++ b/src/platform/plugins/shared/kql/tsconfig.json @@ -11,6 +11,20 @@ "kbn_references": [ "@kbn/core", "@kbn/i18n", + "@kbn/analytics", + "@kbn/usage-collection-plugin", + "@kbn/data-plugin", + "@kbn/es-query", + "@kbn/i18n-react", + "@kbn/data-views-plugin", + "@kbn/datemath", + "@kbn/test-jest-helpers", + "@kbn/ui-theme", + "@kbn/react-kibana-mount", + "@kbn/kibana-utils-plugin", + "@kbn/utility-types-jest", + "@kbn/config-schema", + "@kbn/config", ], "exclude": [ "target/**/*", From a9c41079d9b276e831a533367d91153f95ac979b Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 17 Dec 2025 11:23:51 +0000 Subject: [PATCH 06/60] Changes from node scripts/build_plugin_list_docs --- docs/extend/plugin-list.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/extend/plugin-list.md b/docs/extend/plugin-list.md index cb5093408bf82..5cfe0669006b6 100644 --- a/docs/extend/plugin-list.md +++ b/docs/extend/plugin-list.md @@ -64,6 +64,7 @@ mapped_pages: | [kibanaReact](https://github.com/elastic/kibana/blob/main/src/platform/plugins/shared/kibana_react/README.md) | Tools for building React applications in Kibana. | | [kibanaUsageCollection](https://github.com/elastic/kibana/blob/main/src/platform/plugins/private/kibana_usage_collection/README.md) | This plugin registers the Platform Usage Collectors in Kibana. | | [kibanaUtils](https://github.com/elastic/kibana/blob/main/src/platform/plugins/shared/kibana_utils/README.md) | Utilities for building Kibana plugins. | +| [kql](https://github.com/elastic/kibana/blob/main/src/platform/plugins/shared/kql/README.md) | WARNING: Missing or empty README. | | [links](https://github.com/elastic/kibana/blob/main/src/platform/plugins/private/links/README.md) | This plugin adds the Links panel which allows authors to create hard links to navigate on click and bring all context from the source dashboard to the destination dashboard. | | [management](https://github.com/elastic/kibana/blob/main/src/platform/plugins/shared/management/README.md) | This plugins contains the "Stack Management" page framework. It offers navigation and an API to link individual management section into it. This plugin does not contain any individual management section itself. | | [mapsEms](https://github.com/elastic/kibana/blob/main/src/platform/plugins/private/maps_ems/README.md) | Utility plugin: | From af81bbeb561152b725f2a95c0dca13458bb6d41f Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 17 Dec 2025 11:36:37 +0000 Subject: [PATCH 07/60] Changes from node scripts/generate codeowners --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 070a63a8ab20d..b31a18dd49c10 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -784,6 +784,7 @@ src/platform/plugins/shared/home @elastic/appex-sharedux src/platform/plugins/shared/inspector @elastic/kibana-presentation src/platform/plugins/shared/kibana_react @elastic/appex-sharedux src/platform/plugins/shared/kibana_utils @elastic/appex-sharedux +src/platform/plugins/shared/kql @elastic/kibana-presentation src/platform/plugins/shared/management @elastic/kibana-management src/platform/plugins/shared/metrics_experience @elastic/obs-presentation-team src/platform/plugins/shared/navigation @elastic/appex-sharedux From 04af071a86acdbbceec93a3fceb7a342c8640953 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 17 Dec 2025 11:36:46 +0000 Subject: [PATCH 08/60] Changes from node scripts/regenerate_moon_projects.js --update --- src/platform/plugins/shared/kql/moon.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/platform/plugins/shared/kql/moon.yml b/src/platform/plugins/shared/kql/moon.yml index 77faf2785d93b..9bce68836e27b 100644 --- a/src/platform/plugins/shared/kql/moon.yml +++ b/src/platform/plugins/shared/kql/moon.yml @@ -20,6 +20,20 @@ project: dependsOn: - '@kbn/core' - '@kbn/i18n' + - '@kbn/analytics' + - '@kbn/usage-collection-plugin' + - '@kbn/data-plugin' + - '@kbn/es-query' + - '@kbn/i18n-react' + - '@kbn/data-views-plugin' + - '@kbn/datemath' + - '@kbn/test-jest-helpers' + - '@kbn/ui-theme' + - '@kbn/react-kibana-mount' + - '@kbn/kibana-utils-plugin' + - '@kbn/utility-types-jest' + - '@kbn/config-schema' + - '@kbn/config' tags: - plugin - prod From 008596be71280cd5f9cb985a4cd012b9e31e7fec Mon Sep 17 00:00:00 2001 From: Stratoula Date: Thu, 18 Dec 2025 08:55:59 +0100 Subject: [PATCH 09/60] Fixes the paths --- .../shared/kql/public/autocomplete/autocomplete_service.ts | 3 +-- .../autocomplete/providers/kql_query_suggestion/index.ts | 4 ++-- .../autocomplete/providers/kql_query_suggestion/types.ts | 4 ++-- .../autocomplete/providers/kql_query_suggestion/value.ts | 4 ++-- .../autocomplete/providers/query_suggestion_provider.ts | 2 +- .../components/query_string_input/filter_button_group.tsx | 2 +- .../query_string_input/query_string_input.styles.tsx | 2 +- .../components/typeahead/suggestion_component.test.tsx | 4 ++-- .../public/components/typeahead/suggestion_component.tsx | 6 +++--- .../components/typeahead/suggestions_component.test.tsx | 4 ++-- .../public/components/typeahead/suggestions_component.tsx | 6 +++--- .../plugins/shared/kql/public/components/typeahead/types.ts | 2 +- .../{query_string_input => utils}/use_memo_css.ts | 0 13 files changed, 21 insertions(+), 22 deletions(-) rename src/platform/plugins/shared/kql/public/components/{query_string_input => utils}/use_memo_css.ts (100%) diff --git a/src/platform/plugins/shared/kql/public/autocomplete/autocomplete_service.ts b/src/platform/plugins/shared/kql/public/autocomplete/autocomplete_service.ts index 2905a139a1ec5..519992c6baafe 100644 --- a/src/platform/plugins/shared/kql/public/autocomplete/autocomplete_service.ts +++ b/src/platform/plugins/shared/kql/public/autocomplete/autocomplete_service.ts @@ -24,7 +24,6 @@ import { KUERY_LANGUAGE_NAME, setupKqlQuerySuggestionProvider, } from './providers/kql_query_suggestion'; -import type { UnifiedSearchPublicPluginStart, UnifiedSearchStartDependencies } from '../types'; export class AutocompleteService { autocompleteConfig: ConfigSchema['autocomplete']; @@ -51,7 +50,7 @@ export class AutocompleteService { /** @public **/ public setup( - core: CoreSetup, + core: CoreSetup, { timefilter, usageCollection, diff --git a/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/index.ts b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/index.ts index 2c964543ec5a0..c7a0945c8d8e8 100644 --- a/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/index.ts +++ b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/index.ts @@ -10,13 +10,13 @@ import type { CoreSetup } from '@kbn/core/public'; import type { $Keys } from 'utility-types'; import { flatten, uniqBy } from 'lodash'; -import type { UnifiedSearchPublicPluginStart } from '../../../types'; import type { QuerySuggestion, QuerySuggestionGetFnArgs, QuerySuggestionGetFn, } from '../query_suggestion_provider'; +import type { AutocompleteStart } from '../..'; const cursorSymbol = '@kuery-cursor@'; @@ -26,7 +26,7 @@ const dedup = (suggestions: QuerySuggestion[]): QuerySuggestion[] => export const KUERY_LANGUAGE_NAME = 'kuery'; export const setupKqlQuerySuggestionProvider = ( - core: CoreSetup + core: CoreSetup ): QuerySuggestionGetFn => { let getSuggestionsByType: | (( diff --git a/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/types.ts b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/types.ts index 9423af3bb4de1..26db092538eef 100644 --- a/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/types.ts +++ b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/types.ts @@ -9,9 +9,9 @@ import type { KueryNode } from '@kbn/es-query'; import type { CoreSetup } from '@kbn/core/public'; -import type { UnifiedSearchPublicPluginStart } from '../../../types'; import type { QuerySuggestionBasic, QuerySuggestionGetFnArgs } from '../query_suggestion_provider'; +import type { AutocompleteStart } from '../../autocomplete_service'; export type KqlQuerySuggestionProvider = ( - core: CoreSetup + core: CoreSetup ) => (querySuggestionsGetFnArgs: QuerySuggestionGetFnArgs, kueryNode: KueryNode) => Promise; diff --git a/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/value.ts b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/value.ts index d2c0cc4e79eeb..21157c548cf72 100644 --- a/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/value.ts +++ b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/value.ts @@ -12,7 +12,7 @@ import type { CoreSetup } from '@kbn/core/public'; import type { DataView, DataViewField } from '@kbn/data-views-plugin/common'; import { escapeQuotes } from '@kbn/es-query'; import type { KqlQuerySuggestionProvider } from './types'; -import type { UnifiedSearchPublicPluginStart } from '../../../types'; +import type { AutocompleteStart } from '../../autocomplete_service'; import type { QuerySuggestion } from '../query_suggestion_provider'; import { QuerySuggestionTypes } from '../query_suggestion_provider'; @@ -27,7 +27,7 @@ const wrapAsSuggestions = (start: number, end: number, query: string, values: st })); export const setupGetValueSuggestions: KqlQuerySuggestionProvider = ( - core: CoreSetup + core: CoreSetup ) => { const autoCompleteServicePromise = core .getStartServices() diff --git a/src/platform/plugins/shared/kql/public/autocomplete/providers/query_suggestion_provider.ts b/src/platform/plugins/shared/kql/public/autocomplete/providers/query_suggestion_provider.ts index 9131d118f19d0..c9e7a095d90b7 100644 --- a/src/platform/plugins/shared/kql/public/autocomplete/providers/query_suggestion_provider.ts +++ b/src/platform/plugins/shared/kql/public/autocomplete/providers/query_suggestion_provider.ts @@ -10,7 +10,7 @@ import type { ValueSuggestionsMethod } from '@kbn/data-plugin/common'; // for replace IIndexPattern => DataView need to fix the issue https://github.com/elastic/kibana/issues/131292 import type { DataViewField, DataView } from '@kbn/data-views-plugin/common'; -import type { SuggestionsAbstraction } from '../../typeahead/suggestions_component'; +import type { SuggestionsAbstraction } from '../../components/typeahead/suggestions_component'; export enum QuerySuggestionTypes { Field = 'field', diff --git a/src/platform/plugins/shared/kql/public/components/query_string_input/filter_button_group.tsx b/src/platform/plugins/shared/kql/public/components/query_string_input/filter_button_group.tsx index eaa7c35154c87..36b11c04d65b0 100644 --- a/src/platform/plugins/shared/kql/public/components/query_string_input/filter_button_group.tsx +++ b/src/platform/plugins/shared/kql/public/components/query_string_input/filter_button_group.tsx @@ -13,7 +13,7 @@ import classNames from 'classnames'; import type { UseEuiTheme } from '@elastic/eui'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { css } from '@emotion/react'; -import { useMemoCss } from './use_memo_css'; +import { useMemoCss } from '../utils/use_memo_css'; interface Props { items: ReactNode[]; diff --git a/src/platform/plugins/shared/kql/public/components/query_string_input/query_string_input.styles.tsx b/src/platform/plugins/shared/kql/public/components/query_string_input/query_string_input.styles.tsx index 3454d42c97576..5b007ff25c8e0 100644 --- a/src/platform/plugins/shared/kql/public/components/query_string_input/query_string_input.styles.tsx +++ b/src/platform/plugins/shared/kql/public/components/query_string_input/query_string_input.styles.tsx @@ -11,7 +11,7 @@ import React from 'react'; import type { UseEuiTheme } from '@elastic/eui'; import { euiThemeVars } from '@kbn/ui-theme'; import { css } from '@emotion/react'; -import { useMemoCss } from './use_memo_css'; +import { useMemoCss } from '../utils/use_memo_css'; const queryStringInputStyles = { container: ({ euiTheme }: UseEuiTheme) => diff --git a/src/platform/plugins/shared/kql/public/components/typeahead/suggestion_component.test.tsx b/src/platform/plugins/shared/kql/public/components/typeahead/suggestion_component.test.tsx index 50fbe7b3dd715..510a7ea1ccf38 100644 --- a/src/platform/plugins/shared/kql/public/components/typeahead/suggestion_component.test.tsx +++ b/src/platform/plugins/shared/kql/public/components/typeahead/suggestion_component.test.tsx @@ -10,8 +10,8 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import { SuggestionComponent } from './suggestion_component'; -import type { QuerySuggestion } from '../autocomplete'; -import { QuerySuggestionTypes } from '../autocomplete'; +import type { QuerySuggestion } from '../../autocomplete'; +import { QuerySuggestionTypes } from '../../autocomplete'; import { userEvent } from '@testing-library/user-event'; const noop = () => {}; diff --git a/src/platform/plugins/shared/kql/public/components/typeahead/suggestion_component.tsx b/src/platform/plugins/shared/kql/public/components/typeahead/suggestion_component.tsx index ae0b561cd2ad8..56f258fdd3cfc 100644 --- a/src/platform/plugins/shared/kql/public/components/typeahead/suggestion_component.tsx +++ b/src/platform/plugins/shared/kql/public/components/typeahead/suggestion_component.tsx @@ -12,9 +12,9 @@ import { EuiIcon, euiFontSize } from '@elastic/eui'; import classNames from 'classnames'; import React, { useCallback } from 'react'; import { css } from '@emotion/react'; -import type { EmotionStyles } from '../use_memo_css'; -import { useMemoCss } from '../use_memo_css'; -import type { QuerySuggestion } from '../autocomplete'; +import type { EmotionStyles } from '../utils/use_memo_css'; +import { useMemoCss } from '../utils/use_memo_css'; +import type { QuerySuggestion } from '../../autocomplete'; import type { SuggestionOnClick, SuggestionOnMouseEnter } from './types'; function getEuiIconType(type: string) { diff --git a/src/platform/plugins/shared/kql/public/components/typeahead/suggestions_component.test.tsx b/src/platform/plugins/shared/kql/public/components/typeahead/suggestions_component.test.tsx index 11712960e0f8a..2c88cb6f4d61c 100644 --- a/src/platform/plugins/shared/kql/public/components/typeahead/suggestions_component.test.tsx +++ b/src/platform/plugins/shared/kql/public/components/typeahead/suggestions_component.test.tsx @@ -9,8 +9,8 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; -import type { QuerySuggestion } from '../autocomplete'; -import { QuerySuggestionTypes } from '../autocomplete'; +import type { QuerySuggestion } from '../../autocomplete'; +import { QuerySuggestionTypes } from '../../autocomplete'; import { SuggestionsComponent } from './suggestions_component'; import { EuiThemeProvider } from '@elastic/eui'; import { userEvent } from '@testing-library/user-event'; diff --git a/src/platform/plugins/shared/kql/public/components/typeahead/suggestions_component.tsx b/src/platform/plugins/shared/kql/public/components/typeahead/suggestions_component.tsx index 46f52654107f3..86a0b514a45a0 100644 --- a/src/platform/plugins/shared/kql/public/components/typeahead/suggestions_component.tsx +++ b/src/platform/plugins/shared/kql/public/components/typeahead/suggestions_component.tsx @@ -16,9 +16,9 @@ import useRafState from 'react-use/lib/useRafState'; import type { UseEuiTheme } from '@elastic/eui'; import { euiShadow, euiShadowFlat } from '@elastic/eui'; import { css } from '@emotion/react'; -import type { EmotionStyles } from '../use_memo_css'; -import { useMemoCss } from '../use_memo_css'; -import type { QuerySuggestion } from '../autocomplete'; +import type { EmotionStyles } from '../utils/use_memo_css'; +import { useMemoCss } from '../utils/use_memo_css'; +import type { QuerySuggestion } from '../../autocomplete'; import { SuggestionComponent } from './suggestion_component'; import { SUGGESTIONS_LIST_REQUIRED_BOTTOM_SPACE, diff --git a/src/platform/plugins/shared/kql/public/components/typeahead/types.ts b/src/platform/plugins/shared/kql/public/components/typeahead/types.ts index 20082382333ee..333094efd39e6 100644 --- a/src/platform/plugins/shared/kql/public/components/typeahead/types.ts +++ b/src/platform/plugins/shared/kql/public/components/typeahead/types.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { QuerySuggestion } from '../autocomplete'; +import type { QuerySuggestion } from '../../autocomplete'; export type SuggestionOnClick = (suggestion: QuerySuggestion, index: number) => void; diff --git a/src/platform/plugins/shared/kql/public/components/query_string_input/use_memo_css.ts b/src/platform/plugins/shared/kql/public/components/utils/use_memo_css.ts similarity index 100% rename from src/platform/plugins/shared/kql/public/components/query_string_input/use_memo_css.ts rename to src/platform/plugins/shared/kql/public/components/utils/use_memo_css.ts From 76db41d4b5ffe7b4b27f708076b9c92bff9e9950 Mon Sep 17 00:00:00 2001 From: Stratoula Date: Thu, 18 Dec 2025 09:15:17 +0100 Subject: [PATCH 10/60] Some fixes --- packages/kbn-optimizer/limits.yml | 25 ++++++++++--------- src/platform/plugins/shared/kql/kibana.jsonc | 12 +-------- .../autocomplete/autocomplete_service.ts | 3 ++- .../plugins/shared/kql/public/index.ts | 11 ++++++++ .../plugins/shared/kql/public/plugin.ts | 9 ++++--- 5 files changed, 33 insertions(+), 27 deletions(-) create mode 100644 src/platform/plugins/shared/kql/public/index.ts diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 816ae80cd5978..524b257f4c005 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -20,8 +20,8 @@ pageLoadAssetSize: cloudExperiments: 103984 cloudFullStory: 4752 cloudLinks: 36333 - cloudSecurityPosture: 19900 - console: 31271 + cloudSecurityPosture: 19745 + console: 31192 contentConnectors: 33014 contentManagement: 8350 controls: 12234 @@ -42,7 +42,7 @@ pageLoadAssetSize: dataViewFieldEditor: 26024 dataViewManagement: 6250 dataViews: 65000 - dataVisualizer: 32800 + dataVisualizer: 32797 developerToolbar: 4467 devTools: 8109 discover: 26000 @@ -53,7 +53,7 @@ pageLoadAssetSize: embeddable: 18432 embeddableAlertsTable: 6524 embeddableEnhanced: 8448 - enterpriseSearch: 41839 + enterpriseSearch: 40244 esql: 18224 esqlDataGrid: 10209 esUiShared: 101220 @@ -92,11 +92,12 @@ pageLoadAssetSize: inspectComponent: 4900 inspector: 17954 interactiveSetup: 36524 - intercepts: 21246 + intercepts: 21066 kibanaOverview: 6339 kibanaReact: 22503 kibanaUsageCollection: 1736 kibanaUtils: 54848 + kql: 1171 kubernetesSecurity: 6807 lens: 71718 licenseManagement: 8265 @@ -122,11 +123,11 @@ pageLoadAssetSize: observabilityAgentBuilder: 5727 observabilityAIAssistant: 54556 observabilityAIAssistantApp: 18012 - observabilityAiAssistantManagement: 7438 + observabilityAiAssistantManagement: 7126 observabilityLogsExplorer: 4918 observabilityOnboarding: 12872 observabilityShared: 75115 - onechat: 88464 + onechat: 78482 osquery: 47422 painlessLab: 6299 presentationPanel: 11418 @@ -149,7 +150,7 @@ pageLoadAssetSize: screenshotMode: 2351 screenshotting: 3252 searchAssistant: 6150 - searchGettingStarted: 7327 + searchGettingStarted: 6682 searchHomepage: 7962 searchIndices: 9991 searchInferenceEndpoints: 8071 @@ -165,17 +166,17 @@ pageLoadAssetSize: securitySolutionServerless: 52082 serverless: 7451 serverlessObservability: 19472 - serverlessSearch: 26393 - serverlessWorkplaceAI: 6736 + serverlessSearch: 26340 + serverlessWorkplaceAI: 6671 sessionView: 47912 share: 58677 slo: 36645 snapshotRestore: 22068 - spaces: 30665 + spaces: 30016 stackAlerts: 31499 stackConnectors: 85421 streams: 10000 - streamsApp: 27784 + streamsApp: 27001 synthetics: 31571 telemetry: 25755 telemetryManagementSection: 5522 diff --git a/src/platform/plugins/shared/kql/kibana.jsonc b/src/platform/plugins/shared/kql/kibana.jsonc index de6126fcc53ea..df8558dcd13be 100644 --- a/src/platform/plugins/shared/kql/kibana.jsonc +++ b/src/platform/plugins/shared/kql/kibana.jsonc @@ -9,23 +9,13 @@ "server": true, "browser": true, "optionalPlugins": [ - "fieldsMetadata", "usageCollection", - "licensing" ], "requiredPlugins": [ "data", "dataViews", "uiActions", - "contentManagement", - "share", - "fileUpload", - "fieldFormats" ], - "requiredBundles": [ - "kibanaReact", - "kibanaUtils", - "unifiedSearch" - ] + "requiredBundles": [] } } diff --git a/src/platform/plugins/shared/kql/public/autocomplete/autocomplete_service.ts b/src/platform/plugins/shared/kql/public/autocomplete/autocomplete_service.ts index 519992c6baafe..b199cc69e5440 100644 --- a/src/platform/plugins/shared/kql/public/autocomplete/autocomplete_service.ts +++ b/src/platform/plugins/shared/kql/public/autocomplete/autocomplete_service.ts @@ -24,6 +24,7 @@ import { KUERY_LANGUAGE_NAME, setupKqlQuerySuggestionProvider, } from './providers/kql_query_suggestion'; +import type { KqlPluginStart, KqlPluginSetupDependencies } from '../plugin'; export class AutocompleteService { autocompleteConfig: ConfigSchema['autocomplete']; @@ -50,7 +51,7 @@ export class AutocompleteService { /** @public **/ public setup( - core: CoreSetup, + core: CoreSetup, { timefilter, usageCollection, diff --git a/src/platform/plugins/shared/kql/public/index.ts b/src/platform/plugins/shared/kql/public/index.ts new file mode 100644 index 0000000000000..206946cf29ae3 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/index.ts @@ -0,0 +1,11 @@ +/* + * 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". + */ +export type { KqlPluginStart } from './plugin'; +export type { QueryStringInputProps } from './components/query_string_input/query_string_input'; +export { QueryStringInput } from './components/query_string_input/query_string_input'; diff --git a/src/platform/plugins/shared/kql/public/plugin.ts b/src/platform/plugins/shared/kql/public/plugin.ts index 8780a8a0819c9..e82d4f1e710bd 100644 --- a/src/platform/plugins/shared/kql/public/plugin.ts +++ b/src/platform/plugins/shared/kql/public/plugin.ts @@ -17,12 +17,12 @@ import { createQueryStringInput } from './components/query_string_input/get_quer import type { QueryStringInputProps } from './components/query_string_input/query_string_input'; import { AutocompleteService, type AutocompleteStart } from './autocomplete/autocomplete_service'; -interface KqlPluginSetupDependencies { +export interface KqlPluginSetupDependencies { data: DataPublicPluginSetup; usageCollection?: UsageCollectionSetup; } -interface KqlPluginStartDependencies { +export interface KqlPluginStartDependencies { data: DataPublicPluginStart; dataViews: DataViewsPublicPluginStart; } @@ -45,7 +45,10 @@ export class KqlPlugin implements Plugin<{}, KqlPluginStart> { this.storage = new Storage(window.localStorage); } - public setup(core: CoreSetup, { data, usageCollection }: KqlPluginSetupDependencies) { + public setup( + core: CoreSetup, + { data, usageCollection }: KqlPluginSetupDependencies + ) { const { query } = data; return { autocomplete: this.autocomplete.setup(core, { From 6a3e881bbea81648a73d938186838dbeba998b5f Mon Sep 17 00:00:00 2001 From: Stratoula Date: Thu, 18 Dec 2025 11:53:25 +0100 Subject: [PATCH 11/60] Chaos --- config/kibana.yml | 4 +- .../general-settings.md | 4 +- .../resources/base/bin/kibana-docker | 10 +- .../annotation_editor_controls/index.test.tsx | 2 +- .../packages/shared/kbn-lens-common/types.ts | 2 + .../components/query_input/query_input.tsx | 22 +- .../input_control_vis/public/plugin.ts | 14 +- .../public/components/controls/filter.tsx | 4 +- .../options_list_suggestions_route.ts | 4 +- .../plugins/shared/controls/server/plugin.ts | 8 +- .../kql_query_suggestion/conjunction.tsx | 16 +- .../kql_query_suggestion/operator.tsx | 48 +-- .../query_string_input/language_switcher.tsx | 8 +- .../query_string_input/query_string_input.tsx | 18 +- .../plugins/shared/kql/public/index.ts | 11 +- .../plugins/shared/kql/public/mocks.ts | 42 +++ .../plugins/shared/kql/public/plugin.ts | 12 +- .../{unified_search => kql}/server/mocks.ts | 0 .../shared/unified_search/kibana.jsonc | 2 +- .../plugins/shared/unified_search/moon.yml | 1 - .../autocomplete/autocomplete_service.ts | 104 ------ .../collectors/create_usage_collector.ts | 60 ---- .../public/autocomplete/collectors/index.ts | 12 - .../public/autocomplete/collectors/types.ts | 22 -- .../public/autocomplete/index.ts | 20 -- .../providers/kql_query_suggestion/README.md | 1 - .../__fixtures__/index_pattern_response.json | 333 ----------------- .../kql_query_suggestion/conjunction.test.ts | 66 ---- .../kql_query_suggestion/conjunction.tsx | 80 ----- .../kql_query_suggestion/field.test.ts | 233 ------------ .../providers/kql_query_suggestion/field.tsx | 106 ------ .../providers/kql_query_suggestion/index.ts | 105 ------ .../kql_query_suggestion/kql_module.ts | 14 - .../kql_query_suggestion/operator.test.ts | 130 ------- .../kql_query_suggestion/operator.tsx | 191 ---------- .../sort_prefix_first.test.ts | 82 ----- .../kql_query_suggestion/sort_prefix_first.ts | 25 -- .../providers/kql_query_suggestion/types.ts | 17 - .../kql_query_suggestion/value.test.ts | 200 ----------- .../providers/kql_query_suggestion/value.ts | 74 ---- .../providers/query_suggestion_provider.ts | 58 --- .../value_suggestion_provider.test.ts | 334 ------------------ .../providers/value_suggestion_provider.ts | 156 -------- .../filter_editor/phrase_suggestor.tsx | 2 +- .../shared/unified_search/public/index.ts | 14 +- .../unified_search/public/mocks/mocks.ts | 18 +- .../shared/unified_search/public/plugin.ts | 40 +-- .../public/query_string_input/index.tsx | 2 +- .../query_string_input/query_bar_top_row.tsx | 2 +- .../public/search_bar/create_search_bar.tsx | 8 +- .../shared/unified_search/public/types.ts | 18 +- .../shared/unified_search/public/ui_module.ts | 1 - .../autocomplete/autocomplete_service.ts | 45 --- .../server/autocomplete/index.ts | 10 - .../server/autocomplete/routes.ts | 19 - .../server/autocomplete/terms_agg.test.ts | 155 -------- .../server/autocomplete/terms_agg.ts | 115 ------ .../server/autocomplete/terms_enum.test.ts | 113 ------ .../server/autocomplete/terms_enum.ts | 59 ---- .../autocomplete/value_suggestions_route.ts | 86 ----- .../shared/unified_search/server/config.ts | 38 -- .../server/config_deprecations.test.ts | 138 -------- .../server/config_deprecations.ts | 60 ---- .../unified_search/server/data_views/index.ts | 10 - .../shared/unified_search/server/index.ts | 43 --- .../shared/unified_search/server/plugin.ts | 59 ---- .../shared/unified_search/tsconfig.json | 2 +- .../components/query_bar_wrapper.tsx | 10 +- .../vis_types/timeseries/public/plugin.ts | 7 + .../vis_types/timeseries/public/services.ts | 3 + .../test_suites/core_plugins/rendering.ts | 10 +- .../public/components/search_bar.test.tsx | 11 +- .../graph/public/components/search_bar.tsx | 6 +- .../kuery_bar/autocomplete_field.tsx | 2 +- .../public/components/kuery_bar/index.tsx | 2 +- .../components/kuery_bar/suggestion_item.tsx | 4 +- .../kuery_bar/with_kuery_autocompletion.tsx | 14 +- .../public/app/__mocks__/app_dependencies.tsx | 2 + .../transform/public/app/app_dependencies.tsx | 2 + .../public/app/mount_management_section.ts | 2 + .../source_search_bar/source_search_bar.tsx | 4 +- .../private/transform/public/plugin.ts | 2 + .../shared/alerting/server/plugin.test.ts | 10 +- .../plugins/shared/alerting/server/plugin.ts | 6 +- ...ugin_cancel_alerts_on_rule_timeout.test.ts | 4 +- .../shared/alerting/server/routes/index.ts | 2 +- .../values_suggestion_alerts.test.ts | 8 +- .../suggestions/values_suggestion_alerts.ts | 4 +- .../values_suggestion_rules.test.ts | 8 +- .../suggestions/values_suggestion_rules.ts | 4 +- .../fleet/components/search_bar.test.tsx | 4 +- .../fleet/components/search_bar.tsx | 8 +- .../agent_logs/filter_dataset.test.tsx | 2 +- .../components/agent_logs/filter_dataset.tsx | 6 +- .../components/agent_logs/query_bar.tsx | 4 +- .../fleet/public/mock/plugin_dependencies.ts | 2 + .../plugins/shared/fleet/public/plugin.ts | 5 + .../shared/lens/public/app_plugin/mounter.tsx | 2 + .../filters/filter_popover.test.tsx | 4 +- .../formula/editor/formula_editor.tsx | 8 +- .../formula/editor/math_completion.test.ts | 4 +- .../formula/editor/math_completion.ts | 19 +- .../operations/definitions/index.ts | 4 +- .../plugins/shared/lens/public/plugin.ts | 2 + .../shared/maps/public/kibana_services.ts | 2 +- .../plugins/shared/maps/public/plugin.ts | 2 + .../contexts/kibana/kibana_context.ts | 2 + .../exploration_query_bar.tsx | 4 +- .../explorer_query_bar/explorer_query_bar.tsx | 4 +- .../geo_containment/rule_form/query_input.tsx | 4 +- .../plugins/apm/public/application/index.tsx | 1 + .../trace_explorer/trace_search_box/index.tsx | 4 +- .../components/shared/kuery_bar/index.tsx | 6 +- .../context/apm_plugin/apm_plugin_context.tsx | 2 + .../public/embeddable/embeddable_context.tsx | 1 + .../plugins/apm/public/plugin.ts | 3 + .../log_rate_analysis.tsx | 1 + .../components/custom_threshold/types.ts | 2 + .../autocomplete_field/autocomplete_field.tsx | 2 +- .../autocomplete_field/suggestion_item.tsx | 4 +- .../components/rule_kql_filter/kuery_bar.tsx | 2 +- .../with_kuery_autocompletion.tsx | 6 +- .../observability/public/plugin.mock.tsx | 2 + .../plugins/observability/public/plugin.ts | 2 + .../components/alerts/query_bar.tsx | 4 +- .../public/legacy_uptime/app/uptime_app.tsx | 1 + .../alerts/alert_query_bar/query_bar.tsx | 4 +- .../overview/query_bar/query_bar.tsx | 4 +- .../lazy_wrapper/monitor_status.tsx | 2 +- .../plugins/uptime/public/plugin.ts | 2 + .../src/field_value_match/index.tsx | 2 +- .../src/field_value_match_any/index.tsx | 2 +- .../src/field_value_wildcard/index.tsx | 2 +- .../use_field_value_autocomplete/index.ts | 2 +- .../components/builder/builder.stories.tsx | 2 +- .../builder/entry_renderer.stories.tsx | 2 +- .../builder/entry_renderer.test.tsx | 4 +- .../components/builder/entry_renderer.tsx | 2 +- .../builder/exception_item_renderer.test.tsx | 4 +- .../builder/exception_item_renderer.tsx | 2 +- .../builder/exception_items_renderer.test.tsx | 4 +- .../builder/exception_items_renderer.tsx | 2 +- .../public/common/lib/kibana/services.ts | 4 +- .../severity_mapping/severity_override.tsx | 2 +- .../item_conditions/index.tsx | 4 +- .../management/hooks/use_suggestions.ts | 4 +- .../plugins/security_solution/public/types.ts | 2 + .../endpoint/routes/suggestions/index.test.ts | 12 +- .../endpoint/routes/suggestions/index.ts | 6 +- .../security_solution/server/plugin.ts | 2 +- .../server/plugin_contract.ts | 4 +- 151 files changed, 369 insertions(+), 3687 deletions(-) create mode 100644 src/platform/plugins/shared/kql/public/mocks.ts rename src/platform/plugins/shared/{unified_search => kql}/server/mocks.ts (100%) delete mode 100644 src/platform/plugins/shared/unified_search/public/autocomplete/autocomplete_service.ts delete mode 100644 src/platform/plugins/shared/unified_search/public/autocomplete/collectors/create_usage_collector.ts delete mode 100644 src/platform/plugins/shared/unified_search/public/autocomplete/collectors/index.ts delete mode 100644 src/platform/plugins/shared/unified_search/public/autocomplete/collectors/types.ts delete mode 100644 src/platform/plugins/shared/unified_search/public/autocomplete/index.ts delete mode 100644 src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/README.md delete mode 100644 src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/__fixtures__/index_pattern_response.json delete mode 100644 src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts delete mode 100644 src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/conjunction.tsx delete mode 100644 src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/field.test.ts delete mode 100644 src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/field.tsx delete mode 100644 src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/index.ts delete mode 100644 src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/kql_module.ts delete mode 100644 src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/operator.test.ts delete mode 100644 src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/operator.tsx delete mode 100644 src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.test.ts delete mode 100644 src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.ts delete mode 100644 src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/types.ts delete mode 100644 src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/value.test.ts delete mode 100644 src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/value.ts delete mode 100644 src/platform/plugins/shared/unified_search/public/autocomplete/providers/query_suggestion_provider.ts delete mode 100644 src/platform/plugins/shared/unified_search/public/autocomplete/providers/value_suggestion_provider.test.ts delete mode 100644 src/platform/plugins/shared/unified_search/public/autocomplete/providers/value_suggestion_provider.ts delete mode 100644 src/platform/plugins/shared/unified_search/server/autocomplete/autocomplete_service.ts delete mode 100644 src/platform/plugins/shared/unified_search/server/autocomplete/index.ts delete mode 100644 src/platform/plugins/shared/unified_search/server/autocomplete/routes.ts delete mode 100644 src/platform/plugins/shared/unified_search/server/autocomplete/terms_agg.test.ts delete mode 100644 src/platform/plugins/shared/unified_search/server/autocomplete/terms_agg.ts delete mode 100644 src/platform/plugins/shared/unified_search/server/autocomplete/terms_enum.test.ts delete mode 100644 src/platform/plugins/shared/unified_search/server/autocomplete/terms_enum.ts delete mode 100644 src/platform/plugins/shared/unified_search/server/autocomplete/value_suggestions_route.ts delete mode 100644 src/platform/plugins/shared/unified_search/server/config.ts delete mode 100644 src/platform/plugins/shared/unified_search/server/config_deprecations.test.ts delete mode 100644 src/platform/plugins/shared/unified_search/server/config_deprecations.ts delete mode 100644 src/platform/plugins/shared/unified_search/server/data_views/index.ts delete mode 100644 src/platform/plugins/shared/unified_search/server/index.ts delete mode 100644 src/platform/plugins/shared/unified_search/server/plugin.ts diff --git a/config/kibana.yml b/config/kibana.yml index ea8c763487aab..5c0ec35aa8ff5 100644 --- a/config/kibana.yml +++ b/config/kibana.yml @@ -172,8 +172,8 @@ # =================== Search Autocomplete =================== # Time in milliseconds to wait for autocomplete suggestions from Elasticsearch. # This value must be a whole number greater than zero. Defaults to 1000ms -#unifiedSearch.autocomplete.valueSuggestions.timeout: 1000 +#kql.autocomplete.valueSuggestions.timeout: 1000 # Maximum number of documents loaded by each shard to generate autocomplete suggestions. # This value must be a whole number greater than zero. Defaults to 100_000 -#unifiedSearch.autocomplete.valueSuggestions.terminateAfter: 100000 +#kql.autocomplete.valueSuggestions.terminateAfter: 100000 diff --git a/docs/reference/configuration-reference/general-settings.md b/docs/reference/configuration-reference/general-settings.md index a0f67e9248772..4f2aed779c77c 100644 --- a/docs/reference/configuration-reference/general-settings.md +++ b/docs/reference/configuration-reference/general-settings.md @@ -517,10 +517,10 @@ $$$settings-telemetry-optIn$$$ `telemetry.optIn` This setting can be changed at any time in [Advanced Settings](/reference/advanced-settings.md). To prevent users from changing it, set [`telemetry.allowChangingOptInStatus`](#telemetry-allowChangingOptInStatus) to `false`. **Default: `true`** -`unifiedSearch.autocomplete.valueSuggestions.timeout` ![logo cloud](https://doc-icons.s3.us-east-2.amazonaws.com/logo_cloud.svg "Supported on {{ech}}") +`kql.autocomplete.valueSuggestions.timeout` ![logo cloud](https://doc-icons.s3.us-east-2.amazonaws.com/logo_cloud.svg "Supported on {{ech}}") : Time in milliseconds to wait for autocomplete suggestions from {{es}}. This value must be a whole number greater than zero. **Default: `"1000"`** -`unifiedSearch.autocomplete.valueSuggestions.terminateAfter` ![logo cloud](https://doc-icons.s3.us-east-2.amazonaws.com/logo_cloud.svg "Supported on {{ech}}") +`kql.autocomplete.valueSuggestions.terminateAfter` ![logo cloud](https://doc-icons.s3.us-east-2.amazonaws.com/logo_cloud.svg "Supported on {{ech}}") : Maximum number of documents loaded by each shard to generate autocomplete suggestions. This value must be a whole number greater than zero. **Default: `"100000"`** ::::{note} diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker index 8e6192d910c34..798db80fb825e 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker @@ -53,11 +53,11 @@ kibana_vars=( data.search.sessions.notTouchedTimeout data.search.sessions.pageSize data.search.sessions.trackingInterval - unifiedSearch.autocomplete.valueSuggestions.terminateAfter - unifiedSearch.autocomplete.valueSuggestions.timeout - unifiedSearch.autocomplete.querySuggestions.enabled - unifiedSearch.autocomplete.valueSuggestions.enabled - unifiedSearch.autocomplete.valueSuggestions.tiers + kql.autocomplete.valueSuggestions.terminateAfter + kql.autocomplete.valueSuggestions.timeout + kql.autocomplete.querySuggestions.enabled + kql.autocomplete.valueSuggestions.enabled + kql.autocomplete.valueSuggestions.tiers elasticsearch.customHeaders elasticsearch.hosts elasticsearch.logQueries diff --git a/src/platform/packages/shared/kbn-event-annotation-components/components/annotation_editor_controls/index.test.tsx b/src/platform/packages/shared/kbn-event-annotation-components/components/annotation_editor_controls/index.test.tsx index 436b9c0b0fb9c..04449be8576c9 100644 --- a/src/platform/packages/shared/kbn-event-annotation-components/components/annotation_editor_controls/index.test.tsx +++ b/src/platform/packages/shared/kbn-event-annotation-components/components/annotation_editor_controls/index.test.tsx @@ -23,7 +23,7 @@ import { act } from 'react-dom/test-utils'; import { EuiButtonGroup, EuiThemeProvider } from '@elastic/eui'; import { render, screen } from '@testing-library/react'; -jest.mock('@kbn/unified-search-plugin/public', () => ({ +jest.mock('@kbn/kql/public', () => ({ QueryStringInput: () => { return 'QueryStringInput'; }, diff --git a/src/platform/packages/shared/kbn-lens-common/types.ts b/src/platform/packages/shared/kbn-lens-common/types.ts index 0c12268943737..22c6b8217ad57 100644 --- a/src/platform/packages/shared/kbn-lens-common/types.ts +++ b/src/platform/packages/shared/kbn-lens-common/types.ts @@ -71,6 +71,7 @@ import type { NavigationPublicPluginStart, TopNavMenuData } from '@kbn/navigatio import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public'; import type { ServerlessPluginStart } from '@kbn/serverless/public'; import type { SharePluginStart } from '@kbn/share-plugin/public'; +import type { KqlPluginStart } from '@kbn/kql/public'; import type { SpacesApi } from '@kbn/spaces-plugin/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; @@ -177,6 +178,7 @@ export interface LensAppServices extends StartServices { lensDocumentService: ILensDocumentService; serverless?: ServerlessPluginStart; cps?: CPSPluginStart; + kql: KqlPluginStart; } export type StartServices = Pick< diff --git a/src/platform/packages/shared/kbn-visualization-ui-components/components/query_input/query_input.tsx b/src/platform/packages/shared/kbn-visualization-ui-components/components/query_input/query_input.tsx index 16722cb714944..88bcb63b6eed3 100644 --- a/src/platform/packages/shared/kbn-visualization-ui-components/components/query_input/query_input.tsx +++ b/src/platform/packages/shared/kbn-visualization-ui-components/components/query_input/query_input.tsx @@ -11,10 +11,9 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { isEqual } from 'lodash'; import type { Query } from '@kbn/es-query'; -import { - type UnifiedSearchPublicPluginStart, - QueryStringInput, -} from '@kbn/unified-search-plugin/public'; +import { type UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import type { KqlPluginStart } from '@kbn/kql/public'; +import { QueryStringInput } from '@kbn/kql/public'; import type { HttpStart } from '@kbn/core-http-browser'; import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; @@ -32,6 +31,7 @@ export interface QueryInputServices { uiSettings: IUiSettingsClient; notifications: NotificationsStart; unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; docLinks: DocLinksStart; } @@ -45,7 +45,7 @@ export const QueryInput = ({ ['data-test-subj']: dataTestSubj, placeholder, appName, - services: { data, uiSettings, http, notifications, docLinks, storage, unifiedSearch, dataViews }, + services: { data, uiSettings, http, notifications, docLinks, storage, kql, dataViews, core }, }: { value: Query; onChange: (input: Query) => void; @@ -93,7 +93,17 @@ export const QueryInput = ({ } languageSwitcherPopoverAnchorPosition="rightDown" appName={appName} - deps={{ unifiedSearch, notifications, http, docLinks, uiSettings, data, storage, dataViews }} + deps={{ + autocomplete: kql.autocomplete, + core, + notifications, + http, + docLinks, + uiSettings, + data, + storage, + dataViews, + }} /> ); }; diff --git a/src/platform/plugins/private/input_control_vis/public/plugin.ts b/src/platform/plugins/private/input_control_vis/public/plugin.ts index a35f0c2a8d83b..a5ec8db5e9a10 100644 --- a/src/platform/plugins/private/input_control_vis/public/plugin.ts +++ b/src/platform/plugins/private/input_control_vis/public/plugin.ts @@ -14,6 +14,7 @@ import type { UnifiedSearchPublicPluginStart, UnifiedSearchPluginSetup, } from '@kbn/unified-search-plugin/public'; +import type { KqlPluginSetup } from '@kbn/kql/public'; import type { Plugin as ExpressionsPublicPlugin } from '@kbn/expressions-plugin/public'; import type { VisualizationsSetup, VisualizationsStart } from '@kbn/visualizations-plugin/public'; import type { UiActionsStart } from '@kbn/ui-actions-plugin/public'; @@ -34,6 +35,7 @@ export interface InputControlVisDependencies { core: InputControlVisCoreSetup; data: DataPublicPluginSetup; unifiedSearch: UnifiedSearchPluginSetup; + kql: KqlPluginSetup; getSettings: () => Promise; } @@ -43,6 +45,7 @@ export interface InputControlVisPluginSetupDependencies { visualizations: VisualizationsSetup; data: DataPublicPluginSetup; unifiedSearch: UnifiedSearchPluginSetup; + kql: KqlPluginSetup; } /** @internal */ @@ -60,13 +63,20 @@ export class InputControlVisPlugin implements Plugin { public setup( core: InputControlVisCoreSetup, - { expressions, visualizations, unifiedSearch, data }: InputControlVisPluginSetupDependencies + { + expressions, + visualizations, + unifiedSearch, + data, + kql, + }: InputControlVisPluginSetupDependencies ) { const visualizationDependencies: Readonly = { core, unifiedSearch, + kql, getSettings: async () => { - const { timeout, terminateAfter } = unifiedSearch.autocomplete.getAutocompleteSettings(); + const { timeout, terminateAfter } = kql.autocomplete.getAutocompleteSettings(); return { autocompleteTimeout: timeout, autocompleteTerminateAfter: terminateAfter }; }, data, diff --git a/src/platform/plugins/private/vis_default_editor/public/components/controls/filter.tsx b/src/platform/plugins/private/vis_default_editor/public/components/controls/filter.tsx index a3951d61d9734..084a01df8c003 100644 --- a/src/platform/plugins/private/vis_default_editor/public/components/controls/filter.tsx +++ b/src/platform/plugins/private/vis_default_editor/public/components/controls/filter.tsx @@ -45,9 +45,7 @@ function FilterRow({ const { services } = useKibana(); const { data, - unifiedSearch: { - ui: { QueryStringInput }, - }, + kql: { QueryStringInput }, appName, } = services; diff --git a/src/platform/plugins/shared/controls/server/options_list/options_list_suggestions_route.ts b/src/platform/plugins/shared/controls/server/options_list/options_list_suggestions_route.ts index c7f8caddf0d75..736b2753241b3 100644 --- a/src/platform/plugins/shared/controls/server/options_list/options_list_suggestions_route.ts +++ b/src/platform/plugins/shared/controls/server/options_list/options_list_suggestions_route.ts @@ -12,7 +12,7 @@ import type { Observable } from 'rxjs'; import { schema } from '@kbn/config-schema'; import type { CoreSetup, ElasticsearchClient } from '@kbn/core/server'; import { getKbnServerError, reportServerError } from '@kbn/kibana-utils-plugin/server'; -import type { PluginSetup as UnifiedSearchPluginSetup } from '@kbn/unified-search-plugin/server'; +import type { PluginSetup as KqlPluginSetup } from '@kbn/kql/server'; import type { SearchRequest } from '@elastic/elasticsearch/lib/api/types'; import type { OptionsListRequestBody, OptionsListResponse } from '../../common/options_list/types'; @@ -21,7 +21,7 @@ import { getSuggestionAggregationBuilder } from './suggestion_queries'; export const setupOptionsListSuggestionsRoute = ( { http }: CoreSetup, - getAutocompleteSettings: UnifiedSearchPluginSetup['autocomplete']['getAutocompleteSettings'] + getAutocompleteSettings: KqlPluginSetup['autocomplete']['getAutocompleteSettings'] ) => { const router = http.createRouter(); diff --git a/src/platform/plugins/shared/controls/server/plugin.ts b/src/platform/plugins/shared/controls/server/plugin.ts index cd75903266958..71c842ead0e54 100644 --- a/src/platform/plugins/shared/controls/server/plugin.ts +++ b/src/platform/plugins/shared/controls/server/plugin.ts @@ -10,7 +10,7 @@ import type { CoreSetup, Plugin } from '@kbn/core/server'; import type { PluginSetup as DataSetup } from '@kbn/data-plugin/server'; import type { EmbeddableSetup } from '@kbn/embeddable-plugin/server'; -import type { PluginSetup as UnifiedSearchSetup } from '@kbn/unified-search-plugin/server'; +import type { PluginSetup as KqlSetup } from '@kbn/kql/server'; import { setupOptionsListSuggestionsRoute } from './options_list/options_list_suggestions_route'; import { controlGroupContainerPersistableStateServiceFactory } from './control_group/control_group_container_factory'; import { optionsListPersistableStateServiceFactory } from './options_list/options_list_embeddable_factory'; @@ -22,11 +22,11 @@ import { setupOptionsListClusterSettingsRoute } from './options_list/options_lis interface SetupDeps { embeddable: EmbeddableSetup; data: DataSetup; - unifiedSearch: UnifiedSearchSetup; + kql: KqlSetup; } export class ControlsPlugin implements Plugin { - public setup(core: CoreSetup, { embeddable, unifiedSearch }: SetupDeps) { + public setup(core: CoreSetup, { embeddable, kql }: SetupDeps) { embeddable.registerEmbeddableFactory( controlGroupContainerPersistableStateServiceFactory(embeddable) ); @@ -35,7 +35,7 @@ export class ControlsPlugin implements Plugin { embeddable.registerEmbeddableFactory(timeSliderPersistableStateServiceFactory()); embeddable.registerEmbeddableFactory(esqlStaticControlPersistableStateServiceFactory()); setupOptionsListClusterSettingsRoute(core); - setupOptionsListSuggestionsRoute(core, unifiedSearch.autocomplete.getAutocompleteSettings); + setupOptionsListSuggestionsRoute(core, kql.autocomplete.getAutocompleteSettings); return {}; } diff --git a/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/conjunction.tsx b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/conjunction.tsx index 9c34f0919687a..c267d49081719 100644 --- a/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/conjunction.tsx +++ b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/conjunction.tsx @@ -16,17 +16,17 @@ import { QuerySuggestionTypes } from '../query_suggestion_provider'; const bothArgumentsText = ( ); const oneOrMoreArgumentsText = ( ); @@ -34,20 +34,20 @@ const conjunctions: Record = { and: (

{bothArgumentsText}, }} description="Full text: ' Requires both arguments to be true'. See - 'unifiedSearch.kueryAutocomplete.andOperatorDescription.bothArgumentsText' for 'both arguments' part." + 'kql.kueryAutocomplete.andOperatorDescription.bothArgumentsText' for 'both arguments' part." />

), or: (

= { ), }} description="Full text: 'Requires one or more arguments to be true'. See - 'unifiedSearch.kueryAutocomplete.orOperatorDescription.oneOrMoreArgumentsText' for 'one or more arguments' part." + 'kql.kueryAutocomplete.orOperatorDescription.oneOrMoreArgumentsText' for 'one or more arguments' part." />

), diff --git a/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/operator.tsx b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/operator.tsx index 3ef36221235f5..fc9db819e5973 100644 --- a/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/operator.tsx +++ b/src/platform/plugins/shared/kql/public/autocomplete/providers/kql_query_suggestion/operator.tsx @@ -17,44 +17,44 @@ import { QuerySuggestionTypes } from '../query_suggestion_provider'; const equalsText = ( ); const lessThanOrEqualToText = ( ); const greaterThanOrEqualToText = ( ); const lessThanText = ( ); const greaterThanText = ( ); const existsText = ( ); @@ -62,11 +62,11 @@ const operators = { ':': { description: ( {equalsText} }} description="Full text: 'equals some value'. See - 'unifiedSearch.kueryAutocomplete.equalOperatorDescription.equalsText' for 'equals' part." + 'kql.kueryAutocomplete.equalOperatorDescription.equalsText' for 'equals' part." /> ), fieldTypes: [ @@ -85,7 +85,7 @@ const operators = { '<=': { description: ( ), fieldTypes: ['number', 'number_range', 'date', 'date_range', 'ip', 'ip_range'], @@ -101,7 +101,7 @@ const operators = { '>=': { description: ( ), fieldTypes: ['number', 'number_range', 'date', 'date_range', 'ip', 'ip_range'], @@ -117,11 +117,11 @@ const operators = { '<': { description: ( {lessThanText} }} description="Full text: 'is less than some value'. See - 'unifiedSearch.kueryAutocomplete.lessThanOperatorDescription.lessThanText' for 'less than' part." + 'kql.kueryAutocomplete.lessThanOperatorDescription.lessThanText' for 'less than' part." /> ), fieldTypes: ['number', 'number_range', 'date', 'date_range', 'ip', 'ip_range'], @@ -129,13 +129,13 @@ const operators = { '>': { description: ( {greaterThanText}, }} description="Full text: 'is greater than some value'. See - 'unifiedSearch.kueryAutocomplete.greaterThanOperatorDescription.greaterThanText' for 'greater than' part." + 'kql.kueryAutocomplete.greaterThanOperatorDescription.greaterThanText' for 'greater than' part." /> ), fieldTypes: ['number', 'number_range', 'date', 'date_range', 'ip', 'ip_range'], @@ -143,11 +143,11 @@ const operators = { ': *': { description: ( {existsText} }} description="Full text: 'exists in any form'. See - 'unifiedSearch.kueryAutocomplete.existOperatorDescription.existsText' for 'exists' part." + 'kql.kueryAutocomplete.existOperatorDescription.existsText' for 'exists' part." /> ), fieldTypes: undefined, diff --git a/src/platform/plugins/shared/kql/public/components/query_string_input/language_switcher.tsx b/src/platform/plugins/shared/kql/public/components/query_string_input/language_switcher.tsx index a8f31744401bf..248f29111bd74 100644 --- a/src/platform/plugins/shared/kql/public/components/query_string_input/language_switcher.tsx +++ b/src/platform/plugins/shared/kql/public/components/query_string_input/language_switcher.tsx @@ -24,15 +24,15 @@ import { i18n } from '@kbn/i18n'; export const strings = { getSwitchLanguageButtonText: () => - i18n.translate('unifiedSearch.switchLanguage.buttonText', { + i18n.translate('kql.switchLanguage.buttonText', { defaultMessage: 'Switch language button.', }), getFilterLanguageLabel: () => - i18n.translate('unifiedSearch.switchLanguage.filterLanguageLabel', { + i18n.translate('kql.switchLanguage.filterLanguageLabel', { defaultMessage: 'Filter language', }), documentationLabel: () => - i18n.translate('unifiedSearch.switchLanguage.documentationLabel', { + i18n.translate('kql.switchLanguage.documentationLabel', { defaultMessage: 'Documentation', }), }; @@ -129,7 +129,7 @@ export const QueryLanguageSwitcher = React.memo(function QueryLanguageSwitcher({ > diff --git a/src/platform/plugins/shared/kql/public/components/query_string_input/query_string_input.tsx b/src/platform/plugins/shared/kql/public/components/query_string_input/query_string_input.tsx index caddc8cf97ac0..77353fa8ea90b 100644 --- a/src/platform/plugins/shared/kql/public/components/query_string_input/query_string_input.tsx +++ b/src/platform/plugins/shared/kql/public/components/query_string_input/query_string_input.tsx @@ -60,30 +60,30 @@ import { StyledDiv } from './query_string_input.styles'; export const strings = { getSearchInputPlaceholderForText: () => - i18n.translate('unifiedSearch.query.queryBar.searchInputPlaceholderForText', { + i18n.translate('kql.query.queryBar.searchInputPlaceholderForText', { defaultMessage: 'Filter your data', }), getSearchInputPlaceholder: (language: string) => - i18n.translate('unifiedSearch.query.queryBar.searchInputPlaceholder', { + i18n.translate('kql.query.queryBar.searchInputPlaceholder', { defaultMessage: 'Filter your data using {language} syntax', values: { language }, }), getQueryBarComboboxAriaLabel: (pageType: string) => - i18n.translate('unifiedSearch.query.queryBar.comboboxAriaLabel', { + i18n.translate('kql.query.queryBar.comboboxAriaLabel', { defaultMessage: 'Search and filter the {pageType} page', values: { pageType }, }), getQueryBarSearchInputAriaLabel: (pageType: string) => - i18n.translate('unifiedSearch.query.queryBar.searchInputAriaLabel', { + i18n.translate('kql.query.queryBar.searchInputAriaLabel', { defaultMessage: 'Start typing to search and filter the {pageType} page', values: { pageType }, }), getQueryBarClearInputLabel: () => - i18n.translate('unifiedSearch.query.queryBar.clearInputLabel', { + i18n.translate('kql.query.queryBar.clearInputLabel', { defaultMessage: 'Clear input', }), getKQLNestedQuerySyntaxInfoTitle: () => - i18n.translate('unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoTitle', { + i18n.translate('kql.query.queryBar.KQLNestedQuerySyntaxInfoTitle', { defaultMessage: 'KQL nested query syntax', }), }; @@ -537,7 +537,7 @@ export class QueryStringInput extends PureComponent

@@ -557,7 +557,7 @@ export class QueryStringInput extends PureComponent onKQLNestedQuerySyntaxInfoOptOut(toast)}> diff --git a/src/platform/plugins/shared/kql/public/index.ts b/src/platform/plugins/shared/kql/public/index.ts index 206946cf29ae3..0693300995b75 100644 --- a/src/platform/plugins/shared/kql/public/index.ts +++ b/src/platform/plugins/shared/kql/public/index.ts @@ -6,6 +6,15 @@ * your election, the "Elastic License 2.0", the "GNU Affero General Public * License v3.0 only", or the "Server Side Public License, v 1". */ -export type { KqlPluginStart } from './plugin'; +export type { KqlPluginStart, KqlPluginSetup } from './plugin'; export type { QueryStringInputProps } from './components/query_string_input/query_string_input'; export { QueryStringInput } from './components/query_string_input/query_string_input'; + +export type { + QuerySuggestion, + QuerySuggestionGetFn, + QuerySuggestionGetFnArgs, + AutocompleteStart, +} from './autocomplete'; + +export { QuerySuggestionTypes } from './autocomplete/providers/query_suggestion_provider'; diff --git a/src/platform/plugins/shared/kql/public/mocks.ts b/src/platform/plugins/shared/kql/public/mocks.ts new file mode 100644 index 0000000000000..be655a3d64c54 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/mocks.ts @@ -0,0 +1,42 @@ +/* + * 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 type { KqlPlugin } from './plugin'; +import type { AutocompleteStart, AutocompleteSetup } from './autocomplete'; + +export type Setup = jest.Mocked>; +export type Start = jest.Mocked>; +const autocompleteSetupMock: jest.Mocked = { + getQuerySuggestions: jest.fn(), + getAutocompleteSettings: jest.fn(), +}; + +const autocompleteStartMock: jest.Mocked = { + getValueSuggestions: jest.fn(), + getQuerySuggestions: jest.fn(), + hasQuerySuggestions: jest.fn(), +}; + +const createSetupContract = (): Setup => { + return { + autocomplete: autocompleteSetupMock, + }; +}; + +const createStartContract = (): Start => { + return { + autocomplete: autocompleteStartMock, + QueryStringInput: jest.fn().mockReturnValue('QueryStringInput'), + }; +}; + +export const kqlPluginMock = { + createStartContract, + createSetupContract, +}; diff --git a/src/platform/plugins/shared/kql/public/plugin.ts b/src/platform/plugins/shared/kql/public/plugin.ts index e82d4f1e710bd..659e008eb26a0 100644 --- a/src/platform/plugins/shared/kql/public/plugin.ts +++ b/src/platform/plugins/shared/kql/public/plugin.ts @@ -15,7 +15,11 @@ import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; import { createQueryStringInput } from './components/query_string_input/get_query_string_input'; import type { QueryStringInputProps } from './components/query_string_input/query_string_input'; -import { AutocompleteService, type AutocompleteStart } from './autocomplete/autocomplete_service'; +import { + AutocompleteService, + type AutocompleteStart, + type AutocompleteSetup, +} from './autocomplete/autocomplete_service'; export interface KqlPluginSetupDependencies { data: DataPublicPluginSetup; @@ -27,6 +31,10 @@ export interface KqlPluginStartDependencies { dataViews: DataViewsPublicPluginStart; } +export interface KqlPluginSetup { + autocomplete: AutocompleteSetup; +} + export interface KqlPluginStart { /** * autocomplete service @@ -48,7 +56,7 @@ export class KqlPlugin implements Plugin<{}, KqlPluginStart> { public setup( core: CoreSetup, { data, usageCollection }: KqlPluginSetupDependencies - ) { + ): KqlPluginSetup { const { query } = data; return { autocomplete: this.autocomplete.setup(core, { diff --git a/src/platform/plugins/shared/unified_search/server/mocks.ts b/src/platform/plugins/shared/kql/server/mocks.ts similarity index 100% rename from src/platform/plugins/shared/unified_search/server/mocks.ts rename to src/platform/plugins/shared/kql/server/mocks.ts diff --git a/src/platform/plugins/shared/unified_search/kibana.jsonc b/src/platform/plugins/shared/unified_search/kibana.jsonc index c2fcf824054dc..24efd5e775028 100644 --- a/src/platform/plugins/shared/unified_search/kibana.jsonc +++ b/src/platform/plugins/shared/unified_search/kibana.jsonc @@ -13,7 +13,7 @@ "plugin": { "id": "unifiedSearch", "browser": true, - "server": true, + "server": false, "configPath": [ "unifiedSearch" ], diff --git a/src/platform/plugins/shared/unified_search/moon.yml b/src/platform/plugins/shared/unified_search/moon.yml index e328cb131620d..fdbeed5f2ea0a 100644 --- a/src/platform/plugins/shared/unified_search/moon.yml +++ b/src/platform/plugins/shared/unified_search/moon.yml @@ -74,7 +74,6 @@ fileGroups: src: - public/**/* - public/**/*.json - - server/**/* - '!target/**/*' tasks: jest: diff --git a/src/platform/plugins/shared/unified_search/public/autocomplete/autocomplete_service.ts b/src/platform/plugins/shared/unified_search/public/autocomplete/autocomplete_service.ts deleted file mode 100644 index 2905a139a1ec5..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/autocomplete/autocomplete_service.ts +++ /dev/null @@ -1,104 +0,0 @@ -/* - * 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 type { CoreSetup, PluginInitializerContext } from '@kbn/core/public'; -import moment from 'moment'; -import type { TimefilterSetup } from '@kbn/data-plugin/public'; -import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; -import type { QuerySuggestionGetFn } from './providers/query_suggestion_provider'; -import { - getEmptyValueSuggestions, - setupValueSuggestionProvider, -} from './providers/value_suggestion_provider'; -import type { ValueSuggestionsGetFn } from './providers/value_suggestion_provider'; - -import type { ConfigSchema } from '../../server/config'; -import { createUsageCollector } from './collectors'; -import { - KUERY_LANGUAGE_NAME, - setupKqlQuerySuggestionProvider, -} from './providers/kql_query_suggestion'; -import type { UnifiedSearchPublicPluginStart, UnifiedSearchStartDependencies } from '../types'; - -export class AutocompleteService { - autocompleteConfig: ConfigSchema['autocomplete']; - - constructor(private initializerContext: PluginInitializerContext) { - const { autocomplete } = initializerContext.config.get(); - - this.autocompleteConfig = autocomplete; - } - - private readonly querySuggestionProviders: Map = new Map(); - private getValueSuggestions?: ValueSuggestionsGetFn; - - private getQuerySuggestions: QuerySuggestionGetFn = (args) => { - const { language } = args; - const provider = this.querySuggestionProviders.get(language); - - if (provider) { - return provider(args); - } - }; - - private hasQuerySuggestions = (language: string) => this.querySuggestionProviders.has(language); - - /** @public **/ - public setup( - core: CoreSetup, - { - timefilter, - usageCollection, - }: { timefilter: TimefilterSetup; usageCollection?: UsageCollectionSetup } - ) { - const { autocomplete } = this.initializerContext.config.get(); - const { terminateAfter, timeout } = autocomplete.valueSuggestions; - const usageCollector = createUsageCollector(core.getStartServices, usageCollection); - - this.getValueSuggestions = this.autocompleteConfig.valueSuggestions.enabled - ? setupValueSuggestionProvider(core, { timefilter, usageCollector }) - : getEmptyValueSuggestions; - - if (this.autocompleteConfig.querySuggestions.enabled) { - this.querySuggestionProviders.set(KUERY_LANGUAGE_NAME, setupKqlQuerySuggestionProvider(core)); - } - - return { - /** - * @deprecated - * please use "getQuerySuggestions" from the start contract - */ - getQuerySuggestions: this.getQuerySuggestions, - getAutocompleteSettings: () => ({ - terminateAfter: moment.duration(terminateAfter).asMilliseconds(), - timeout: moment.duration(timeout).asMilliseconds(), - }), - }; - } - - /** @public **/ - public start() { - return { - getQuerySuggestions: this.getQuerySuggestions, - hasQuerySuggestions: this.hasQuerySuggestions, - getValueSuggestions: this.getValueSuggestions!, - }; - } - - /** @internal **/ - public clearProviders(): void { - this.querySuggestionProviders.clear(); - } -} - -/** @public **/ -export type AutocompleteSetup = ReturnType; - -/** @public **/ -export type AutocompleteStart = ReturnType; diff --git a/src/platform/plugins/shared/unified_search/public/autocomplete/collectors/create_usage_collector.ts b/src/platform/plugins/shared/unified_search/public/autocomplete/collectors/create_usage_collector.ts deleted file mode 100644 index c1679de364d26..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/autocomplete/collectors/create_usage_collector.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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 { first } from 'rxjs'; -import { METRIC_TYPE } from '@kbn/analytics'; -import type { StartServicesAccessor } from '@kbn/core/public'; -import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; -import type { AutocompleteUsageCollector } from './types'; -import { AUTOCOMPLETE_EVENT_TYPE } from './types'; - -export const createUsageCollector = ( - getStartServices: StartServicesAccessor, - usageCollection?: UsageCollectionSetup -): AutocompleteUsageCollector => { - const getCurrentApp = async () => { - const [{ application }] = await getStartServices(); - return application.currentAppId$.pipe(first()).toPromise(); - }; - - return { - trackCall: async () => { - const currentApp = await getCurrentApp(); - return usageCollection?.reportUiCounter( - currentApp!, - METRIC_TYPE.LOADED, - AUTOCOMPLETE_EVENT_TYPE.CALL - ); - }, - trackRequest: async () => { - const currentApp = await getCurrentApp(); - return usageCollection?.reportUiCounter( - currentApp!, - METRIC_TYPE.LOADED, - AUTOCOMPLETE_EVENT_TYPE.REQUEST - ); - }, - trackResult: async () => { - const currentApp = await getCurrentApp(); - return usageCollection?.reportUiCounter( - currentApp!, - METRIC_TYPE.LOADED, - AUTOCOMPLETE_EVENT_TYPE.RESULT - ); - }, - trackError: async () => { - const currentApp = await getCurrentApp(); - return usageCollection?.reportUiCounter( - currentApp!, - METRIC_TYPE.LOADED, - AUTOCOMPLETE_EVENT_TYPE.ERROR - ); - }, - }; -}; diff --git a/src/platform/plugins/shared/unified_search/public/autocomplete/collectors/index.ts b/src/platform/plugins/shared/unified_search/public/autocomplete/collectors/index.ts deleted file mode 100644 index 758587e69d85d..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/autocomplete/collectors/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * 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". - */ - -export { createUsageCollector } from './create_usage_collector'; -export type { AutocompleteUsageCollector } from './types'; -export { AUTOCOMPLETE_EVENT_TYPE } from './types'; diff --git a/src/platform/plugins/shared/unified_search/public/autocomplete/collectors/types.ts b/src/platform/plugins/shared/unified_search/public/autocomplete/collectors/types.ts deleted file mode 100644 index b25cd305ba51e..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/autocomplete/collectors/types.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * 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". - */ - -export enum AUTOCOMPLETE_EVENT_TYPE { - CALL = 'autocomplete:call', - REQUEST = 'autocomplete:req', - RESULT = 'autocomplete:res', - ERROR = 'autocomplete:err', -} - -export interface AutocompleteUsageCollector { - trackCall: () => Promise; - trackRequest: () => Promise; - trackResult: () => Promise; - trackError: () => Promise; -} diff --git a/src/platform/plugins/shared/unified_search/public/autocomplete/index.ts b/src/platform/plugins/shared/unified_search/public/autocomplete/index.ts deleted file mode 100644 index 0d54c2b2c97e6..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/autocomplete/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * 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". - */ - -export type { - QuerySuggestion, - QuerySuggestionGetFn, - QuerySuggestionGetFnArgs, - QuerySuggestionBasic, - QuerySuggestionField, -} from './providers/query_suggestion_provider'; -export { QuerySuggestionTypes } from './providers/query_suggestion_provider'; - -export type { AutocompleteSetup, AutocompleteStart } from './autocomplete_service'; -export { AutocompleteService } from './autocomplete_service'; diff --git a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/README.md b/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/README.md deleted file mode 100644 index 2ab87a7a490c1..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/README.md +++ /dev/null @@ -1 +0,0 @@ -This is implementation of KQL query suggestions diff --git a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/__fixtures__/index_pattern_response.json b/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/__fixtures__/index_pattern_response.json deleted file mode 100644 index b69403326f74c..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/__fixtures__/index_pattern_response.json +++ /dev/null @@ -1,333 +0,0 @@ -{ - "id": "logstash-*", - "title": "logstash-*", - "fields": [ - { - "name": "bytes", - "type": "number", - "esTypes": ["long"], - "count": 10, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true - }, - { - "name": "bytes_range", - "type": "number_range", - "esTypes": ["long_range"], - "count": 10, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true - }, - { - "name": "ssl", - "type": "boolean", - "esTypes": ["boolean"], - "count": 20, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true - }, - { - "name": "@timestamp", - "type": "date", - "esTypes": ["date"], - "count": 30, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true - }, - { - "name": "time", - "type": "date", - "esTypes": ["date"], - "count": 30, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true - }, - { - "name": "time_range", - "type": "date_range", - "esTypes": ["date_range"], - "count": 10, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true - }, - { - "name": "@tags", - "type": "string", - "esTypes": ["keyword"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true - }, - { - "name": "utc_time", - "type": "date", - "esTypes": ["date"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true - }, - { - "name": "phpmemory", - "type": "number", - "esTypes": ["integer"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true - }, - { - "name": "ip", - "type": "ip", - "esTypes": ["ip"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true - }, - { - "name": "ip_range", - "type": "ip_range", - "esTypes": ["ip_range"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true - }, - { - "name": "request_body", - "type": "attachment", - "esTypes": ["attachment"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true - }, - { - "name": "point", - "type": "geo_point", - "esTypes": ["geo_point"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true - }, - { - "name": "area", - "type": "geo_shape", - "esTypes": ["geo_shape"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true - }, - { - "name": "hashed", - "type": "murmur3", - "esTypes": ["murmur3"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": false, - "readFromDocValues": false - }, - { - "name": "geo.coordinates", - "type": "geo_point", - "esTypes": ["geo_point"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true - }, - { - "name": "extension", - "type": "string", - "esTypes": ["keyword"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true - }, - { - "name": "machine.os", - "type": "string", - "esTypes": ["text"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": false - }, - { - "name": "machine.os.raw", - "type": "string", - "esTypes": ["keyword"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true, - "subType":{ "multi":{ "parent": "machine.os" } } - }, - { - "name": "geo.src", - "type": "string", - "esTypes": ["keyword"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true - }, - { - "name": "_id", - "type": "string", - "esTypes": ["_id"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": false - }, - { - "name": "_type", - "type": "string", - "esTypes": ["_type"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": false - }, - { - "name": "_source", - "type": "_source", - "esTypes": ["_source"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": false - }, - { - "name": "non-filterable", - "type": "string", - "esTypes": ["text"], - "count": 0, - "scripted": false, - "searchable": false, - "aggregatable": true, - "readFromDocValues": false - }, - { - "name": "non-sortable", - "type": "string", - "esTypes": ["text"], - "count": 0, - "scripted": false, - "searchable": false, - "aggregatable": false, - "readFromDocValues": false - }, - { - "name": "custom_user_field", - "type": "conflict", - "esTypes": ["long", "text"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true - }, - { - "name": "script string", - "type": "string", - "count": 0, - "scripted": true, - "script": "'i am a string'", - "lang": "expression", - "searchable": true, - "aggregatable": true, - "readFromDocValues": false - }, - { - "name": "script number", - "type": "number", - "count": 0, - "scripted": true, - "script": "1234", - "lang": "expression", - "searchable": true, - "aggregatable": true, - "readFromDocValues": false - }, - { - "name": "script date", - "type": "date", - "count": 0, - "scripted": true, - "script": "1234", - "lang": "painless", - "searchable": true, - "aggregatable": true, - "readFromDocValues": false - }, - { - "name": "script murmur3", - "type": "murmur3", - "count": 0, - "scripted": true, - "script": "1234", - "lang": "expression", - "searchable": true, - "aggregatable": true, - "readFromDocValues": false - }, - { - "name": "nestedField.child", - "type": "string", - "esTypes": ["text"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": false, - "readFromDocValues": false, - "subType": { "nested": { "path": "nestedField" } } - }, - { - "name": "nestedField.nestedChild.doublyNestedChild", - "type": "string", - "esTypes": ["text"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": false, - "readFromDocValues": false, - "subType": { "nested": { "path": "nestedField.nestedChild" } } - } - ] -} diff --git a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts b/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts deleted file mode 100644 index 206840648bf5d..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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 { coreMock } from '@kbn/core/public/mocks'; -import type { KueryNode } from '@kbn/es-query'; -import { setupGetConjunctionSuggestions } from './conjunction'; -import type { QuerySuggestionGetFnArgs } from '../query_suggestion_provider'; - -const mockKueryNode = (kueryNode: Partial) => kueryNode as unknown as KueryNode; - -describe('Kuery conjunction suggestions', () => { - const querySuggestionsArgs = null as unknown as QuerySuggestionGetFnArgs; - let getSuggestions: ReturnType; - - beforeEach(() => { - getSuggestions = setupGetConjunctionSuggestions(coreMock.createSetup()); - }); - - test('should return a function', () => { - expect(typeof getSuggestions).toBe('function'); - }); - - test('should not suggest anything for phrases not ending in whitespace', async () => { - const text = 'foo'; - const suggestions = await getSuggestions(querySuggestionsArgs, mockKueryNode({ text })); - - expect(suggestions).toEqual([]); - }); - - test('should suggest and/or for phrases ending in whitespace', async () => { - const text = 'foo '; - const suggestions = await getSuggestions(querySuggestionsArgs, mockKueryNode({ text })); - - expect(suggestions.length).toBe(2); - expect(suggestions.map((suggestion) => suggestion.text)).toEqual(['and ', 'or ']); - }); - - test('should suggest to insert the suggestion at the end of the string', async () => { - const text = 'bar '; - const end = text.length; - const suggestions = await getSuggestions(querySuggestionsArgs, mockKueryNode({ text, end })); - - expect(suggestions.length).toBe(2); - expect(suggestions.map((suggestion) => suggestion.start)).toEqual([end, end]); - expect(suggestions.map((suggestion) => suggestion.end)).toEqual([end, end]); - }); - - test('should have descriptions', async () => { - const text = ' '; - const suggestions = await getSuggestions(querySuggestionsArgs, mockKueryNode({ text })); - - expect(typeof suggestions).toBe('object'); - expect(Object.keys(suggestions).length).toBe(2); - - suggestions.forEach((suggestion) => { - expect(typeof suggestion).toBe('object'); - expect(suggestion).toHaveProperty('description'); - }); - }); -}); diff --git a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/conjunction.tsx b/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/conjunction.tsx deleted file mode 100644 index 9c34f0919687a..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/conjunction.tsx +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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 React from 'react'; -import type { $Keys } from 'utility-types'; -import { FormattedMessage } from '@kbn/i18n-react'; -import type { KqlQuerySuggestionProvider } from './types'; -import type { QuerySuggestion } from '../query_suggestion_provider'; -import { QuerySuggestionTypes } from '../query_suggestion_provider'; - -const bothArgumentsText = ( - -); - -const oneOrMoreArgumentsText = ( - -); - -const conjunctions: Record = { - and: ( -

- {bothArgumentsText}, - }} - description="Full text: ' Requires both arguments to be true'. See - 'unifiedSearch.kueryAutocomplete.andOperatorDescription.bothArgumentsText' for 'both arguments' part." - /> -

- ), - or: ( -

- {oneOrMoreArgumentsText} - ), - }} - description="Full text: 'Requires one or more arguments to be true'. See - 'unifiedSearch.kueryAutocomplete.orOperatorDescription.oneOrMoreArgumentsText' for 'one or more arguments' part." - /> -

- ), -}; - -export const setupGetConjunctionSuggestions: KqlQuerySuggestionProvider = (core) => { - return (querySuggestionsArgs, { text, end }) => { - let suggestions: QuerySuggestion[] | [] = []; - - if (text.endsWith(' ')) { - suggestions = Object.keys(conjunctions).map((key: $Keys) => ({ - type: QuerySuggestionTypes.Conjunction, - text: `${key} `, - description: conjunctions[key], - start: end, - end, - })); - } - - return Promise.resolve(suggestions); - }; -}; diff --git a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/field.test.ts b/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/field.test.ts deleted file mode 100644 index 76bc53461d0b4..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/field.test.ts +++ /dev/null @@ -1,233 +0,0 @@ -/* - * 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 indexPatternResponse from './__fixtures__/index_pattern_response.json'; - -import { isFilterable } from '@kbn/data-views-plugin/common'; -import type { KueryNode } from '@kbn/es-query'; -import { setupGetFieldSuggestions } from './field'; -import type { QuerySuggestionGetFnArgs } from '../query_suggestion_provider'; -import { coreMock } from '@kbn/core/public/mocks'; -import type { DataViewField } from '@kbn/data-views-plugin/public'; - -const mockKueryNode = (kueryNode: Partial) => kueryNode as unknown as KueryNode; - -describe('Kuery field suggestions', () => { - let querySuggestionsArgs: QuerySuggestionGetFnArgs; - let getSuggestions: ReturnType; - - beforeEach(() => { - querySuggestionsArgs = { - indexPatterns: [indexPatternResponse], - } as unknown as QuerySuggestionGetFnArgs; - - getSuggestions = setupGetFieldSuggestions(coreMock.createSetup()); - }); - - test('should return a function', () => { - expect(typeof getSuggestions).toBe('function'); - }); - - test('should return filterable fields', async () => { - const prefix = ''; - const suffix = ''; - const suggestions = await getSuggestions( - querySuggestionsArgs, - mockKueryNode({ prefix, suffix }) - ); - const filterableFields = (indexPatternResponse.fields as DataViewField[]).filter(isFilterable); - - expect(suggestions.length).toBe(filterableFields.length); - }); - - test('should filter suggestions based on the query', async () => { - const prefix = 'machine'; - const suffix = ''; - const suggestions = await getSuggestions( - querySuggestionsArgs, - mockKueryNode({ prefix, suffix }) - ); - - expect(suggestions.find(({ text }) => text === 'machine.os ')).toBeDefined(); - }); - - test('should filter suggestions case insensitively', async () => { - const prefix = 'MACHINE'; - const suffix = ''; - const suggestions = await getSuggestions( - querySuggestionsArgs, - mockKueryNode({ prefix, suffix }) - ); - - expect(suggestions.find(({ text }) => text === 'machine.os ')).toBeDefined(); - }); - - test('should return suggestions where the query matches somewhere in the middle', async () => { - const prefix = '.'; - const suffix = ''; - const suggestions = await getSuggestions( - querySuggestionsArgs, - mockKueryNode({ prefix, suffix }) - ); - - expect(suggestions.find(({ text }) => text === 'machine.os ')).toBeDefined(); - }); - - test('should field names that match the search', async () => { - const prefix = 'machi'; - const suffix = 'ne.os'; - const suggestions = await getSuggestions( - querySuggestionsArgs, - mockKueryNode({ prefix, suffix }) - ); - - expect(suggestions.find(({ text }) => text === 'machine.os ')).toBeDefined(); - }); - - test('should return field names that start with the query first', async () => { - const prefix = 'e'; - const suffix = ''; - const suggestions = await getSuggestions( - querySuggestionsArgs, - mockKueryNode({ prefix, suffix }) - ); - const extensionIndex = suggestions.findIndex(({ text }) => text === 'extension '); - const bytesIndex = suggestions.findIndex(({ text }) => text === 'bytes '); - - expect(extensionIndex).toBeLessThan(bytesIndex); - }); - - test('should sort keyword fields before analyzed versions', async () => { - const prefix = ''; - const suffix = ''; - const suggestions = await getSuggestions( - querySuggestionsArgs, - mockKueryNode({ prefix, suffix }) - ); - const analyzedIndex = suggestions.findIndex(({ text }) => text === 'machine.os '); - const keywordIndex = suggestions.findIndex(({ text }) => text === 'machine.os.raw '); - - expect(keywordIndex).toBeLessThan(analyzedIndex); - }); - - test('should not have descriptions', async () => { - const prefix = ''; - const suffix = ''; - const suggestions = await getSuggestions( - querySuggestionsArgs, - mockKueryNode({ prefix, suffix }) - ); - expect(suggestions.length).toBeGreaterThan(0); - suggestions.forEach((suggestion) => { - expect(suggestion).not.toHaveProperty('description'); - }); - }); - - describe('nested fields', () => { - test("should automatically wrap nested fields in KQL's nested syntax", async () => { - const prefix = 'ch'; - const suffix = ''; - const suggestions = await getSuggestions( - querySuggestionsArgs, - mockKueryNode({ prefix, suffix }) - ); - - const suggestion = suggestions.find(({ field }) => field.name === 'nestedField.child'); - - expect(suggestion).toBeDefined(); - - if (suggestion) { - expect(suggestion.text).toBe('nestedField:{ child }'); - - // For most suggestions the cursor can be placed at the end of the suggestion text, but - // for the nested field syntax we want to place the cursor inside the curly braces - expect(suggestion.cursorIndex).toBe(20); - } - }); - - test('should narrow suggestions to children of a nested path if provided', async () => { - const prefix = 'ch'; - const suffix = ''; - - const allSuggestions = await getSuggestions( - querySuggestionsArgs, - mockKueryNode({ prefix, suffix }) - ); - expect(allSuggestions.length).toBeGreaterThan(2); - - const nestedSuggestions = await getSuggestions( - querySuggestionsArgs, - mockKueryNode({ - prefix, - suffix, - nestedPath: 'nestedField', - }) - ); - expect(nestedSuggestions).toHaveLength(2); - }); - - test("should not wrap the suggestion in KQL's nested syntax if the correct nested path is already provided", async () => { - const prefix = 'ch'; - const suffix = ''; - - const suggestions = await getSuggestions( - querySuggestionsArgs, - mockKueryNode({ - prefix, - suffix, - nestedPath: 'nestedField', - }) - ); - const suggestion = suggestions.find(({ field }) => field.name === 'nestedField.child'); - - expect(suggestion).toBeDefined(); - - if (suggestion) { - expect(suggestion.text).toBe('child '); - } - }); - - test('should handle fields nested multiple levels deep', async () => { - const prefix = 'doubly'; - const suffix = ''; - - const suggestionsWithNoPath = await getSuggestions( - querySuggestionsArgs, - mockKueryNode({ prefix, suffix }) - ); - expect(suggestionsWithNoPath).toHaveLength(1); - const [noPathSuggestion] = suggestionsWithNoPath; - expect(noPathSuggestion.text).toBe('nestedField.nestedChild:{ doublyNestedChild }'); - - const suggestionsWithPartialPath = await getSuggestions( - querySuggestionsArgs, - mockKueryNode({ - prefix, - suffix, - nestedPath: 'nestedField', - }) - ); - expect(suggestionsWithPartialPath).toHaveLength(1); - const [partialPathSuggestion] = suggestionsWithPartialPath; - expect(partialPathSuggestion.text).toBe('nestedChild:{ doublyNestedChild }'); - - const suggestionsWithFullPath = await getSuggestions( - querySuggestionsArgs, - mockKueryNode({ - prefix, - suffix, - nestedPath: 'nestedField.nestedChild', - }) - ); - expect(suggestionsWithFullPath).toHaveLength(1); - const [fullPathSuggestion] = suggestionsWithFullPath; - expect(fullPathSuggestion.text).toBe('doublyNestedChild '); - }); - }); -}); diff --git a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/field.tsx b/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/field.tsx deleted file mode 100644 index 8aa123bb1af1f..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/field.tsx +++ /dev/null @@ -1,106 +0,0 @@ -/* - * 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 { isFilterable, getFieldSubtypeNested } from '@kbn/data-views-plugin/common'; -import type { DataViewField } from '@kbn/data-views-plugin/public'; -import { flatten } from 'lodash'; -import { escapeKuery } from '@kbn/es-query'; -import { sortPrefixFirst } from './sort_prefix_first'; -import type { QuerySuggestionField } from '../query_suggestion_provider'; -import { QuerySuggestionTypes } from '../query_suggestion_provider'; -import type { KqlQuerySuggestionProvider } from './types'; - -const keywordComparator = (first: DataViewField, second: DataViewField) => { - const extensions = ['raw', 'keyword']; - if (extensions.map((ext) => `${first.name}.${ext}`).includes(second.name)) { - return 1; - } else if (extensions.map((ext) => `${second.name}.${ext}`).includes(first.name)) { - return -1; - } - - return first.name.localeCompare(second.name); -}; - -export const setupGetFieldSuggestions: KqlQuerySuggestionProvider = ( - core -) => { - return async ( - { indexPatterns, suggestionsAbstraction }, - { start, end, prefix, suffix, nestedPath = '' } - ) => { - const allFields = flatten( - indexPatterns.map((indexPattern) => { - return indexPattern.fields.filter(isFilterable); - }) - // temp until IIndexPattern => DataView - ) as DataViewField[]; - const search = `${prefix}${suffix}`.trim().toLowerCase(); - const matchingFields = allFields.filter((field) => { - const subTypeNested = getFieldSubtypeNested(field); - if (suggestionsAbstraction?.fields?.[field.name]) { - return ( - (!nestedPath || (nestedPath && subTypeNested?.nested.path.includes(nestedPath))) && - (suggestionsAbstraction?.fields[field.name]?.displayField ?? '') - .toLowerCase() - .includes(search) - ); - } else { - return ( - (!nestedPath || (nestedPath && subTypeNested?.nested.path.includes(nestedPath))) && - field.name.toLowerCase().includes(search) - ); - } - }); - const sortedFields = sortPrefixFirst(matchingFields.sort(keywordComparator), search, 'name'); - const suggestions: QuerySuggestionField[] = sortedFields.map((field) => { - const isNested = field.subType && field.subType.nested; - const isSuggestionsAbstractionOn = !!suggestionsAbstraction?.fields?.[field.name]; - - const remainingPath = - field.subType && field.subType.nested - ? isSuggestionsAbstractionOn - ? (suggestionsAbstraction?.fields[field.name].displayField ?? '').slice( - nestedPath ? nestedPath.length + 1 : 0 - ) - : field.subType.nested.path.slice(nestedPath ? nestedPath.length + 1 : 0) - : ''; - let text = - isNested && remainingPath.length > 0 - ? `${escapeKuery(remainingPath)}:{ ${escapeKuery( - field.name.slice(field.subType.nested.path.length + 1) - )} }` - : `${escapeKuery(field.name.slice(nestedPath ? nestedPath.length + 1 : 0))} `; - - if (isSuggestionsAbstractionOn) { - if (isNested && remainingPath.length > 0) { - text = `${escapeKuery(remainingPath)}:{ ${escapeKuery( - suggestionsAbstraction?.fields[field.name]?.nestedDisplayField ?? '' - )} }`; - } else if (isNested && remainingPath.length === 0) { - text = suggestionsAbstraction?.fields[field.name]?.nestedDisplayField ?? ''; - } else { - text = suggestionsAbstraction?.fields[field.name].displayField ?? ''; - } - } - - const cursorIndex = isNested && remainingPath.length > 0 ? text.length - 2 : text.length; - - return { - type: QuerySuggestionTypes.Field, - text, - start, - end, - cursorIndex, - field, - }; - }); - - return Promise.resolve(suggestions); - }; -}; diff --git a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/index.ts b/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/index.ts deleted file mode 100644 index 2c964543ec5a0..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/index.ts +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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 type { CoreSetup } from '@kbn/core/public'; -import type { $Keys } from 'utility-types'; -import { flatten, uniqBy } from 'lodash'; -import type { UnifiedSearchPublicPluginStart } from '../../../types'; - -import type { - QuerySuggestion, - QuerySuggestionGetFnArgs, - QuerySuggestionGetFn, -} from '../query_suggestion_provider'; - -const cursorSymbol = '@kuery-cursor@'; - -const dedup = (suggestions: QuerySuggestion[]): QuerySuggestion[] => - uniqBy(suggestions, ({ type, text, start, end }) => [type, text, start, end].join('|')); - -export const KUERY_LANGUAGE_NAME = 'kuery'; - -export const setupKqlQuerySuggestionProvider = ( - core: CoreSetup -): QuerySuggestionGetFn => { - let getSuggestionsByType: - | (( - cursoredQuery: string, - querySuggestionsArgs: QuerySuggestionGetFnArgs - ) => Promise> | []>) - | undefined; - - const asyncGetSuggestionsByTypeFn = async () => { - if (getSuggestionsByType) { - return getSuggestionsByType; - } - - const { - fromKueryExpression, - setupGetFieldSuggestions, - setupGetValueSuggestions, - setupGetOperatorSuggestions, - setupGetConjunctionSuggestions, - } = await import('./kql_module'); - - const providers = { - field: setupGetFieldSuggestions(core), - value: setupGetValueSuggestions(core), - operator: setupGetOperatorSuggestions(core), - conjunction: setupGetConjunctionSuggestions(core), - }; - - return (getSuggestionsByType = async ( - cursoredQuery: string, - querySuggestionsArgs: QuerySuggestionGetFnArgs - ): Promise> | []> => { - try { - const { suggestionsAbstraction } = querySuggestionsArgs; - let cursorNode = fromKueryExpression(cursoredQuery, { - cursorSymbol, - parseCursor: true, - }); - const isNested = cursorNode.nestedPath && cursorNode.nestedPath.length > 0; - const fieldName = isNested - ? `${cursorNode.nestedPath}.${cursorNode.fieldName}` - : cursorNode.fieldName; - if (suggestionsAbstraction && suggestionsAbstraction?.fields[fieldName]) { - if (isNested) { - cursorNode = { - ...cursorNode, - fieldName: suggestionsAbstraction?.fields[fieldName]?.nestedField ?? fieldName, - nestedPath: suggestionsAbstraction?.fields[fieldName]?.nestedPath ?? fieldName, - }; - } else { - cursorNode = { - ...cursorNode, - fieldName: suggestionsAbstraction?.fields[fieldName]?.field ?? fieldName, - }; - } - } - return cursorNode.suggestionTypes.map((type: $Keys) => - providers[type](querySuggestionsArgs, cursorNode) - ); - } catch (e) { - return []; - } - }); - }; - - return async (querySuggestionsArgs): Promise => { - const { query, selectionStart, selectionEnd } = querySuggestionsArgs; - const cursoredQuery = `${query.substr(0, selectionStart)}${cursorSymbol}${query.substr( - selectionEnd - )}`; - const fn = await asyncGetSuggestionsByTypeFn(); - return Promise.all(await fn(cursoredQuery, querySuggestionsArgs)).then((suggestionsByType) => - dedup(flatten(suggestionsByType)) - ); - }; -}; diff --git a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/kql_module.ts b/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/kql_module.ts deleted file mode 100644 index 924af99469aa6..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/kql_module.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * 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". - */ - -export { fromKueryExpression } from '@kbn/es-query'; -export { setupGetFieldSuggestions } from './field'; -export { setupGetValueSuggestions } from './value'; -export { setupGetOperatorSuggestions } from './operator'; -export { setupGetConjunctionSuggestions } from './conjunction'; diff --git a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/operator.test.ts b/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/operator.test.ts deleted file mode 100644 index 18a2aee2ad754..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/operator.test.ts +++ /dev/null @@ -1,130 +0,0 @@ -/* - * 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 indexPatternResponse from './__fixtures__/index_pattern_response.json'; - -import { setupGetOperatorSuggestions } from './operator'; -import type { KueryNode } from '@kbn/es-query'; -import type { QuerySuggestionGetFnArgs } from '../query_suggestion_provider'; -import { coreMock } from '@kbn/core/public/mocks'; - -const mockKueryNode = (kueryNode: Partial) => kueryNode as unknown as KueryNode; - -describe('Kuery operator suggestions', () => { - let getSuggestions: ReturnType; - let querySuggestionsArgs: QuerySuggestionGetFnArgs; - - beforeEach(() => { - querySuggestionsArgs = { - indexPatterns: [indexPatternResponse], - } as unknown as QuerySuggestionGetFnArgs; - - getSuggestions = setupGetOperatorSuggestions(coreMock.createSetup()); - }); - - test('should return a function', () => { - expect(typeof getSuggestions).toBe('function'); - }); - - test('should not return suggestions for non-fields', async () => { - const suggestions = await getSuggestions( - querySuggestionsArgs, - mockKueryNode({ fieldName: 'foo' }) - ); - - expect(suggestions).toEqual([]); - }); - - test('should return exists for every field', async () => { - const suggestions = await getSuggestions( - querySuggestionsArgs, - mockKueryNode({ - fieldName: 'custom_user_field', - }) - ); - - expect(suggestions.length).toEqual(1); - expect(suggestions[0].text).toBe(': * '); - }); - - test('should return equals for string fields', async () => { - const suggestions = await getSuggestions( - querySuggestionsArgs, - mockKueryNode({ fieldName: 'machine.os' }) - ); - - expect(suggestions.find(({ text }) => text === ': ')).toBeDefined(); - expect(suggestions.find(({ text }) => text === '< ')).not.toBeDefined(); - }); - - test('should return numeric operators for numeric fields', async () => { - const suggestions = await getSuggestions( - querySuggestionsArgs, - mockKueryNode({ fieldName: 'bytes' }) - ); - - expect(suggestions.find(({ text }) => text === ': ')).toBeDefined(); - expect(suggestions.find(({ text }) => text === '< ')).toBeDefined(); - }); - - test('should return numeric operators for numeric range fields', async () => { - const suggestions = await getSuggestions( - querySuggestionsArgs, - mockKueryNode({ fieldName: 'bytes_range' }) - ); - - expect(suggestions.find(({ text }) => text === ': ')).toBeDefined(); - expect(suggestions.find(({ text }) => text === '< ')).toBeDefined(); - }); - - test('should return range operators for date range fields', async () => { - const suggestions = await getSuggestions( - querySuggestionsArgs, - mockKueryNode({ fieldName: 'time_range' }) - ); - - expect(suggestions.find(({ text }) => text === ': ')).toBeDefined(); - expect(suggestions.find(({ text }) => text === '< ')).toBeDefined(); - }); - - test('should return range operators for ip range fields', async () => { - const suggestions = await getSuggestions( - querySuggestionsArgs, - mockKueryNode({ fieldName: 'ip_range' }) - ); - - expect(suggestions.find(({ text }) => text === ': ')).toBeDefined(); - expect(suggestions.find(({ text }) => text === '< ')).toBeDefined(); - }); - - test('should have descriptions', async () => { - const suggestions = await getSuggestions( - querySuggestionsArgs, - mockKueryNode({ fieldName: 'bytes' }) - ); - - expect(suggestions.length).toBeGreaterThan(0); - - suggestions.forEach((suggestion) => { - expect(suggestion).toHaveProperty('description'); - }); - }); - - test('should handle nested paths', async () => { - const suggestions = await getSuggestions( - querySuggestionsArgs, - mockKueryNode({ - fieldName: 'child', - nestedPath: 'nestedField', - }) - ); - - expect(suggestions.length).toBeGreaterThan(0); - }); -}); diff --git a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/operator.tsx b/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/operator.tsx deleted file mode 100644 index 3ef36221235f5..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/operator.tsx +++ /dev/null @@ -1,191 +0,0 @@ -/* - * 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 React from 'react'; -import { FormattedMessage } from '@kbn/i18n-react'; -import type { $Keys } from 'utility-types'; -import { flatten } from 'lodash'; - -import type { KqlQuerySuggestionProvider } from './types'; -import { QuerySuggestionTypes } from '../query_suggestion_provider'; - -const equalsText = ( - -); -const lessThanOrEqualToText = ( - -); -const greaterThanOrEqualToText = ( - -); -const lessThanText = ( - -); -const greaterThanText = ( - -); -const existsText = ( - -); - -const operators = { - ':': { - description: ( - {equalsText} }} - description="Full text: 'equals some value'. See - 'unifiedSearch.kueryAutocomplete.equalOperatorDescription.equalsText' for 'equals' part." - /> - ), - fieldTypes: [ - 'string', - 'number', - 'number_range', - 'date', - 'date_range', - 'ip', - 'ip_range', - 'geo_point', - 'geo_shape', - 'boolean', - ], - }, - '<=': { - description: ( - {lessThanOrEqualToText} - ), - }} - description="Full text: 'is less than or equal to some value'. See - 'unifiedSearch.kueryAutocomplete.lessThanOrEqualOperatorDescription.lessThanOrEqualToText' for 'less than or equal to' part." - /> - ), - fieldTypes: ['number', 'number_range', 'date', 'date_range', 'ip', 'ip_range'], - }, - '>=': { - description: ( - {greaterThanOrEqualToText} - ), - }} - description="Full text: 'is greater than or equal to some value'. See - 'unifiedSearch.kueryAutocomplete.greaterThanOrEqualOperatorDescription.greaterThanOrEqualToText' for 'greater than or equal to' part." - /> - ), - fieldTypes: ['number', 'number_range', 'date', 'date_range', 'ip', 'ip_range'], - }, - '<': { - description: ( - {lessThanText} }} - description="Full text: 'is less than some value'. See - 'unifiedSearch.kueryAutocomplete.lessThanOperatorDescription.lessThanText' for 'less than' part." - /> - ), - fieldTypes: ['number', 'number_range', 'date', 'date_range', 'ip', 'ip_range'], - }, - '>': { - description: ( - {greaterThanText}, - }} - description="Full text: 'is greater than some value'. See - 'unifiedSearch.kueryAutocomplete.greaterThanOperatorDescription.greaterThanText' for 'greater than' part." - /> - ), - fieldTypes: ['number', 'number_range', 'date', 'date_range', 'ip', 'ip_range'], - }, - ': *': { - description: ( - {existsText} }} - description="Full text: 'exists in any form'. See - 'unifiedSearch.kueryAutocomplete.existOperatorDescription.existsText' for 'exists' part." - /> - ), - fieldTypes: undefined, - }, -}; - -type Operators = $Keys; - -const getOperatorByName = (operator: string) => operators[operator as Operators]; -const getDescription = (operator: string) =>

{getOperatorByName(operator).description}

; - -export const setupGetOperatorSuggestions: KqlQuerySuggestionProvider = () => { - return ({ indexPatterns }, { end, fieldName, nestedPath }) => { - const allFields = flatten( - indexPatterns.map((indexPattern) => { - return indexPattern.fields.slice(); - }) - ); - const fullFieldName = nestedPath ? `${nestedPath}.${fieldName}` : fieldName; - const fields = allFields - .filter((field) => field.name === fullFieldName) - .map((field) => { - const matchingOperators = Object.keys(operators).filter((operator) => { - const { fieldTypes } = getOperatorByName(operator); - - return !fieldTypes || fieldTypes.includes(field.type); - }); - - const suggestions = matchingOperators.map((operator) => ({ - type: QuerySuggestionTypes.Operator, - text: operator + ' ', - description: getDescription(operator), - start: end, - end, - })); - return suggestions; - }); - - return Promise.resolve(flatten(fields)); - }; -}; diff --git a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.test.ts b/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.test.ts deleted file mode 100644 index 4f32823743940..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.test.ts +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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 { sortPrefixFirst } from './sort_prefix_first'; - -describe('sortPrefixFirst', () => { - test('should return the original unmodified array if no prefix is provided', () => { - const array = ['foo', 'bar', 'baz']; - const result = sortPrefixFirst(array); - - expect(result).toBe(array); - expect(result).toEqual(['foo', 'bar', 'baz']); - }); - - test('should sort items that match the prefix first without modifying the original array', () => { - const array = ['foo', 'bar', 'baz']; - const result = sortPrefixFirst(array, 'b'); - - expect(result).not.toBe(array); - expect(result).toEqual(['bar', 'baz', 'foo']); - expect(array).toEqual(['foo', 'bar', 'baz']); - }); - - test('should not modify the order of the array other than matching prefix without modifying the original array', () => { - const array = ['foo', 'bar', 'baz', 'qux', 'quux']; - const result = sortPrefixFirst(array, 'b'); - - expect(result).not.toBe(array); - expect(result).toEqual(['bar', 'baz', 'foo', 'qux', 'quux']); - expect(array).toEqual(['foo', 'bar', 'baz', 'qux', 'quux']); - }); - - test('should sort objects by property if provided', () => { - const array = [ - { name: 'foo' }, - { name: 'bar' }, - { name: 'baz' }, - { name: 'qux' }, - { name: 'quux' }, - ]; - const result = sortPrefixFirst(array, 'b', 'name'); - - expect(result).not.toBe(array); - expect(result).toEqual([ - { name: 'bar' }, - { name: 'baz' }, - { name: 'foo' }, - { name: 'qux' }, - { name: 'quux' }, - ]); - expect(array).toEqual([ - { name: 'foo' }, - { name: 'bar' }, - { name: 'baz' }, - { name: 'qux' }, - { name: 'quux' }, - ]); - }); - - test('should handle numbers', () => { - const array = [1, 50, 5]; - const result = sortPrefixFirst(array, 5); - - expect(result).not.toBe(array); - expect(result).toEqual([50, 5, 1]); - }); - - test('should handle mixed case', () => { - const array = ['Date Histogram', 'Histogram']; - const prefix = 'histo'; - const result = sortPrefixFirst(array, prefix); - - expect(result).not.toBe(array); - expect(result).toEqual(['Histogram', 'Date Histogram']); - }); -}); diff --git a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.ts b/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.ts deleted file mode 100644 index f5c29103f49a6..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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 { partition } from 'lodash'; - -export function sortPrefixFirst(array: any[], prefix?: string | number, property?: string): any[] { - if (!prefix) { - return array; - } - const lowerCasePrefix = ('' + prefix).toLowerCase(); - - const partitions = partition(array, (entry) => { - const value = ('' + (property ? entry[property] : entry)).toLowerCase(); - - return value.startsWith(lowerCasePrefix); - }); - - return [...partitions[0], ...partitions[1]]; -} diff --git a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/types.ts b/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/types.ts deleted file mode 100644 index 9423af3bb4de1..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/types.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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 type { KueryNode } from '@kbn/es-query'; -import type { CoreSetup } from '@kbn/core/public'; -import type { UnifiedSearchPublicPluginStart } from '../../../types'; -import type { QuerySuggestionBasic, QuerySuggestionGetFnArgs } from '../query_suggestion_provider'; - -export type KqlQuerySuggestionProvider = ( - core: CoreSetup -) => (querySuggestionsGetFnArgs: QuerySuggestionGetFnArgs, kueryNode: KueryNode) => Promise; diff --git a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/value.test.ts b/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/value.test.ts deleted file mode 100644 index 3bbbb4d7e4290..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/value.test.ts +++ /dev/null @@ -1,200 +0,0 @@ -/* - * 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 { setupGetValueSuggestions } from './value'; -import indexPatternResponse from './__fixtures__/index_pattern_response.json'; - -import { coreMock } from '@kbn/core/public/mocks'; -import type { KueryNode } from '@kbn/es-query'; -import type { QuerySuggestionGetFnArgs } from '../query_suggestion_provider'; - -const mockKueryNode = (kueryNode: Partial) => kueryNode as unknown as KueryNode; - -describe('Kuery value suggestions', () => { - let getSuggestions: ReturnType; - let querySuggestionsArgs: QuerySuggestionGetFnArgs; - let autocompleteServiceMock: any; - - beforeEach(() => { - autocompleteServiceMock = { - getValueSuggestions: jest.fn(({ field }) => { - let res: any[]; - - if (field.type === 'boolean') { - res = [true, false]; - } else if (field.name === 'machine.os') { - res = ['Windo"ws', "Mac'", 'Linux']; - } else if (field.name === 'nestedField.child') { - res = ['foo']; - } else { - res = []; - } - return Promise.resolve(res); - }), - }; - - const coreSetup = coreMock.createSetup({ - pluginStartContract: { - autocomplete: autocompleteServiceMock, - }, - }); - getSuggestions = setupGetValueSuggestions(coreSetup); - querySuggestionsArgs = { - indexPatterns: [indexPatternResponse], - } as unknown as QuerySuggestionGetFnArgs; - - jest.clearAllMocks(); - }); - - test('should return a function', () => { - expect(typeof getSuggestions).toBe('function'); - }); - - test('should not search for non existing field', async () => { - const fieldName = 'i_dont_exist'; - const prefix = ''; - const suffix = ''; - - const suggestions = await getSuggestions( - querySuggestionsArgs, - mockKueryNode({ fieldName, prefix, suffix }) - ); - - expect(suggestions.map(({ text }) => text)).toEqual([]); - expect(autocompleteServiceMock.getValueSuggestions).toHaveBeenCalledTimes(0); - }); - - test('should format suggestions', async () => { - const start = 1; - const end = 5; - const suggestions = await getSuggestions( - querySuggestionsArgs, - mockKueryNode({ - fieldName: 'ssl', - prefix: '', - suffix: '', - start, - end, - }) - ); - - expect(suggestions[0].type).toEqual('value'); - expect(suggestions[0].start).toEqual(start); - expect(suggestions[0].end).toEqual(end); - }); - - test('should handle nested paths', async () => { - const suggestions = await getSuggestions( - querySuggestionsArgs, - mockKueryNode({ - fieldName: 'child', - nestedPath: 'nestedField', - prefix: '', - suffix: '', - }) - ); - - expect(suggestions.length).toEqual(1); - expect(suggestions[0].text).toEqual('"foo" '); - }); - - describe('Boolean suggestions', () => { - test('should stringify boolean fields', async () => { - const suggestions = await getSuggestions( - querySuggestionsArgs, - mockKueryNode({ - fieldName: 'ssl', - prefix: '', - suffix: '', - }) - ); - - expect(suggestions.map(({ text }) => text)).toEqual(['true ', 'false ']); - expect(autocompleteServiceMock.getValueSuggestions).toHaveBeenCalledTimes(1); - }); - - test('should filter out boolean suggestions', async () => { - const suggestions = await getSuggestions( - querySuggestionsArgs, - mockKueryNode({ - fieldName: 'ssl', - prefix: 'fa', - suffix: '', - }) - ); - - expect(suggestions.length).toEqual(1); - }); - }); - - describe('String suggestions', () => { - test('should merge prefix and suffix', async () => { - const prefix = 'he'; - const suffix = 'llo'; - - await getSuggestions( - querySuggestionsArgs, - mockKueryNode({ - fieldName: 'machine.os.raw', - prefix, - suffix, - }) - ); - - expect(autocompleteServiceMock.getValueSuggestions).toHaveBeenCalledTimes(1); - expect(autocompleteServiceMock.getValueSuggestions).toBeCalledWith( - expect.objectContaining({ - field: expect.any(Object), - query: prefix + suffix, - }) - ); - }); - - test('should escape quotes in suggestions', async () => { - const suggestions = await getSuggestions( - querySuggestionsArgs, - mockKueryNode({ - fieldName: 'machine.os', - prefix: '', - suffix: '', - }) - ); - - expect(suggestions[0].text).toEqual('"Windo\\"ws" '); - expect(suggestions[1].text).toEqual('"Mac\'" '); - expect(suggestions[2].text).toEqual('"Linux" '); - }); - - test('should filter out string suggestions', async () => { - const suggestions = await getSuggestions( - querySuggestionsArgs, - mockKueryNode({ - fieldName: 'machine.os', - prefix: 'banana', - suffix: '', - }) - ); - - expect(suggestions.length).toEqual(0); - }); - - test('should partially filter out string suggestions - case insensitive', async () => { - const suggestions = await getSuggestions( - querySuggestionsArgs, - mockKueryNode({ - fieldName: 'machine.os', - prefix: 'ma', - suffix: '', - }) - ); - - expect(suggestions.length).toEqual(1); - }); - }); -}); diff --git a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/value.ts b/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/value.ts deleted file mode 100644 index d2c0cc4e79eeb..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/kql_query_suggestion/value.ts +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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 { flatten } from 'lodash'; -import type { CoreSetup } from '@kbn/core/public'; -import type { DataView, DataViewField } from '@kbn/data-views-plugin/common'; -import { escapeQuotes } from '@kbn/es-query'; -import type { KqlQuerySuggestionProvider } from './types'; -import type { UnifiedSearchPublicPluginStart } from '../../../types'; -import type { QuerySuggestion } from '../query_suggestion_provider'; -import { QuerySuggestionTypes } from '../query_suggestion_provider'; - -const wrapAsSuggestions = (start: number, end: number, query: string, values: string[]) => - values - .filter((value) => value.toLowerCase().includes(query.toLowerCase())) - .map((value) => ({ - type: QuerySuggestionTypes.Value, - text: `${value} `, - start, - end, - })); - -export const setupGetValueSuggestions: KqlQuerySuggestionProvider = ( - core: CoreSetup -) => { - const autoCompleteServicePromise = core - .getStartServices() - .then(([_, __, dataStart]) => dataStart.autocomplete); - return async ( - { indexPatterns, boolFilter, useTimeRange, signal, method, suggestionsAbstraction }, - { start, end, prefix, suffix, fieldName, nestedPath } - ): Promise => { - const fullFieldName = nestedPath ? `${nestedPath}.${fieldName}` : fieldName; - - const indexPatternFieldEntries: Array<[DataView, DataViewField]> = []; - indexPatterns.forEach((indexPattern) => { - indexPattern.fields - .filter((field) => field.name === fullFieldName) - .forEach((field) => indexPatternFieldEntries.push([indexPattern, field])); - }); - - const query = `${prefix}${suffix}`.trim(); - const { getValueSuggestions } = await autoCompleteServicePromise; - - const data = await Promise.all( - indexPatternFieldEntries.map(([indexPattern, field]) => - getValueSuggestions({ - indexPattern, - field, - query, - boolFilter, - useTimeRange, - signal, - method, - querySuggestionKey: suggestionsAbstraction?.type, - }).then((valueSuggestions) => { - const quotedValues = valueSuggestions.map((value) => - typeof value === 'string' ? `"${escapeQuotes(value)}"` : `${value}` - ); - - return wrapAsSuggestions(start, end, query, quotedValues); - }) - ) - ); - - return flatten(data); - }; -}; diff --git a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/query_suggestion_provider.ts b/src/platform/plugins/shared/unified_search/public/autocomplete/providers/query_suggestion_provider.ts deleted file mode 100644 index 9131d118f19d0..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/query_suggestion_provider.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 type { ValueSuggestionsMethod } from '@kbn/data-plugin/common'; -// for replace IIndexPattern => DataView need to fix the issue https://github.com/elastic/kibana/issues/131292 -import type { DataViewField, DataView } from '@kbn/data-views-plugin/common'; -import type { SuggestionsAbstraction } from '../../typeahead/suggestions_component'; - -export enum QuerySuggestionTypes { - Field = 'field', - Value = 'value', - Operator = 'operator', - Conjunction = 'conjunction', - RecentSearch = 'recentSearch', -} - -export type QuerySuggestionGetFn = ( - args: QuerySuggestionGetFnArgs -) => Promise | undefined; - -/** @public **/ -export interface QuerySuggestionGetFnArgs { - language: string; - indexPatterns: DataView[]; - query: string; - selectionStart: number; - selectionEnd: number; - signal?: AbortSignal; - useTimeRange?: boolean; - boolFilter?: any; - method?: ValueSuggestionsMethod; - suggestionsAbstraction?: SuggestionsAbstraction; -} - -/** @public **/ -export interface QuerySuggestionBasic { - type: QuerySuggestionTypes; - description?: string | JSX.Element; - end: number; - start: number; - text: string; - cursorIndex?: number; -} - -/** @public **/ -export interface QuerySuggestionField extends QuerySuggestionBasic { - type: QuerySuggestionTypes.Field; - field: DataViewField; -} - -/** @public **/ -export type QuerySuggestion = QuerySuggestionBasic | QuerySuggestionField; diff --git a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/value_suggestion_provider.test.ts b/src/platform/plugins/shared/unified_search/public/autocomplete/providers/value_suggestion_provider.test.ts deleted file mode 100644 index 5983e12080a9f..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/value_suggestion_provider.test.ts +++ /dev/null @@ -1,334 +0,0 @@ -/* - * 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 type { IUiSettingsClient, CoreSetup } from '@kbn/core/public'; -import { stubIndexPattern, stubFields } from '@kbn/data-plugin/public/stubs'; -import type { TimefilterSetup } from '@kbn/data-plugin/public'; -import { UI_SETTINGS } from '@kbn/data-plugin/common'; -import { setupValueSuggestionProvider } from './value_suggestion_provider'; -import type { ValueSuggestionsGetFn } from './value_suggestion_provider'; -import type { DataView } from '@kbn/data-views-plugin/public'; - -describe('FieldSuggestions', () => { - let getValueSuggestions: ValueSuggestionsGetFn; - let http: any; - let uiConfig: Record = {}; - const uiSettings = { - get: (key: string) => uiConfig[key], - } as IUiSettingsClient; - let getTimeMock: jest.Mock; - let createFilterMock: jest.Mock; - - beforeEach(() => { - getTimeMock = jest.fn().mockReturnValue({ to: 'now', from: 'now-15m' }); - createFilterMock = jest.fn().mockReturnValue({ time: 'fake' }); - http = { fetch: jest.fn().mockResolvedValue([]) }; - - getValueSuggestions = setupValueSuggestionProvider({ http, uiSettings } as CoreSetup, { - timefilter: { - timefilter: { - createFilter: createFilterMock, - getTime: getTimeMock, - }, - } as unknown as TimefilterSetup, - }); - }); - - describe('with value suggestions disabled', () => { - uiConfig = { [UI_SETTINGS.FILTERS_EDITOR_SUGGEST_VALUES]: false }; - - it('should return an empty array', async () => { - const suggestions = await getValueSuggestions({ - indexPattern: stubIndexPattern, - field: stubFields[0], - query: '', - }); - - expect(suggestions).toEqual([]); - expect(http.fetch).not.toHaveBeenCalled(); - }); - }); - - describe('with value suggestions enabled', () => { - uiConfig = { [UI_SETTINGS.FILTERS_EDITOR_SUGGEST_VALUES]: true }; - - it('should return true/false for boolean fields', async () => { - const [field] = stubFields.filter(({ type }) => type === 'boolean'); - const suggestions = await getValueSuggestions({ - indexPattern: stubIndexPattern, - field, - query: '', - }); - - expect(suggestions).toEqual([true, false]); - expect(http.fetch).not.toHaveBeenCalled(); - }); - - it('should return an empty array if the field type is not a string, boolean, or IP', async () => { - const fields = stubFields.filter( - ({ type }) => type !== 'string' && type !== 'boolean' && type !== 'ip' - ); - await Promise.all( - fields.map(async (field) => { - const suggestions = await getValueSuggestions({ - indexPattern: stubIndexPattern, - field, - query: '', - }); - expect(suggestions).toEqual([]); - }) - ); - expect(http.fetch).not.toHaveBeenCalled(); - }); - - it('should return an empty array if the field is not aggregatable', async () => { - const [field] = stubFields.filter(({ aggregatable }) => !aggregatable); - const suggestions = await getValueSuggestions({ - indexPattern: stubIndexPattern, - field, - query: '', - }); - - expect(suggestions).toEqual([]); - expect(http.fetch).not.toHaveBeenCalled(); - }); - - it('should request suggestions for strings', async () => { - const [field] = stubFields.filter( - ({ type, aggregatable }) => type === 'string' && aggregatable - ); - - await getValueSuggestions({ - indexPattern: stubIndexPattern, - field, - query: '', - useTimeRange: false, - }); - - expect(http.fetch).toHaveBeenCalled(); - }); - - it('should request suggestions for ips', async () => { - const [field] = stubFields.filter(({ type, aggregatable }) => type === 'ip' && aggregatable); - - await getValueSuggestions({ - indexPattern: stubIndexPattern, - field, - query: '', - useTimeRange: false, - }); - - expect(http.fetch).toHaveBeenCalled(); - }); - - it('should cache results if using the same index/field/query/filter', async () => { - const [field] = stubFields.filter( - ({ type, aggregatable }) => type === 'string' && aggregatable - ); - const args = { - indexPattern: stubIndexPattern, - field, - query: '', - useTimeRange: false, - }; - - await getValueSuggestions(args); - await getValueSuggestions(args); - - expect(http.fetch).toHaveBeenCalledTimes(1); - }); - - it('should cache results for only one minute', async () => { - const [field] = stubFields.filter( - ({ type, aggregatable }) => type === 'string' && aggregatable - ); - const args = { - indexPattern: stubIndexPattern, - field, - query: '', - useTimeRange: false, - }; - - const { now } = Date; - Date.now = jest.fn(() => 0); - - await getValueSuggestions(args); - - Date.now = jest.fn(() => 60 * 1000); - await getValueSuggestions(args); - Date.now = now; - - expect(http.fetch).toHaveBeenCalledTimes(2); - }); - - it('should not cache results if using a different index/field/query', async () => { - const fields = stubFields.filter( - ({ type, aggregatable }) => type === 'string' && aggregatable - ); - - await getValueSuggestions({ - indexPattern: stubIndexPattern, - field: fields[0], - query: '', - useTimeRange: false, - }); - await getValueSuggestions({ - indexPattern: stubIndexPattern, - field: fields[0], - query: 'query', - useTimeRange: false, - }); - await getValueSuggestions({ - indexPattern: stubIndexPattern, - field: fields[1], - query: '', - useTimeRange: false, - }); - await getValueSuggestions({ - indexPattern: stubIndexPattern, - field: fields[1], - query: 'query', - useTimeRange: false, - }); - - const customIndexPattern = { - ...stubIndexPattern, - title: 'customIndexPattern', - useTimeRange: false, - } as unknown as DataView; - - await getValueSuggestions({ - indexPattern: customIndexPattern, - field: fields[0], - query: '', - useTimeRange: false, - }); - await getValueSuggestions({ - indexPattern: customIndexPattern as unknown as DataView, - field: fields[0], - query: 'query', - useTimeRange: false, - }); - await getValueSuggestions({ - indexPattern: customIndexPattern, - field: fields[1], - query: '', - useTimeRange: false, - }); - await getValueSuggestions({ - indexPattern: customIndexPattern, - field: fields[1], - query: 'query', - useTimeRange: false, - }); - - expect(http.fetch).toHaveBeenCalledTimes(8); - }); - - it('should apply timefilter', async () => { - const [field] = stubFields.filter( - ({ type, aggregatable }) => type === 'string' && aggregatable - ); - - await getValueSuggestions({ - indexPattern: stubIndexPattern, - field, - query: '', - useTimeRange: true, - }); - const callParams = http.fetch.mock.calls[0][1]; - - expect(JSON.parse(callParams.body).filters).toHaveLength(1); - expect(http.fetch).toHaveBeenCalled(); - }); - - it('should round timefilter `to` value', async () => { - getTimeMock.mockReturnValue({ from: '2022-10-27||/d', to: '2022-10-27||/d' }); - - const [field] = stubFields.filter( - ({ type, aggregatable }) => type === 'string' && aggregatable - ); - - await getValueSuggestions({ - indexPattern: stubIndexPattern, - field, - query: '', - useTimeRange: true, - }); - - expect(createFilterMock.mock.calls[0][1]).toMatchInlineSnapshot(` - Object { - "from": "2022-10-27T04:00:00.000Z", - "to": "2022-10-28T03:59:59.999Z", - } - `); - }); - - it('should use terms_enum', async () => { - uiConfig = { - [UI_SETTINGS.FILTERS_EDITOR_SUGGEST_VALUES]: true, - [UI_SETTINGS.AUTOCOMPLETE_VALUE_SUGGESTION_METHOD]: 'terms_enum', - }; - const [field] = stubFields.filter( - ({ type, aggregatable }) => type === 'string' && aggregatable - ); - - await getValueSuggestions({ - indexPattern: stubIndexPattern, - field, - query: '', - useTimeRange: true, - }); - const callParams = http.fetch.mock.calls[0][1]; - - expect(JSON.parse(callParams.body)).toHaveProperty('method', 'terms_enum'); - }); - - it('should use terms_agg', async () => { - uiConfig = { - [UI_SETTINGS.FILTERS_EDITOR_SUGGEST_VALUES]: true, - [UI_SETTINGS.AUTOCOMPLETE_VALUE_SUGGESTION_METHOD]: 'terms_agg', - }; - const [field] = stubFields.filter( - ({ type, aggregatable }) => type === 'string' && aggregatable - ); - - await getValueSuggestions({ - indexPattern: stubIndexPattern, - field, - query: '', - useTimeRange: true, - }); - const callParams = http.fetch.mock.calls[0][1]; - - expect(JSON.parse(callParams.body)).toHaveProperty('method', 'terms_agg'); - }); - - it('should use method passed in', async () => { - uiConfig = { - [UI_SETTINGS.FILTERS_EDITOR_SUGGEST_VALUES]: true, - [UI_SETTINGS.AUTOCOMPLETE_VALUE_SUGGESTION_METHOD]: 'terms_agg', - }; - const [field] = stubFields.filter( - ({ type, aggregatable }) => type === 'string' && aggregatable - ); - - await getValueSuggestions({ - indexPattern: stubIndexPattern, - field, - query: '', - useTimeRange: true, - method: 'terms_agg', - }); - const callParams = http.fetch.mock.calls[0][1]; - - expect(JSON.parse(callParams.body)).toHaveProperty('method', 'terms_agg'); - }); - }); -}); diff --git a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/value_suggestion_provider.ts b/src/platform/plugins/shared/unified_search/public/autocomplete/providers/value_suggestion_provider.ts deleted file mode 100644 index 24d524cbca4c7..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/autocomplete/providers/value_suggestion_provider.ts +++ /dev/null @@ -1,156 +0,0 @@ -/* - * 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 type { CoreSetup } from '@kbn/core/public'; -import dateMath from '@kbn/datemath'; -import { memoize } from 'lodash'; -import type { ValueSuggestionsMethod } from '@kbn/data-plugin/common'; -import { UI_SETTINGS } from '@kbn/data-plugin/common'; -import type { DataView, DataViewField } from '@kbn/data-views-plugin/common'; -import type { TimefilterSetup } from '@kbn/data-plugin/public'; -import { buildQueryFromFilters } from '@kbn/es-query'; -import type { AutocompleteUsageCollector } from '../collectors'; - -export type ValueSuggestionsGetFn = (args: ValueSuggestionsGetFnArgs) => Promise; - -interface ValueSuggestionsGetFnArgs { - indexPattern: DataView; - field: DataViewField; - query: string; - useTimeRange?: boolean; - boolFilter?: any[]; - signal?: AbortSignal; - method?: ValueSuggestionsMethod; - querySuggestionKey?: 'rules' | 'cases' | 'alerts' | 'endpoints'; -} - -const getAutocompleteTimefilter = ({ timefilter }: TimefilterSetup, indexPattern: DataView) => { - const timeRange = timefilter.getTime(); - - // Use a rounded timerange so that memoizing works properly - const roundedTimerange = { - from: dateMath.parse(timeRange.from)!.startOf('minute').toISOString(), - to: dateMath.parse(timeRange.to, { roundUp: true })!.endOf('minute').toISOString(), - }; - return timefilter.createFilter(indexPattern, roundedTimerange); -}; - -export const getEmptyValueSuggestions = (() => Promise.resolve([])) as ValueSuggestionsGetFn; - -export const setupValueSuggestionProvider = ( - core: CoreSetup, - { - timefilter, - usageCollector, - }: { timefilter: TimefilterSetup; usageCollector?: AutocompleteUsageCollector } -): ValueSuggestionsGetFn => { - function resolver(title: string, field: DataViewField, query: string, filters: any[]) { - // Only cache results for a minute - const ttl = Math.floor(Date.now() / 1000 / 60); - return [ttl, query, title, field.name, JSON.stringify(filters)].join('|'); - } - - const requestSuggestions = memoize( - ( - index: string, - field: DataViewField, - query: string, - filters: any = [], - signal?: AbortSignal, - method: ValueSuggestionsMethod = core.uiSettings.get( - UI_SETTINGS.AUTOCOMPLETE_VALUE_SUGGESTION_METHOD - ), - querySuggestionKey?: string - ) => { - usageCollector?.trackRequest(); - let path = `/internal/kibana/suggestions/values/${index}`; - if (querySuggestionKey) { - if (querySuggestionKey === 'endpoints') { - path = '/internal/api/endpoint/suggestions/endpoints'; - } else { - path = `/internal/${querySuggestionKey}/suggestions/values`; - } - } - return core.http - .fetch(path, { - method: 'POST', - body: JSON.stringify({ - query, - field: field.name, - fieldMeta: field.toSpec?.() ?? field, - filters, - ...(querySuggestionKey === undefined ? { method } : {}), - }), - signal, - version: '1', - }) - .then((r) => { - usageCollector?.trackResult(); - return r; - }); - }, - resolver - ); - - return async ({ - indexPattern, - field, - query, - useTimeRange, - boolFilter, - signal, - method, - querySuggestionKey, - }: ValueSuggestionsGetFnArgs): Promise => { - const shouldSuggestValues = core!.uiSettings.get( - UI_SETTINGS.FILTERS_EDITOR_SUGGEST_VALUES - ); - useTimeRange = - useTimeRange ?? core!.uiSettings.get(UI_SETTINGS.AUTOCOMPLETE_USE_TIMERANGE); - const { title } = indexPattern; - - const isVersionFieldType = field.type === 'string' && field.esTypes?.includes('version'); - - if (field.type === 'boolean') { - return [true, false]; - } else if ( - !shouldSuggestValues || - !field.aggregatable || - (field.type !== 'string' && field.type !== 'ip') || - isVersionFieldType // suggestions don't work for version fields - ) { - return []; - } - - const timeFilter = useTimeRange - ? getAutocompleteTimefilter(timefilter, indexPattern) - : undefined; - const filterQuery = timeFilter ? buildQueryFromFilters([timeFilter], indexPattern).filter : []; - const filters = [...(boolFilter ? boolFilter : []), ...filterQuery]; - try { - usageCollector?.trackCall(); - return await requestSuggestions( - title, - field, - query, - filters, - signal, - method, - querySuggestionKey - ); - } catch (e) { - if (!signal?.aborted) { - usageCollector?.trackError(); - } - // Remove rejected results from memoize cache - requestSuggestions.cache.delete(resolver(title, field, query, filters)); - return []; - } - }; -}; diff --git a/src/platform/plugins/shared/unified_search/public/filter_bar/filter_editor/phrase_suggestor.tsx b/src/platform/plugins/shared/unified_search/public/filter_bar/filter_editor/phrase_suggestor.tsx index 9d08f86707f95..0ea129a4e7be9 100644 --- a/src/platform/plugins/shared/unified_search/public/filter_bar/filter_editor/phrase_suggestor.tsx +++ b/src/platform/plugins/shared/unified_search/public/filter_bar/filter_editor/phrase_suggestor.tsx @@ -88,7 +88,7 @@ export class PhraseSuggestorUI extends React.Com return; } this.setState({ isLoading: true }); - const suggestions = await this.services.unifiedSearch.autocomplete.getValueSuggestions({ + const suggestions = await this.services.kql.autocomplete.getValueSuggestions({ indexPattern, field, query, diff --git a/src/platform/plugins/shared/unified_search/public/index.ts b/src/platform/plugins/shared/unified_search/public/index.ts index 4de00c1245f2a..f0868f6f36c38 100755 --- a/src/platform/plugins/shared/unified_search/public/index.ts +++ b/src/platform/plugins/shared/unified_search/public/index.ts @@ -7,17 +7,10 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { CoreStart, PluginInitializerContext } from '@kbn/core/public'; +import type { CoreStart } from '@kbn/core/public'; import type { FilterManager, TimefilterContract } from '@kbn/data-plugin/public'; -import type { ConfigSchema } from '../server/config'; import { UnifiedSearchPublicPlugin } from './plugin'; -export type { - QuerySuggestion, - QuerySuggestionGetFn, - QuerySuggestionGetFnArgs, - AutocompleteStart, -} from './autocomplete'; export type { IndexPatternSelectProps } from './index_pattern_select'; export type { QueryStringInputProps } from './query_string_input'; export type { StatefulSearchBarProps, SearchBarProps } from './search_bar'; @@ -32,7 +25,6 @@ export type { DataViewPickerProps } from './dataview_picker'; export type { ApplyGlobalFilterActionContext } from './actions/apply_filter_action/apply_filter_action'; export { QueryStringInput } from './query_string_input'; -export { QuerySuggestionTypes } from './autocomplete/providers/query_suggestion_provider'; export { SearchBar } from './search_bar'; export { createSearchBar } from './search_bar/create_search_bar'; export { FilterItem, FilterItems } from './filter_bar'; @@ -52,6 +44,6 @@ export async function createFilterAction( return createAction(filterManager, timeFilter, coreStart, id, type); } -export function plugin(initializerContext: PluginInitializerContext) { - return new UnifiedSearchPublicPlugin(initializerContext); +export function plugin() { + return new UnifiedSearchPublicPlugin(); } diff --git a/src/platform/plugins/shared/unified_search/public/mocks/mocks.ts b/src/platform/plugins/shared/unified_search/public/mocks/mocks.ts index 2fde687a327a9..371afa550c45b 100644 --- a/src/platform/plugins/shared/unified_search/public/mocks/mocks.ts +++ b/src/platform/plugins/shared/unified_search/public/mocks/mocks.ts @@ -8,38 +8,22 @@ */ import type { UnifiedSearchPublicPlugin } from '../plugin'; -import type { AutocompleteStart, AutocompleteSetup } from '../autocomplete'; export type Setup = jest.Mocked>; export type Start = jest.Mocked>; -const autocompleteSetupMock: jest.Mocked = { - getQuerySuggestions: jest.fn(), - getAutocompleteSettings: jest.fn(), -}; - -const autocompleteStartMock: jest.Mocked = { - getValueSuggestions: jest.fn(), - getQuerySuggestions: jest.fn(), - hasQuerySuggestions: jest.fn(), -}; - const createSetupContract = (): Setup => { - return { - autocomplete: autocompleteSetupMock, - }; + return {}; }; const createStartContract = (): Start => { return { - autocomplete: autocompleteStartMock, ui: { IndexPatternSelect: jest.fn(), getCustomSearchBar: jest.fn(), SearchBar: jest.fn().mockReturnValue(null), AggregateQuerySearchBar: jest.fn().mockReturnValue(null), FiltersBuilderLazy: jest.fn(), - QueryStringInput: jest.fn().mockReturnValue('QueryStringInput'), }, }; }; diff --git a/src/platform/plugins/shared/unified_search/public/plugin.ts b/src/platform/plugins/shared/unified_search/public/plugin.ts index f62b5f4726541..3e0bbe2a579af 100755 --- a/src/platform/plugins/shared/unified_search/public/plugin.ts +++ b/src/platform/plugins/shared/unified_search/public/plugin.ts @@ -7,16 +7,13 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; +import type { CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; import { APPLY_FILTER_TRIGGER } from '@kbn/data-plugin/public'; -import { createQueryStringInput } from './query_string_input/get_query_string_input'; import { UPDATE_FILTER_REFERENCES_TRIGGER, updateFilterReferencesTrigger } from './triggers'; -import type { ConfigSchema } from '../server/config'; import { setCoreStart, setIndexPatterns } from './services'; -import { AutocompleteService } from './autocomplete/autocomplete_service'; import { createSearchBar } from './search_bar/create_search_bar'; import { createIndexPatternSelect } from './index_pattern_select'; import type { @@ -33,31 +30,21 @@ export class UnifiedSearchPublicPlugin implements Plugin { private readonly storage: IStorageWrapper; - private readonly autocomplete: AutocompleteService; private usageCollection: UsageCollectionSetup | undefined; - constructor(initializerContext: PluginInitializerContext) { + constructor() { this.storage = new Storage(window.localStorage); - - this.autocomplete = new AutocompleteService(initializerContext); } public setup( core: CoreSetup, { uiActions, data, usageCollection }: UnifiedSearchSetupDependencies ): UnifiedSearchPluginSetup { - const { query } = data; - uiActions.registerTrigger(updateFilterReferencesTrigger); this.usageCollection = usageCollection; - return { - autocomplete: this.autocomplete.setup(core, { - timefilter: query.timefilter, - usageCollection, - }), - }; + return {}; } public start( @@ -68,11 +55,11 @@ export class UnifiedSearchPublicPlugin uiActions, screenshotMode, cps: crossProjectSearch, + kql: { autocomplete: autocompleteStart }, }: UnifiedSearchStartDependencies ): UnifiedSearchPublicPluginStart { setCoreStart(core); setIndexPatterns(dataViews); - const autocompleteStart = this.autocomplete.start(); /* * @@ -90,7 +77,7 @@ export class UnifiedSearchPublicPlugin storage: this.storage, usageCollection: this.usageCollection, isScreenshotMode: Boolean(screenshotMode?.isScreenshotMode()), - unifiedSearch: { + kql: { autocomplete: autocompleteStart, }, cps: crossProjectSearch, @@ -119,24 +106,7 @@ export class UnifiedSearchPublicPlugin getCustomSearchBar, AggregateQuerySearchBar: SearchBar, FiltersBuilderLazy, - QueryStringInput: createQueryStringInput({ - data, - dataViews, - docLinks: core.docLinks, - http: core.http, - notifications: core.notifications, - storage: this.storage, - uiSettings: core.uiSettings, - unifiedSearch: { - autocomplete: autocompleteStart, - }, - }), }, - autocomplete: autocompleteStart, }; } - - public stop() { - this.autocomplete.clearProviders(); - } } diff --git a/src/platform/plugins/shared/unified_search/public/query_string_input/index.tsx b/src/platform/plugins/shared/unified_search/public/query_string_input/index.tsx index 15199053856bd..3ac21561ca427 100644 --- a/src/platform/plugins/shared/unified_search/public/query_string_input/index.tsx +++ b/src/platform/plugins/shared/unified_search/public/query_string_input/index.tsx @@ -28,7 +28,7 @@ export const QueryBarTopRow = ( ); const LazyQueryStringInputUI = React.lazy(async () => { - const { QueryStringInput } = await import('../ui_module'); + const { QueryStringInput } = await import('@kbn/kql/public'); return { default: QueryStringInput }; }); diff --git a/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx b/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx index 3cd69e28565b5..25b9d48d4637f 100644 --- a/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx +++ b/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx @@ -50,6 +50,7 @@ import type { ESQLControlVariable } from '@kbn/esql-types'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { SplitButton } from '@kbn/split-button'; import { useMemoCss } from '@kbn/css-utils/public/use_memo_css'; +import { QueryStringInput } from '@kbn/kql/public'; import { AddFilterPopover } from './add_filter_popover'; import type { DataViewPickerProps } from '../dataview_picker'; import { DataViewPicker } from '../dataview_picker'; @@ -62,7 +63,6 @@ import type { import type { IUnifiedSearchPluginServices, UnifiedSearchDraft } from '../types'; import { shallowEqual } from '../utils/shallow_equal'; -import { QueryStringInput } from './query_string_input'; import { ESQLMenuPopover, type ESQLMenuPopoverProps } from './esql_menu_popover'; export const strings = { diff --git a/src/platform/plugins/shared/unified_search/public/search_bar/create_search_bar.tsx b/src/platform/plugins/shared/unified_search/public/search_bar/create_search_bar.tsx index 93c3cc9bfcaa6..b75be41b5467b 100644 --- a/src/platform/plugins/shared/unified_search/public/search_bar/create_search_bar.tsx +++ b/src/platform/plugins/shared/unified_search/public/search_bar/create_search_bar.tsx @@ -13,6 +13,7 @@ import type { CoreStart } from '@kbn/core/public'; import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import type { QueryStart, SavedQuery, DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { KqlPluginStart } from '@kbn/kql/public'; import type { Query, AggregateQuery } from '@kbn/es-query'; import type { Filter, TimeRange } from '@kbn/es-query'; import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; @@ -24,7 +25,6 @@ import { useTimefilter } from './lib/use_timefilter'; import { useSavedQuery } from './lib/use_saved_query'; import { useQueryStringManager } from './lib/use_query_string_manager'; import { canShowSavedQuery } from './lib/can_show_saved_query'; -import type { UnifiedSearchPublicPluginStart } from '../types'; export interface StatefulSearchBarDeps { core: CoreStart; @@ -32,7 +32,7 @@ export interface StatefulSearchBarDeps { storage: IStorageWrapper; usageCollection?: UsageCollectionSetup; isScreenshotMode?: boolean; - unifiedSearch: Omit; + kql: { autocomplete: KqlPluginStart['autocomplete'] }; cps: CPSPluginStart; } @@ -157,7 +157,7 @@ export function createSearchBar({ data, usageCollection, isScreenshotMode = false, - unifiedSearch, + kql, cps, }: StatefulSearchBarDeps) { // App name should come from the core application service. @@ -220,7 +220,7 @@ export function createSearchBar({ data, storage, usageCollection, - unifiedSearch, + kql, cps, ...core, }} diff --git a/src/platform/plugins/shared/unified_search/public/types.ts b/src/platform/plugins/shared/unified_search/public/types.ts index b4944c5cd2eac..46e8a84190857 100755 --- a/src/platform/plugins/shared/unified_search/public/types.ts +++ b/src/platform/plugins/shared/unified_search/public/types.ts @@ -9,6 +9,7 @@ import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; +import type { AutocompleteStart, KqlPluginStart } from '@kbn/kql/public'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import type { ScreenshotModePluginStart } from '@kbn/screenshot-mode-plugin/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; @@ -22,8 +23,7 @@ import type { CoreStart, DocLinksStart } from '@kbn/core/public'; import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import type { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; import type { CPSPluginStart } from '@kbn/cps/public'; -import type { AutocompleteSetup, AutocompleteStart } from './autocomplete'; -import type { IndexPatternSelectProps, QueryStringInputProps, StatefulSearchBarProps } from '.'; +import type { IndexPatternSelectProps, StatefulSearchBarProps } from '.'; import type { FiltersBuilderProps } from './filters_builder/filters_builder'; import type { StatefulSearchBarDeps } from './search_bar/create_search_bar'; @@ -39,9 +39,8 @@ export interface UnifiedSearchSetupDependencies { usageCollection?: UsageCollectionSetup; } -export interface UnifiedSearchPluginSetup { - autocomplete: AutocompleteSetup; -} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface UnifiedSearchPluginSetup {} export interface UnifiedSearchStartDependencies { dataViews: DataViewsPublicPluginStart; @@ -50,6 +49,7 @@ export interface UnifiedSearchStartDependencies { uiActions: UiActionsStart; screenshotMode?: ScreenshotModePluginStart; cps: CPSPluginStart; + kql: KqlPluginStart; } type AggQuerySearchBarComp = ( @@ -65,18 +65,12 @@ export interface UnifiedSearchPublicPluginStartUi { SearchBar: (props: StatefulSearchBarProps) => React.ReactElement; AggregateQuerySearchBar: AggQuerySearchBarComp; FiltersBuilderLazy: React.ComponentType; - QueryStringInput: React.ComponentType>; } /** * Unified search plugin public Start contract */ export interface UnifiedSearchPublicPluginStart { - /** - * autocomplete service - * {@link AutocompleteStart} - */ - autocomplete: AutocompleteStart; /** * prewired UI components * {@link UnifiedSearchPublicPluginStartUi} @@ -95,7 +89,7 @@ export type FilterPanelOption = | 'deleteFilter'; export interface IUnifiedSearchPluginServices extends Partial { - unifiedSearch: { + kql: { autocomplete: AutocompleteStart; }; appName: string; diff --git a/src/platform/plugins/shared/unified_search/public/ui_module.ts b/src/platform/plugins/shared/unified_search/public/ui_module.ts index 2dae611e10c0e..f7fa78166a708 100644 --- a/src/platform/plugins/shared/unified_search/public/ui_module.ts +++ b/src/platform/plugins/shared/unified_search/public/ui_module.ts @@ -19,6 +19,5 @@ export { FilterItem } from './filter_bar/filter_item/filter_item'; export { FilterItems } from './filter_bar/filter_item/filter_items'; export { IndexPatternSelect } from './index_pattern_select/index_pattern_select'; export { QueryBarTopRow } from './query_string_input/query_bar_top_row'; -export { QueryStringInput } from './query_string_input/query_string_input'; export { SuggestionsComponent } from './typeahead/suggestions_component'; export { default as SearchBar } from './search_bar/search_bar'; diff --git a/src/platform/plugins/shared/unified_search/server/autocomplete/autocomplete_service.ts b/src/platform/plugins/shared/unified_search/server/autocomplete/autocomplete_service.ts deleted file mode 100644 index 0f2fe823363f3..0000000000000 --- a/src/platform/plugins/shared/unified_search/server/autocomplete/autocomplete_service.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 moment from 'moment'; -import { clone } from 'lodash'; -import type { CoreSetup, Plugin, PluginInitializerContext } from '@kbn/core/server'; -import { registerRoutes } from './routes'; -import type { ConfigSchema } from '../config'; - -export class AutocompleteService implements Plugin { - private valueSuggestionsEnabled: boolean = true; - private autocompleteSettings: ConfigSchema['autocomplete']['valueSuggestions']; - - constructor(private initializerContext: PluginInitializerContext) { - initializerContext.config.create().subscribe((configUpdate) => { - this.valueSuggestionsEnabled = configUpdate.autocomplete.valueSuggestions.enabled; - this.autocompleteSettings = configUpdate.autocomplete.valueSuggestions; - }); - this.autocompleteSettings = - this.initializerContext.config.get().autocomplete.valueSuggestions; - } - - public setup(core: CoreSetup) { - if (this.valueSuggestionsEnabled) registerRoutes(core, this.initializerContext.config.create()); - const { terminateAfter, timeout } = this.autocompleteSettings; - return { - getAutocompleteSettings: () => ({ - terminateAfter: moment.duration(terminateAfter).asMilliseconds(), - timeout: moment.duration(timeout).asMilliseconds(), - }), - getInitializerContextConfig: () => clone(this.initializerContext.config), - }; - } - - public start() {} -} - -/** @public **/ -export type AutocompleteSetup = ReturnType; diff --git a/src/platform/plugins/shared/unified_search/server/autocomplete/index.ts b/src/platform/plugins/shared/unified_search/server/autocomplete/index.ts deleted file mode 100644 index 0dbb22a4cf1b3..0000000000000 --- a/src/platform/plugins/shared/unified_search/server/autocomplete/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * 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". - */ - -export { AutocompleteService, type AutocompleteSetup } from './autocomplete_service'; diff --git a/src/platform/plugins/shared/unified_search/server/autocomplete/routes.ts b/src/platform/plugins/shared/unified_search/server/autocomplete/routes.ts deleted file mode 100644 index ab8a9af50d39e..0000000000000 --- a/src/platform/plugins/shared/unified_search/server/autocomplete/routes.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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 type { Observable } from 'rxjs'; -import type { CoreSetup } from '@kbn/core/server'; -import { registerValueSuggestionsRoute } from './value_suggestions_route'; -import type { ConfigSchema } from '../config'; - -export function registerRoutes({ http }: CoreSetup, config$: Observable): void { - const router = http.createRouter(); - - registerValueSuggestionsRoute(router, config$); -} diff --git a/src/platform/plugins/shared/unified_search/server/autocomplete/terms_agg.test.ts b/src/platform/plugins/shared/unified_search/server/autocomplete/terms_agg.test.ts deleted file mode 100644 index fb836897d8722..0000000000000 --- a/src/platform/plugins/shared/unified_search/server/autocomplete/terms_agg.test.ts +++ /dev/null @@ -1,155 +0,0 @@ -/* - * 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 { coreMock } from '@kbn/core/server/mocks'; -import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; -import type { ConfigSchema } from '../config'; -import type { DeeplyMockedKeys } from '@kbn/utility-types-jest'; -import type { DataViewField, FieldSpec } from '@kbn/data-views-plugin/common'; -import { termsAggSuggestions } from './terms_agg'; -import type { estypes } from '@elastic/elasticsearch'; -import { duration } from 'moment'; - -let savedObjectsClientMock: jest.Mocked; -let esClientMock: DeeplyMockedKeys; -const configMock = { - autocomplete: { - valueSuggestions: { timeout: duration(4513), terminateAfter: duration(98430) }, - }, -} as unknown as ConfigSchema; - -const dataViewFieldMock = { name: 'field_name', type: 'string' } as DataViewField; - -// @ts-expect-error not full interface -const mockResponse = { - aggregations: { - suggestions: { - buckets: [{ key: 'whoa' }, { key: 'amazing' }], - }, - }, -} as estypes.SearchResponse; - -jest.mock('../data_views'); - -describe('terms agg suggestions', () => { - beforeEach(() => { - const requestHandlerContext = coreMock.createRequestHandlerContext(); - savedObjectsClientMock = requestHandlerContext.savedObjects.client; - esClientMock = requestHandlerContext.elasticsearch.client.asCurrentUser; - esClientMock.search.mockResolvedValue(mockResponse); - }); - - it('calls the _search API with a terms agg with the given args', async () => { - const result = await termsAggSuggestions( - configMock, - savedObjectsClientMock, - esClientMock, - 'index', - 'fieldName', - 'query', - [], - dataViewFieldMock - ); - - const [[args]] = esClientMock.search.mock.calls; - - expect(args).toMatchInlineSnapshot(` - Object { - "aggs": Object { - "suggestions": Object { - "terms": Object { - "execution_hint": "map", - "field": "field_name", - "include": "query.*", - "shard_size": 10, - }, - }, - }, - "index": "index", - "query": Object { - "bool": Object { - "filter": Array [], - }, - }, - "size": 0, - "terminate_after": 98430, - "timeout": "4513ms", - } - `); - expect(result).toMatchInlineSnapshot(` - Array [ - "whoa", - "amazing", - ] - `); - }); - - it('calls the _search API with a terms agg and fallback to fieldName when field is null', async () => { - const result = await termsAggSuggestions( - configMock, - savedObjectsClientMock, - esClientMock, - 'index', - 'fieldName', - 'query', - [] - ); - - const [[args]] = esClientMock.search.mock.calls; - - expect(args).toMatchInlineSnapshot(` - Object { - "aggs": Object { - "suggestions": Object { - "terms": Object { - "execution_hint": "map", - "field": "fieldName", - "include": "query.*", - "shard_size": 10, - }, - }, - }, - "index": "index", - "query": Object { - "bool": Object { - "filter": Array [], - }, - }, - "size": 0, - "terminate_after": 98430, - "timeout": "4513ms", - } - `); - expect(result).toMatchInlineSnapshot(` - Array [ - "whoa", - "amazing", - ] - `); - }); - - it('does not call the _search API when the field is an IP', async () => { - const result = await termsAggSuggestions( - configMock, - savedObjectsClientMock, - esClientMock, - 'index', - 'fieldName', - 'query', - [], - { - type: 'ip', - name: 'fieldName', - } as FieldSpec - ); - - expect(esClientMock.search).not.toHaveBeenCalled(); - expect(result).toMatchInlineSnapshot(`Array []`); - }); -}); diff --git a/src/platform/plugins/shared/unified_search/server/autocomplete/terms_agg.ts b/src/platform/plugins/shared/unified_search/server/autocomplete/terms_agg.ts deleted file mode 100644 index bf1994e78c2f1..0000000000000 --- a/src/platform/plugins/shared/unified_search/server/autocomplete/terms_agg.ts +++ /dev/null @@ -1,115 +0,0 @@ -/* - * 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 { get, map } from 'lodash'; -import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; -import type { estypes } from '@elastic/elasticsearch'; -import { getFieldSubtypeNested } from '@kbn/data-plugin/common'; -import type { FieldSpec } from '@kbn/data-views-plugin/common'; -import type { ConfigSchema } from '../config'; -import { findIndexPatternById, getFieldByName } from '../data_views'; - -export async function termsAggSuggestions( - config: ConfigSchema, - savedObjectsClient: SavedObjectsClientContract, - esClient: ElasticsearchClient, - index: string, - fieldName: string, - query: string, - filters?: estypes.QueryDslQueryContainer[], - field?: FieldSpec, - abortSignal?: AbortSignal -) { - const autocompleteSearchOptions = { - timeout: `${config.autocomplete.valueSuggestions.timeout.asMilliseconds()}ms`, - terminate_after: config.autocomplete.valueSuggestions.terminateAfter.asMilliseconds(), - }; - - if (!field?.name && !field?.type) { - const indexPattern = await findIndexPatternById(savedObjectsClient, index); - - field = indexPattern && getFieldByName(fieldName, indexPattern); - } - - // Terms agg doesn't support IP with "exclude"/"include" parameter - if (field?.type === 'ip') { - return []; - } - - const body = await getBody(autocompleteSearchOptions, field ?? fieldName, query, filters); - - const result = await esClient.search( - { index, ...body }, - { - signal: abortSignal, - } - ); - - const buckets = - get(result, 'aggregations.suggestions.buckets') || - get(result, 'aggregations.nestedSuggestions.suggestions.buckets'); - - return map(buckets ?? [], 'key'); -} - -async function getBody( - { timeout, terminate_after }: Record, - field: FieldSpec | string, - query: string, - filters: estypes.QueryDslQueryContainer[] = [] -) { - const isFieldObject = (f: any): f is FieldSpec => Boolean(f && f.name); - - // https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html#_standard_operators - const getEscapedQuery = (q: string = '') => - q.replace(/[.?+*|{}[\]()"\\#@&<>~]/g, (match) => `\\${match}`); - - // Helps ensure that the regex is not evaluated eagerly against the terms dictionary - const executionHint = 'map' as const; - - // We don't care about the accuracy of the counts, just the content of the terms, so this reduces - // the amount of information that needs to be transmitted to the coordinating node - const shardSize = 10; - const body = { - size: 0, - timeout, - terminate_after, - query: { - bool: { - filter: filters, - }, - }, - aggs: { - suggestions: { - terms: { - field: isFieldObject(field) ? field.name : field, - include: `${getEscapedQuery(query)}.*`, - execution_hint: executionHint, - shard_size: shardSize, - }, - }, - }, - }; - const subTypeNested = isFieldObject(field) && getFieldSubtypeNested(field); - if (isFieldObject(field) && subTypeNested) { - return { - ...body, - aggs: { - nestedSuggestions: { - nested: { - path: subTypeNested.nested.path, - }, - aggs: body.aggs, - }, - }, - }; - } - - return body; -} diff --git a/src/platform/plugins/shared/unified_search/server/autocomplete/terms_enum.test.ts b/src/platform/plugins/shared/unified_search/server/autocomplete/terms_enum.test.ts deleted file mode 100644 index 84326792414de..0000000000000 --- a/src/platform/plugins/shared/unified_search/server/autocomplete/terms_enum.test.ts +++ /dev/null @@ -1,113 +0,0 @@ -/* - * 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 { termsEnumSuggestions } from './terms_enum'; -import { coreMock } from '@kbn/core/server/mocks'; -import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; -import type { ConfigSchema } from '../config'; -import type { DeeplyMockedKeys } from '@kbn/utility-types-jest'; -import type { TermsEnumResponse } from '@elastic/elasticsearch/lib/api/types'; -import type { DataViewField } from '@kbn/data-views-plugin/common'; - -let savedObjectsClientMock: jest.Mocked; -let esClientMock: DeeplyMockedKeys; -const configMock = { - autocomplete: { valueSuggestions: { tiers: ['data_hot', 'data_warm', 'data_content'] } }, -} as ConfigSchema; -const dataViewFieldMock = { - name: 'field_name', - type: 'string', - searchable: true, - aggregatable: true, -} as DataViewField; -const mockResponse = { terms: ['whoa', 'amazing'] }; - -jest.mock('../data_views'); - -describe('_terms_enum suggestions', () => { - beforeEach(() => { - const requestHandlerContext = coreMock.createRequestHandlerContext(); - savedObjectsClientMock = requestHandlerContext.savedObjects.client; - esClientMock = requestHandlerContext.elasticsearch.client.asCurrentUser; - esClientMock.termsEnum.mockResolvedValue(mockResponse as unknown as TermsEnumResponse); - }); - - it('calls the _terms_enum API with the field, query, filters, and config tiers', async () => { - const result = await termsEnumSuggestions( - configMock, - savedObjectsClientMock, - esClientMock, - 'index', - 'fieldName', - 'query', - [], - dataViewFieldMock - ); - - const [[args]] = esClientMock.termsEnum.mock.calls; - - expect(args).toMatchInlineSnapshot(` - Object { - "field": "field_name", - "index": "index", - "index_filter": Object { - "bool": Object { - "must": Array [], - "must_not": Object { - "terms": Object { - "_tier": Array [ - "data_cold", - "data_frozen", - ], - }, - }, - }, - }, - "string": "query", - } - `); - expect(result).toEqual(mockResponse.terms); - }); - - it('calls the _terms_enum API and fallback to fieldName when field is null', async () => { - const result = await termsEnumSuggestions( - configMock, - savedObjectsClientMock, - esClientMock, - 'index', - 'fieldName', - 'query', - [] - ); - - const [[args]] = esClientMock.termsEnum.mock.calls; - - expect(args).toMatchInlineSnapshot(` - Object { - "field": "fieldName", - "index": "index", - "index_filter": Object { - "bool": Object { - "must": Array [], - "must_not": Object { - "terms": Object { - "_tier": Array [ - "data_cold", - "data_frozen", - ], - }, - }, - }, - }, - "string": "query", - } - `); - expect(result).toEqual(mockResponse.terms); - }); -}); diff --git a/src/platform/plugins/shared/unified_search/server/autocomplete/terms_enum.ts b/src/platform/plugins/shared/unified_search/server/autocomplete/terms_enum.ts deleted file mode 100644 index d9f97c138d85a..0000000000000 --- a/src/platform/plugins/shared/unified_search/server/autocomplete/terms_enum.ts +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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 type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; -import type { estypes } from '@elastic/elasticsearch'; -import type { FieldSpec } from '@kbn/data-views-plugin/common'; -import { findIndexPatternById, getFieldByName } from '../data_views'; -import type { ConfigSchema } from '../config'; - -export async function termsEnumSuggestions( - config: ConfigSchema, - savedObjectsClient: SavedObjectsClientContract, - esClient: ElasticsearchClient, - index: string, - fieldName: string, - query: string, - filters?: estypes.QueryDslQueryContainer[], - field?: FieldSpec, - abortSignal?: AbortSignal -) { - // See https://github.com/elastic/kibana/issues/165264 - const { tiers } = config.autocomplete.valueSuggestions; - const excludedTiers = [ - 'data_content', - 'data_hot', - 'data_warm', - 'data_cold', - 'data_frozen', - ].filter((tier) => !tiers.includes(tier)); - - if (!field?.name && !field?.type) { - const indexPattern = await findIndexPatternById(savedObjectsClient, index); - field = indexPattern && getFieldByName(fieldName, indexPattern); - } - - const body = { - field: field?.name ?? fieldName, - string: query, - index_filter: { - bool: { - must: filters ?? [], - must_not: { - terms: { - _tier: excludedTiers, - }, - }, - }, - }, - }; - - const { terms } = await esClient.termsEnum({ index, ...body }, { signal: abortSignal }); - return terms; -} diff --git a/src/platform/plugins/shared/unified_search/server/autocomplete/value_suggestions_route.ts b/src/platform/plugins/shared/unified_search/server/autocomplete/value_suggestions_route.ts deleted file mode 100644 index 459397c4f6341..0000000000000 --- a/src/platform/plugins/shared/unified_search/server/autocomplete/value_suggestions_route.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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 { schema } from '@kbn/config-schema'; -import type { IRouter } from '@kbn/core/server'; -import type { Observable } from 'rxjs'; -import { firstValueFrom } from 'rxjs'; -import { getRequestAbortedSignal } from '@kbn/data-plugin/server'; -import { getKbnServerError, reportServerError } from '@kbn/kibana-utils-plugin/server'; -import type { ConfigSchema } from '../config'; -import { termsEnumSuggestions } from './terms_enum'; -import { termsAggSuggestions } from './terms_agg'; - -export function registerValueSuggestionsRoute(router: IRouter, config$: Observable) { - router.versioned - .post({ - path: '/internal/kibana/suggestions/values/{index}', - access: 'internal', - security: { - authz: { - enabled: false, - reason: - 'This route is opted out from authorization because uses the current user authorizations.', - }, - }, - }) - .addVersion( - { - version: '1', - validate: { - request: { - params: schema.object( - { - index: schema.string(), - }, - { unknowns: 'allow' } - ), - body: schema.object( - { - field: schema.string(), - query: schema.string(), - filters: schema.maybe(schema.any()), - fieldMeta: schema.maybe(schema.any()), - method: schema.maybe( - schema.oneOf([schema.literal('terms_agg'), schema.literal('terms_enum')]) - ), - }, - { unknowns: 'allow' } - ), - }, - }, - }, - async (context, request, response) => { - const config = await firstValueFrom(config$); - const { field: fieldName, query, filters, fieldMeta, method } = request.body; - const { index } = request.params; - const abortSignal = getRequestAbortedSignal(request.events.aborted$); - const { savedObjects, elasticsearch } = await context.core; - - try { - const fn = method === 'terms_agg' ? termsAggSuggestions : termsEnumSuggestions; - const body = await fn( - config, - savedObjects.client, - elasticsearch.client.asCurrentUser, - index, - fieldName, - query, - filters, - fieldMeta, - abortSignal - ); - return response.ok({ body }); - } catch (e) { - const kbnErr = getKbnServerError(e); - return reportServerError(response, kbnErr); - } - } - ); -} diff --git a/src/platform/plugins/shared/unified_search/server/config.ts b/src/platform/plugins/shared/unified_search/server/config.ts deleted file mode 100644 index b38424a7cda47..0000000000000 --- a/src/platform/plugins/shared/unified_search/server/config.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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 type { TypeOf } from '@kbn/config-schema'; -import { schema } from '@kbn/config-schema'; - -export const configSchema = schema.object({ - autocomplete: schema.object({ - querySuggestions: schema.object({ - enabled: schema.boolean({ defaultValue: true }), - }), - valueSuggestions: schema.object({ - enabled: schema.boolean({ defaultValue: true }), - tiers: schema.arrayOf( - schema.oneOf([ - schema.literal('data_content'), - schema.literal('data_hot'), - schema.literal('data_warm'), - schema.literal('data_cold'), - schema.literal('data_frozen'), - ]), - { - defaultValue: ['data_hot', 'data_warm', 'data_content', 'data_cold'], - } - ), - terminateAfter: schema.duration({ defaultValue: 100000 }), - timeout: schema.duration({ defaultValue: 1000 }), - }), - }), -}); - -export type ConfigSchema = TypeOf; diff --git a/src/platform/plugins/shared/unified_search/server/config_deprecations.test.ts b/src/platform/plugins/shared/unified_search/server/config_deprecations.test.ts deleted file mode 100644 index e8010d5055fd6..0000000000000 --- a/src/platform/plugins/shared/unified_search/server/config_deprecations.test.ts +++ /dev/null @@ -1,138 +0,0 @@ -/* - * 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 { cloneDeep } from 'lodash'; - -import { applyDeprecations, configDeprecationFactory } from '@kbn/config'; -import { configDeprecationsMock } from '@kbn/core/server/mocks'; - -import { autocompleteConfigDeprecationProvider } from './config_deprecations'; - -const deprecationContext = configDeprecationsMock.createContext(); - -const applyConfigDeprecations = (settings: Record = {}) => { - const deprecations = autocompleteConfigDeprecationProvider(configDeprecationFactory); - const deprecationMessages: string[] = []; - const migrated = applyDeprecations( - settings, - deprecations.map((deprecation) => ({ - deprecation, - path: '', - context: deprecationContext, - })), - () => - ({ message }) => - deprecationMessages.push(message) - ); - return { - messages: deprecationMessages, - migrated: migrated.config, - }; -}; - -describe('Config Deprecations', () => { - it('does not report deprecations for default configurationc', () => { - const configFirstStep = { data: { autocomplete: { valueSuggestions: {} } } }; - const { messages, migrated } = applyConfigDeprecations(cloneDeep(configFirstStep)); - expect(migrated).toEqual(configFirstStep); - expect(messages).toHaveLength(0); - }); - - it('renames kibana.autocompleteTerminateAfter to unifiedSearch.autocomplete.valueSuggestions.terminateAfter', () => { - const config = { - kibana: { - autocompleteTerminateAfter: 123, - }, - }; - const { messages, migrated } = applyConfigDeprecations(cloneDeep(config)); - expect(migrated.kibana?.autocompleteTerminateAfter).not.toBeDefined(); - expect(migrated.unifiedSearch.autocomplete.valueSuggestions.terminateAfter).toEqual(123); - expect(messages).toMatchInlineSnapshot(` - Array [ - "Setting \\"kibana.autocompleteTerminateAfter\\" has been replaced by \\"unifiedSearch.autocomplete.valueSuggestions.terminateAfter\\"", - ] - `); - }); - - it('renames kibana.autocompleteTimeout to unifiedSearch.autocomplete.valueSuggestions.timeout', () => { - const config = { - kibana: { - autocompleteTimeout: 123, - }, - }; - const { messages, migrated } = applyConfigDeprecations(cloneDeep(config)); - expect(migrated.kibana?.autocompleteTimeout).not.toBeDefined(); - expect(migrated.unifiedSearch.autocomplete.valueSuggestions.timeout).toEqual(123); - expect(messages).toMatchInlineSnapshot(` - Array [ - "Setting \\"kibana.autocompleteTimeout\\" has been replaced by \\"unifiedSearch.autocomplete.valueSuggestions.timeout\\"", - ] - `); - }); - - it('renames data.autocomplete.querySuggestions.enabled to unifiedSearch.autocomplete.querySuggestions.enabled', () => { - const config = { - data: { - autocomplete: { - querySuggestions: { - enabled: false, - }, - }, - }, - }; - const { messages, migrated } = applyConfigDeprecations(cloneDeep(config)); - expect(migrated.data?.autocomplete.querySuggestions.enabled).not.toBeDefined(); - expect(migrated.unifiedSearch.autocomplete.querySuggestions.enabled).toEqual(false); - expect(messages).toMatchInlineSnapshot(` - Array [ - "Setting \\"data.autocomplete.querySuggestions.enabled\\" has been replaced by \\"unifiedSearch.autocomplete.querySuggestions.enabled\\"", - ] - `); - }); - - it('renames data.autocomplete.valueSuggestions.enabled to unifiedSearch.autocomplete.valueSuggestions.enabled', () => { - const config = { - data: { - autocomplete: { - valueSuggestions: { - enabled: false, - }, - }, - }, - }; - const { messages, migrated } = applyConfigDeprecations(cloneDeep(config)); - expect(migrated.data?.autocomplete.valueSuggestions.enabled).not.toBeDefined(); - expect(migrated.unifiedSearch.autocomplete.valueSuggestions.enabled).toEqual(false); - expect(messages).toMatchInlineSnapshot(` - Array [ - "Setting \\"data.autocomplete.valueSuggestions.enabled\\" has been replaced by \\"unifiedSearch.autocomplete.valueSuggestions.enabled\\"", - ] - `); - }); - - it('renames data.autocomplete.valueSuggestions.tiers to unifiedSearch.autocomplete.valueSuggestions.tiers', () => { - const config = { - data: { - autocomplete: { - valueSuggestions: { - tiers: [], - }, - }, - }, - }; - const { messages, migrated } = applyConfigDeprecations(cloneDeep(config)); - expect(migrated.data?.autocomplete.valueSuggestions.tiers).not.toBeDefined(); - expect(migrated.unifiedSearch.autocomplete.valueSuggestions.tiers).toEqual([]); - expect(messages).toMatchInlineSnapshot(` - Array [ - "Setting \\"data.autocomplete.valueSuggestions.tiers\\" has been replaced by \\"unifiedSearch.autocomplete.valueSuggestions.tiers\\"", - ] - `); - }); -}); diff --git a/src/platform/plugins/shared/unified_search/server/config_deprecations.ts b/src/platform/plugins/shared/unified_search/server/config_deprecations.ts deleted file mode 100644 index d5cab66712076..0000000000000 --- a/src/platform/plugins/shared/unified_search/server/config_deprecations.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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 type { ConfigDeprecationProvider } from '@kbn/core/server'; - -export const autocompleteConfigDeprecationProvider: ConfigDeprecationProvider = ({ - renameFromRoot, -}) => [ - renameFromRoot( - 'data.autocomplete.valueSuggestions.terminateAfter', - 'unifiedSearch.autocomplete.valueSuggestions.terminateAfter', - { level: 'warning' } - ), - renameFromRoot( - 'kibana.autocompleteTerminateAfter', - 'unifiedSearch.autocomplete.valueSuggestions.terminateAfter', - { level: 'warning' } - ), - renameFromRoot( - 'data.autocomplete.valueSuggestions.timeout', - 'unifiedSearch.autocomplete.valueSuggestions.timeout', - { - level: 'warning', - } - ), - renameFromRoot( - 'kibana.autocompleteTimeout', - 'unifiedSearch.autocomplete.valueSuggestions.timeout', - { - level: 'warning', - } - ), - renameFromRoot( - 'data.autocomplete.querySuggestions.enabled', - 'unifiedSearch.autocomplete.querySuggestions.enabled', - { - level: 'warning', - } - ), - renameFromRoot( - 'data.autocomplete.valueSuggestions.enabled', - 'unifiedSearch.autocomplete.valueSuggestions.enabled', - { - level: 'warning', - } - ), - renameFromRoot( - 'data.autocomplete.valueSuggestions.tiers', - 'unifiedSearch.autocomplete.valueSuggestions.tiers', - { - level: 'warning', - } - ), -]; diff --git a/src/platform/plugins/shared/unified_search/server/data_views/index.ts b/src/platform/plugins/shared/unified_search/server/data_views/index.ts deleted file mode 100644 index 2578963999c5a..0000000000000 --- a/src/platform/plugins/shared/unified_search/server/data_views/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * 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". - */ - -export * from '@kbn/data-views-plugin/server'; diff --git a/src/platform/plugins/shared/unified_search/server/index.ts b/src/platform/plugins/shared/unified_search/server/index.ts deleted file mode 100644 index be5fafb8fa576..0000000000000 --- a/src/platform/plugins/shared/unified_search/server/index.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 type { PluginConfigDescriptor, PluginInitializerContext } from '@kbn/core/server'; -import type { ConfigSchema } from './config'; -import { configSchema } from './config'; -import type { - UnifiedSearchServerPlugin, - UnifiedSearchServerPluginSetup, - UnifiedSearchServerPluginStart, -} from './plugin'; - -import { autocompleteConfigDeprecationProvider } from './config_deprecations'; - -/** - * Static code to be shared externally - * @public - */ - -export async function plugin(initializerContext: PluginInitializerContext) { - const { UnifiedSearchServerPlugin } = await import('./plugin'); - return new UnifiedSearchServerPlugin(initializerContext); -} - -export type { - UnifiedSearchServerPluginSetup as PluginSetup, - UnifiedSearchServerPluginStart as PluginStart, -}; -export type { UnifiedSearchServerPlugin as Plugin }; - -export const config: PluginConfigDescriptor = { - deprecations: autocompleteConfigDeprecationProvider, - exposeToBrowser: { - autocomplete: true, - }, - schema: configSchema, -}; diff --git a/src/platform/plugins/shared/unified_search/server/plugin.ts b/src/platform/plugins/shared/unified_search/server/plugin.ts deleted file mode 100644 index 24652933f9c25..0000000000000 --- a/src/platform/plugins/shared/unified_search/server/plugin.ts +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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 type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/server'; -import type { ConfigSchema } from './config'; -import { AutocompleteService } from './autocomplete'; -import type { AutocompleteSetup } from './autocomplete/autocomplete_service'; - -export interface UnifiedSearchServerPluginSetup { - autocomplete: AutocompleteSetup; -} - -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface UnifiedSearchServerPluginStart {} - -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface UnifiedSearchServerPluginSetupDependencies {} - -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface UnifiedSearchServerPluginStartDependencies {} - -export class UnifiedSearchServerPlugin - implements - Plugin< - UnifiedSearchServerPluginSetup, - UnifiedSearchServerPluginStart, - UnifiedSearchServerPluginSetupDependencies, - UnifiedSearchServerPluginStartDependencies - > -{ - private readonly autocompleteService: AutocompleteService; - - constructor(initializerContext: PluginInitializerContext) { - this.autocompleteService = new AutocompleteService(initializerContext); - } - - public setup( - core: CoreSetup, - {}: UnifiedSearchServerPluginSetupDependencies - ) { - return { - autocomplete: this.autocompleteService.setup(core), - }; - } - - public start(core: CoreStart, {}: UnifiedSearchServerPluginStartDependencies) { - return {}; - } - - public stop() {} -} - -export { UnifiedSearchServerPlugin as Plugin }; diff --git a/src/platform/plugins/shared/unified_search/tsconfig.json b/src/platform/plugins/shared/unified_search/tsconfig.json index 89ca07d0f0837..ea232175cbd19 100644 --- a/src/platform/plugins/shared/unified_search/tsconfig.json +++ b/src/platform/plugins/shared/unified_search/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "outDir": "target/types" }, - "include": ["public/**/*", "public/**/*.json", "server/**/*", "../../../../../typings/**/*"], + "include": ["public/**/*", "public/**/*.json", "../../../../../typings/**/*"], "kbn_references": [ "@kbn/core", "@kbn/data-plugin", diff --git a/src/platform/plugins/shared/vis_types/timeseries/public/application/components/query_bar_wrapper.tsx b/src/platform/plugins/shared/vis_types/timeseries/public/application/components/query_bar_wrapper.tsx index 7c76d81959161..611ccafd97e7a 100644 --- a/src/platform/plugins/shared/vis_types/timeseries/public/application/components/query_bar_wrapper.tsx +++ b/src/platform/plugins/shared/vis_types/timeseries/public/application/components/query_bar_wrapper.tsx @@ -9,8 +9,8 @@ import React, { useEffect, useState } from 'react'; -import type { QueryStringInputProps } from '@kbn/unified-search-plugin/public'; -import { QueryStringInput } from '@kbn/unified-search-plugin/public'; +import type { QueryStringInputProps } from '@kbn/kql/public'; +import { QueryStringInput } from '@kbn/kql/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import type { IndexPatternValue } from '../../../common/types'; @@ -36,12 +36,13 @@ export function QueryBarWrapper({ const kibana = useKibana(); const { appName, - unifiedSearch, + kql, storage, data, notifications, http, docLinks, + core, uiSettings, usageCollection, } = kibana.services; @@ -79,7 +80,8 @@ export function QueryBarWrapper({ { timefilter: TimefilterContract; appName: string; unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; + core: CoreStart; notifications: CoreStart['notifications']; storage: IStorageWrapper; data: DataPublicPluginStart; @@ -110,6 +115,7 @@ export class MetricsPlugin implements Plugin { usageCollection, fieldFormats, unifiedSearch, + kql, uiActions, embeddable, }: MetricsPluginStartDependencies @@ -119,6 +125,7 @@ export class MetricsPlugin implements Plugin { setFieldFormats(fieldFormats); setDataStart(data); setUnifiedSearchStart(unifiedSearch); + setKqlStart(kql); setDataViewsStart(dataViews); setCoreStart(core); setUsageCollectionStart(usageCollection); diff --git a/src/platform/plugins/shared/vis_types/timeseries/public/services.ts b/src/platform/plugins/shared/vis_types/timeseries/public/services.ts index d2c5647013684..9fb004f342244 100644 --- a/src/platform/plugins/shared/vis_types/timeseries/public/services.ts +++ b/src/platform/plugins/shared/vis_types/timeseries/public/services.ts @@ -15,6 +15,7 @@ import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import type { KqlPluginStart } from '@kbn/kql/public'; export const [getUISettings, setUISettings] = createGetterSetter('UISettings'); @@ -27,6 +28,8 @@ export const [getDataStart, setDataStart] = createGetterSetter('unifiedSearchStart'); +export const [getKqlStart, setKqlStart] = createGetterSetter('kqlStart'); + export const [getDataViewsStart, setDataViewsStart] = createGetterSetter('dataViews'); diff --git a/src/platform/test/plugin_functional/test_suites/core_plugins/rendering.ts b/src/platform/test/plugin_functional/test_suites/core_plugins/rendering.ts index 3e683bf985d98..3383bd74fe466 100644 --- a/src/platform/test/plugin_functional/test_suites/core_plugins/rendering.ts +++ b/src/platform/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -101,11 +101,11 @@ export default function ({ getService }: PluginFunctionalProviderContext) { 'console.autocompleteDefinitions.endpointsAvailability (stack?|serverless?)', 'console.ui.enabled (boolean?)', 'console.ui.embeddedEnabled (boolean?)', - 'unifiedSearch.autocomplete.querySuggestions.enabled (boolean?)', - 'unifiedSearch.autocomplete.valueSuggestions.enabled (boolean?)', - 'unifiedSearch.autocomplete.valueSuggestions.terminateAfter (duration?)', - 'unifiedSearch.autocomplete.valueSuggestions.tiers (array?)', - 'unifiedSearch.autocomplete.valueSuggestions.timeout (duration?)', + 'kql.autocomplete.querySuggestions.enabled (boolean?)', + 'kql.autocomplete.valueSuggestions.enabled (boolean?)', + 'kql.autocomplete.valueSuggestions.terminateAfter (duration?)', + 'kql.autocomplete.valueSuggestions.tiers (array?)', + 'kql.autocomplete.valueSuggestions.timeout (duration?)', 'data.search.aggs.shardDelay.enabled (boolean?)', 'data.search.asyncSearch.batchedReduceSize (number?)', 'data.search.asyncSearch.keepAlive (duration?)', diff --git a/x-pack/platform/plugins/private/graph/public/components/search_bar.test.tsx b/x-pack/platform/plugins/private/graph/public/components/search_bar.test.tsx index ebc6375a0e305..05efe61e902e4 100644 --- a/x-pack/platform/plugins/private/graph/public/components/search_bar.test.tsx +++ b/x-pack/platform/plugins/private/graph/public/components/search_bar.test.tsx @@ -18,7 +18,7 @@ import type { OverlayStart, } from '@kbn/core/public'; import { act } from 'react-dom/test-utils'; -import { QueryStringInput } from '@kbn/unified-search-plugin/public'; +import { QueryStringInput } from '@kbn/kql/public'; import { createStubDataView } from '@kbn/data-views-plugin/common/mocks'; import type { DataView } from '@kbn/data-views-plugin/public'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; @@ -32,8 +32,9 @@ import { setDatasource, submitSearchSaga } from '../state_management'; import type { ReactWrapper } from 'enzyme'; import { createMockGraphStore } from '../state_management/mocks'; import { Provider } from 'react-redux'; -import { createQueryStringInput } from '@kbn/unified-search-plugin/public/query_string_input/get_query_string_input'; -import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; +import { createQueryStringInput } from '@kbn/kql/public/components/query_string_input/get_query_string_input'; +import { coreMock } from '@kbn/core/public/mocks'; +import { kqlPluginMock } from '@kbn/kql/public/mocks'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; @@ -56,6 +57,7 @@ function getServiceMocks() { }, } as IUiSettingsClient; + const kqlMock = kqlPluginMock.createStartContract(); return { overlays: {} as OverlayStart, unifiedSearch: { @@ -70,10 +72,11 @@ function getServiceMocks() { clear: () => {}, } as IStorageWrapper, data: dataPluginMock.createStartContract(), - unifiedSearch: unifiedSearchPluginMock.createStartContract(), + autocomplete: kqlMock.autocomplete, notifications: {} as NotificationsStart, http: {} as HttpStart, dataViews: dataViewPluginMocks.createStartContract(), + core: coreMock.createStart(), }), }, }, diff --git a/x-pack/platform/plugins/private/graph/public/components/search_bar.tsx b/x-pack/platform/plugins/private/graph/public/components/search_bar.tsx index 4540e585bdcf1..ca85c8be58772 100644 --- a/x-pack/platform/plugins/private/graph/public/components/search_bar.tsx +++ b/x-pack/platform/plugins/private/graph/public/components/search_bar.tsx @@ -28,6 +28,7 @@ import type { import type { ContentManagementPublicStart } from '@kbn/content-management-plugin/public'; import { css } from '@emotion/react'; +import type { KqlPluginStart } from '@kbn/kql/public'; import type { IndexPatternSavedObject, IndexPatternProvider, WorkspaceField } from '../types'; import { openSourceModal } from '../services/source_modal'; import type { GraphState, IndexpatternDatasource } from '../state_management'; @@ -111,15 +112,14 @@ export function SearchBarComponent(props: SearchBarStateProps & SearchBarProps) IUnifiedSearchPluginServices & { contentManagement: ContentManagementPublicStart; unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; } >(); const { uiSettings, appName, - unifiedSearch: { - ui: { QueryStringInput }, - }, + kql: { QueryStringInput }, contentManagement, } = services; if (!overlays) return null; diff --git a/x-pack/platform/plugins/private/monitoring/public/components/kuery_bar/autocomplete_field.tsx b/x-pack/platform/plugins/private/monitoring/public/components/kuery_bar/autocomplete_field.tsx index 75c02c5a34214..b9d6a8648f4e9 100644 --- a/x-pack/platform/plugins/private/monitoring/public/components/kuery_bar/autocomplete_field.tsx +++ b/x-pack/platform/plugins/private/monitoring/public/components/kuery_bar/autocomplete_field.tsx @@ -10,7 +10,7 @@ import { css } from '@emotion/react'; import type { UseEuiTheme } from '@elastic/eui'; import { EuiFieldSearch, EuiOutsideClickDetector, EuiPanel, logicalCSS } from '@elastic/eui'; -import type { QuerySuggestion } from '@kbn/unified-search-plugin/public'; +import type { QuerySuggestion } from '@kbn/kql/public'; import { composeStateUpdaters } from '../../lib/typed_react'; import { SuggestionItem } from './suggestion_item'; diff --git a/x-pack/platform/plugins/private/monitoring/public/components/kuery_bar/index.tsx b/x-pack/platform/plugins/private/monitoring/public/components/kuery_bar/index.tsx index 1f1d0eb6095ac..18d98f963c32f 100644 --- a/x-pack/platform/plugins/private/monitoring/public/components/kuery_bar/index.tsx +++ b/x-pack/platform/plugins/private/monitoring/public/components/kuery_bar/index.tsx @@ -9,7 +9,7 @@ import { fromKueryExpression } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import React, { useEffect, useState } from 'react'; import type { DataView } from '@kbn/data-views-plugin/public'; -import type { QuerySuggestion } from '@kbn/unified-search-plugin/public'; +import type { QuerySuggestion } from '@kbn/kql/public'; import { AutocompleteField } from './autocomplete_field'; import { WithKueryAutocompletion } from './with_kuery_autocompletion'; diff --git a/x-pack/platform/plugins/private/monitoring/public/components/kuery_bar/suggestion_item.tsx b/x-pack/platform/plugins/private/monitoring/public/components/kuery_bar/suggestion_item.tsx index 6c64db17a30ec..5aadfe108bc3d 100644 --- a/x-pack/platform/plugins/private/monitoring/public/components/kuery_bar/suggestion_item.tsx +++ b/x-pack/platform/plugins/private/monitoring/public/components/kuery_bar/suggestion_item.tsx @@ -11,8 +11,8 @@ import { EuiIcon, euiFontSize } from '@elastic/eui'; import { css } from '@emotion/react'; import { transparentize } from 'polished'; -import type { QuerySuggestion } from '@kbn/unified-search-plugin/public'; -import { QuerySuggestionTypes } from '@kbn/unified-search-plugin/public'; +import type { QuerySuggestion } from '@kbn/kql/public'; +import { QuerySuggestionTypes } from '@kbn/kql/public'; interface Props { isSelected?: boolean; diff --git a/x-pack/platform/plugins/private/monitoring/public/components/kuery_bar/with_kuery_autocompletion.tsx b/x-pack/platform/plugins/private/monitoring/public/components/kuery_bar/with_kuery_autocompletion.tsx index 57dd1e0fff52b..91c9021e8305e 100644 --- a/x-pack/platform/plugins/private/monitoring/public/components/kuery_bar/with_kuery_autocompletion.tsx +++ b/x-pack/platform/plugins/private/monitoring/public/components/kuery_bar/with_kuery_autocompletion.tsx @@ -7,18 +7,14 @@ import React from 'react'; import type { DataView } from '@kbn/data-views-plugin/public'; -import type { - UnifiedSearchPublicPluginStart, - QuerySuggestion, -} from '@kbn/unified-search-plugin/public'; +import type { KqlPluginStart } from '@kbn/kql/public'; +import type { QuerySuggestion } from '@kbn/kql/public'; import type { KibanaReactContextValue, KibanaServices } from '@kbn/kibana-react-plugin/public'; import { withKibana } from '@kbn/kibana-react-plugin/public'; import type { RendererFunction } from '../../lib/typed_react'; interface WithKueryAutocompletionLifecycleProps { - kibana: KibanaReactContextValue< - { unifiedSearch: UnifiedSearchPublicPluginStart } & KibanaServices - >; + kibana: KibanaReactContextValue<{ kql: KqlPluginStart } & KibanaServices>; children: RendererFunction<{ isLoadingSuggestions: boolean; loadSuggestions: (expression: string, cursorPosition: number, maxSuggestions?: number) => void; @@ -65,7 +61,7 @@ class WithKueryAutocompletionComponent extends React.Component< const { indexPattern } = this.props; const language = 'kuery'; const hasQuerySuggestions = - this.props.kibana.services.unifiedSearch?.autocomplete.hasQuerySuggestions(language); + this.props.kibana.services.kql?.autocomplete.hasQuerySuggestions(language); if (!hasQuerySuggestions) { return; @@ -80,7 +76,7 @@ class WithKueryAutocompletionComponent extends React.Component< }); const suggestions = - (await this.props.kibana.services.unifiedSearch.autocomplete.getQuerySuggestions({ + (await this.props.kibana.services.kql.autocomplete.getQuerySuggestions({ language, query: expression, selectionStart: cursorPosition, diff --git a/x-pack/platform/plugins/private/transform/public/app/__mocks__/app_dependencies.tsx b/x-pack/platform/plugins/private/transform/public/app/__mocks__/app_dependencies.tsx index 01bbd3fae85e6..595af6d9c86b3 100644 --- a/x-pack/platform/plugins/private/transform/public/app/__mocks__/app_dependencies.tsx +++ b/x-pack/platform/plugins/private/transform/public/app/__mocks__/app_dependencies.tsx @@ -27,6 +27,7 @@ import type { AppDependencies } from '../app_dependencies'; import type { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; import { settingsServiceMock } from '@kbn/core-ui-settings-browser-mocks'; import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; +import { kqlPluginMock } from '@kbn/kql/public/mocks'; import { userProfileServiceMock } from '@kbn/core-user-profile-browser-mocks'; import { fieldsMetadataPluginPublicMock } from '@kbn/fields-metadata-plugin/public/mocks'; @@ -96,6 +97,7 @@ const appDependencies: AppDependencies = { share: { urlGenerators: { getUrlGenerator: jest.fn() } } as unknown as SharePluginStart, triggersActionsUi: {} as jest.Mocked, unifiedSearch: unifiedSearchPluginMock.createStartContract(), + kql: kqlPluginMock.createStartContract(), savedObjectsManagement: {} as jest.Mocked, settings: settingsServiceMock.createStartContract(), savedSearch: savedSearchPluginMock.createStartContract(), diff --git a/x-pack/platform/plugins/private/transform/public/app/app_dependencies.tsx b/x-pack/platform/plugins/private/transform/public/app/app_dependencies.tsx index b62763a086fc0..54cbb2252a210 100644 --- a/x-pack/platform/plugins/private/transform/public/app/app_dependencies.tsx +++ b/x-pack/platform/plugins/private/transform/public/app/app_dependencies.tsx @@ -38,6 +38,7 @@ import type { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-manag import type { SettingsStart } from '@kbn/core-ui-settings-browser'; import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public'; +import type { KqlPluginStart } from '@kbn/kql/public'; export interface AppDependencies { analytics: AnalyticsServiceStart; @@ -63,6 +64,7 @@ export interface AppDependencies { spaces?: SpacesPluginStart; triggersActionsUi: TriggersAndActionsUIPublicPluginStart; unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; usageCollection?: UsageCollectionStart; savedObjectsManagement: SavedObjectsManagementPluginStart; settings: SettingsStart; diff --git a/x-pack/platform/plugins/private/transform/public/app/mount_management_section.ts b/x-pack/platform/plugins/private/transform/public/app/mount_management_section.ts index ff45df0fff938..f18a864039acb 100644 --- a/x-pack/platform/plugins/private/transform/public/app/mount_management_section.ts +++ b/x-pack/platform/plugins/private/transform/public/app/mount_management_section.ts @@ -52,6 +52,7 @@ export async function mountManagementSection( spaces, triggersActionsUi, unifiedSearch, + kql, charts, fieldFormats, savedObjectsManagement, @@ -89,6 +90,7 @@ export async function mountManagementSection( spaces, triggersActionsUi, unifiedSearch, + kql, charts, fieldFormats, savedObjectsManagement, diff --git a/x-pack/platform/plugins/private/transform/public/app/sections/create_transform/components/source_search_bar/source_search_bar.tsx b/x-pack/platform/plugins/private/transform/public/app/sections/create_transform/components/source_search_bar/source_search_bar.tsx index 4343f7dd6c342..09cdd404f5844 100644 --- a/x-pack/platform/plugins/private/transform/public/app/sections/create_transform/components/source_search_bar/source_search_bar.tsx +++ b/x-pack/platform/plugins/private/transform/public/app/sections/create_transform/components/source_search_bar/source_search_bar.tsx @@ -26,9 +26,7 @@ export const SourceSearchBar: FC = ({ dataView, searchBar } = searchBar; const { - unifiedSearch: { - ui: { QueryStringInput }, - }, + kql: { QueryStringInput }, } = useAppDependencies(); return ( diff --git a/x-pack/platform/plugins/private/transform/public/plugin.ts b/x-pack/platform/plugins/private/transform/public/plugin.ts index 33f5b53ebd4de..817ad0bb49be3 100644 --- a/x-pack/platform/plugins/private/transform/public/plugin.ts +++ b/x-pack/platform/plugins/private/transform/public/plugin.ts @@ -17,6 +17,7 @@ import type { SpacesApi } from '@kbn/spaces-plugin/public'; import type { PluginSetupContract as AlertingSetup } from '@kbn/alerting-plugin/public'; import type { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import type { KqlPluginStart } from '@kbn/kql/public'; import type { ChartsPluginStart } from '@kbn/charts-plugin/public'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import type { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public/plugin'; @@ -34,6 +35,7 @@ export interface PluginsDependencies { data: DataPublicPluginStart; dataViewEditor?: DataViewEditorStart; unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; dataViews: DataViewsPublicPluginStart; management: ManagementSetup; home: HomePublicPluginSetup; diff --git a/x-pack/platform/plugins/shared/alerting/server/plugin.test.ts b/x-pack/platform/plugins/shared/alerting/server/plugin.test.ts index 0c1d3b76b2852..4debe59679caf 100644 --- a/x-pack/platform/plugins/shared/alerting/server/plugin.test.ts +++ b/x-pack/platform/plugins/shared/alerting/server/plugin.test.ts @@ -21,7 +21,7 @@ import type { RuleType } from './types'; import { eventLogMock } from '@kbn/event-log-plugin/server/mocks'; import { actionsMock } from '@kbn/actions-plugin/server/mocks'; import { dataPluginMock } from '@kbn/data-plugin/server/mocks'; -import { dataPluginMock as unifiedSearchPluginMock } from '@kbn/unified-search-plugin/server/mocks'; +import { dataPluginMock as kqlPluginMock } from '@kbn/kql/server/mocks'; import { monitoringCollectionMock } from '@kbn/monitoring-collection-plugin/server/mocks'; import type { DataViewsServerPluginStart } from '@kbn/data-views-plugin/server'; import type { PluginSetup as DataPluginSetup } from '@kbn/data-plugin/server'; @@ -76,7 +76,7 @@ describe('Alerting Plugin', () => { monitoringCollection: monitoringCollectionMock.createSetup(), data: dataPluginMock.createSetupContract() as unknown as DataPluginSetup, features: featuresPluginMock.createSetup(), - unifiedSearch: unifiedSearchPluginMock.createSetupContract(), + kql: kqlPluginMock.createSetupContract(), ...(useDataStreamForAlerts ? { serverless: serverlessPluginMock.createSetupContract() } : {}), @@ -433,7 +433,7 @@ describe('Alerting Plugin', () => { monitoringCollection: monitoringCollectionMock.createSetup(), data: dataPluginMock.createSetupContract() as unknown as DataPluginSetup, features: featuresPluginMock.createSetup(), - unifiedSearch: unifiedSearchPluginMock.createSetupContract(), + kql: kqlPluginMock.createSetupContract(), ...(useDataStreamForAlerts ? { serverless: serverlessPluginMock.createSetupContract() } : {}), @@ -486,7 +486,7 @@ describe('Alerting Plugin', () => { monitoringCollection: monitoringCollectionMock.createSetup(), data: dataPluginMock.createSetupContract() as unknown as DataPluginSetup, features: featuresPluginMock.createSetup(), - unifiedSearch: unifiedSearchPluginMock.createSetupContract(), + kql: kqlPluginMock.createSetupContract(), ...(useDataStreamForAlerts ? { serverless: serverlessPluginMock.createSetupContract() } : {}), @@ -551,7 +551,7 @@ describe('Alerting Plugin', () => { monitoringCollection: monitoringCollectionMock.createSetup(), data: dataPluginMock.createSetupContract() as unknown as DataPluginSetup, features: featuresPluginMock.createSetup(), - unifiedSearch: unifiedSearchPluginMock.createSetupContract(), + kql: kqlPluginMock.createSetupContract(), ...(useDataStreamForAlerts ? { serverless: serverlessPluginMock.createSetupContract() } : {}), diff --git a/x-pack/platform/plugins/shared/alerting/server/plugin.ts b/x-pack/platform/plugins/shared/alerting/server/plugin.ts index bf7674d6dfd3f..6d1ff00321276 100644 --- a/x-pack/platform/plugins/shared/alerting/server/plugin.ts +++ b/x-pack/platform/plugins/shared/alerting/server/plugin.ts @@ -48,7 +48,7 @@ import type { IEventLogClientService, } from '@kbn/event-log-plugin/server'; import type { FeaturesPluginStart, FeaturesPluginSetup } from '@kbn/features-plugin/server'; -import type { PluginSetup as UnifiedSearchServerPluginSetup } from '@kbn/unified-search-plugin/server'; +import type { PluginSetup as KQLPluginSetup } from '@kbn/kql/server'; import type { PluginStart as DataPluginStart } from '@kbn/data-plugin/server'; import type { MonitoringCollectionSetup } from '@kbn/monitoring-collection-plugin/server'; import type { SharePluginStart } from '@kbn/share-plugin/server'; @@ -193,7 +193,7 @@ export interface AlertingPluginsSetup { monitoringCollection: MonitoringCollectionSetup; data: DataPluginSetup; features: FeaturesPluginSetup; - unifiedSearch: UnifiedSearchServerPluginSetup; + kql: KQLPluginSetup; } export interface AlertingPluginsStart { @@ -448,7 +448,7 @@ export class AlertingPlugin { usageCounter: this.usageCounter, getAlertIndicesAlias: createGetAlertIndicesAliasFn(this.ruleTypeRegistry!), encryptedSavedObjects: plugins.encryptedSavedObjects, - config$: plugins.unifiedSearch.autocomplete.getInitializerContextConfig().create(), + config$: plugins.kql.autocomplete.getInitializerContextConfig().create(), isServerless: this.isServerless, docLinks: core.docLinks, alertingConfig: this.config, diff --git a/x-pack/platform/plugins/shared/alerting/server/plugin_cancel_alerts_on_rule_timeout.test.ts b/x-pack/platform/plugins/shared/alerting/server/plugin_cancel_alerts_on_rule_timeout.test.ts index f77cfa228b941..936420d7e71da 100644 --- a/x-pack/platform/plugins/shared/alerting/server/plugin_cancel_alerts_on_rule_timeout.test.ts +++ b/x-pack/platform/plugins/shared/alerting/server/plugin_cancel_alerts_on_rule_timeout.test.ts @@ -21,7 +21,7 @@ import type { AlertingConfig } from './config'; import type { RuleType } from './types'; import { actionsMock } from '@kbn/actions-plugin/server/mocks'; import { dataPluginMock } from '@kbn/data-plugin/server/mocks'; -import { dataPluginMock as autocompletePluginMock } from '@kbn/unified-search-plugin/server/mocks'; +import { dataPluginMock as autocompletePluginMock } from '@kbn/kql/server/mocks'; import { monitoringCollectionMock } from '@kbn/monitoring-collection-plugin/server/mocks'; import type { PluginSetup as DataPluginSetup } from '@kbn/data-plugin/server'; import { alertsServiceMock } from './alerts_service/alerts_service.mock'; @@ -64,7 +64,7 @@ describe('Alerting Plugin - cancelAlertsOnRuleTimeout', () => { monitoringCollection: monitoringCollectionMock.createSetup(), data: dataPluginMock.createSetupContract() as unknown as DataPluginSetup, features: featuresPluginMock.createSetup(), - unifiedSearch: autocompletePluginMock.createSetupContract(), + kql: autocompletePluginMock.createSetupContract(), }; let context: PluginInitializerContextMock; diff --git a/x-pack/platform/plugins/shared/alerting/server/routes/index.ts b/x-pack/platform/plugins/shared/alerting/server/routes/index.ts index 4702851660501..547a2cb0596fc 100644 --- a/x-pack/platform/plugins/shared/alerting/server/routes/index.ts +++ b/x-pack/platform/plugins/shared/alerting/server/routes/index.ts @@ -8,7 +8,7 @@ import type { CoreSetup, DocLinksServiceSetup, IRouter } from '@kbn/core/server'; import type { UsageCounter } from '@kbn/usage-collection-plugin/server'; import type { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server'; -import type { ConfigSchema } from '@kbn/unified-search-plugin/server/config'; +import type { ConfigSchema } from '@kbn/kql/server/config'; import type { Observable } from 'rxjs'; import type { AlertingConfig } from '../config'; import type { GetAlertIndicesAlias, ILicenseState } from '../lib'; diff --git a/x-pack/platform/plugins/shared/alerting/server/routes/suggestions/values_suggestion_alerts.test.ts b/x-pack/platform/plugins/shared/alerting/server/routes/suggestions/values_suggestion_alerts.test.ts index 70fdf49bbc5c9..3bdf5a215cddd 100644 --- a/x-pack/platform/plugins/shared/alerting/server/routes/suggestions/values_suggestion_alerts.test.ts +++ b/x-pack/platform/plugins/shared/alerting/server/routes/suggestions/values_suggestion_alerts.test.ts @@ -6,16 +6,16 @@ */ import { httpServerMock, httpServiceMock } from '@kbn/core-http-server-mocks'; -import type { ConfigSchema } from '@kbn/unified-search-plugin/server/config'; -import { dataPluginMock } from '@kbn/unified-search-plugin/server/mocks'; -import { termsAggSuggestions } from '@kbn/unified-search-plugin/server/autocomplete/terms_agg'; +import type { ConfigSchema } from '@kbn/kql/server/config'; +import { dataPluginMock } from '@kbn/kql/server/mocks'; +import { termsAggSuggestions } from '@kbn/kql/server/autocomplete/terms_agg'; import type { Observable } from 'rxjs'; import { licenseStateMock } from '../../lib/license_state.mock'; import { rulesClientMock } from '../../rules_client.mock'; import { mockHandlerArguments } from '../_mock_handler_arguments'; import { registerAlertsValueSuggestionsRoute } from './values_suggestion_alerts'; -jest.mock('@kbn/unified-search-plugin/server/autocomplete/terms_agg', () => { +jest.mock('@kbn/kql/server/autocomplete/terms_agg', () => { return { termsAggSuggestions: jest.fn(), }; diff --git a/x-pack/platform/plugins/shared/alerting/server/routes/suggestions/values_suggestion_alerts.ts b/x-pack/platform/plugins/shared/alerting/server/routes/suggestions/values_suggestion_alerts.ts index 1a038c5cb2466..4bb03f50396ed 100644 --- a/x-pack/platform/plugins/shared/alerting/server/routes/suggestions/values_suggestion_alerts.ts +++ b/x-pack/platform/plugins/shared/alerting/server/routes/suggestions/values_suggestion_alerts.ts @@ -10,8 +10,8 @@ import type { IRouter } from '@kbn/core/server'; import type { Observable } from 'rxjs'; import { firstValueFrom } from 'rxjs'; import { getRequestAbortedSignal } from '@kbn/data-plugin/server'; -import { termsAggSuggestions } from '@kbn/unified-search-plugin/server/autocomplete/terms_agg'; -import type { ConfigSchema } from '@kbn/unified-search-plugin/server/config'; +import { termsAggSuggestions } from '@kbn/kql/server/autocomplete/terms_agg'; +import type { ConfigSchema } from '@kbn/kql/server/config'; import { getKbnServerError, reportServerError } from '@kbn/kibana-utils-plugin/server'; import type { estypes } from '@elastic/elasticsearch'; import { ALERT_RULE_CONSUMER, ALERT_RULE_TYPE_ID, SPACE_IDS } from '@kbn/rule-data-utils'; diff --git a/x-pack/platform/plugins/shared/alerting/server/routes/suggestions/values_suggestion_rules.test.ts b/x-pack/platform/plugins/shared/alerting/server/routes/suggestions/values_suggestion_rules.test.ts index 3c4fe19fcb2a4..d8190c3c43d79 100644 --- a/x-pack/platform/plugins/shared/alerting/server/routes/suggestions/values_suggestion_rules.test.ts +++ b/x-pack/platform/plugins/shared/alerting/server/routes/suggestions/values_suggestion_rules.test.ts @@ -6,16 +6,16 @@ */ import { httpServerMock, httpServiceMock } from '@kbn/core-http-server-mocks'; -import type { ConfigSchema } from '@kbn/unified-search-plugin/server/config'; -import { dataPluginMock } from '@kbn/unified-search-plugin/server/mocks'; -import { termsAggSuggestions } from '@kbn/unified-search-plugin/server/autocomplete/terms_agg'; +import type { ConfigSchema } from '@kbn/kql/server/config'; +import { dataPluginMock } from '@kbn/kql/server/mocks'; +import { termsAggSuggestions } from '@kbn/kql/server/autocomplete/terms_agg'; import type { Observable } from 'rxjs'; import { licenseStateMock } from '../../lib/license_state.mock'; import { rulesClientMock } from '../../rules_client.mock'; import { mockHandlerArguments } from '../_mock_handler_arguments'; import { registerRulesValueSuggestionsRoute } from './values_suggestion_rules'; -jest.mock('@kbn/unified-search-plugin/server/autocomplete/terms_agg', () => { +jest.mock('@kbn/kql/server/autocomplete/terms_agg', () => { return { termsAggSuggestions: jest.fn(), }; diff --git a/x-pack/platform/plugins/shared/alerting/server/routes/suggestions/values_suggestion_rules.ts b/x-pack/platform/plugins/shared/alerting/server/routes/suggestions/values_suggestion_rules.ts index 902646fc586ac..b7ec3b50823ff 100644 --- a/x-pack/platform/plugins/shared/alerting/server/routes/suggestions/values_suggestion_rules.ts +++ b/x-pack/platform/plugins/shared/alerting/server/routes/suggestions/values_suggestion_rules.ts @@ -10,8 +10,8 @@ import type { IRouter } from '@kbn/core/server'; import type { Observable } from 'rxjs'; import { firstValueFrom } from 'rxjs'; import { getRequestAbortedSignal } from '@kbn/data-plugin/server'; -import { termsAggSuggestions } from '@kbn/unified-search-plugin/server/autocomplete/terms_agg'; -import type { ConfigSchema } from '@kbn/unified-search-plugin/server/config'; +import { termsAggSuggestions } from '@kbn/kql/server/autocomplete/terms_agg'; +import type { ConfigSchema } from '@kbn/kql/server/config'; import type { UsageCounter } from '@kbn/usage-collection-plugin/server'; import { getKbnServerError, reportServerError } from '@kbn/kibana-utils-plugin/server'; import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server/src/saved_objects_index_pattern'; diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/components/search_bar.test.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/components/search_bar.test.tsx index 0fedd7568e17d..844a0f6baafe7 100644 --- a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/components/search_bar.test.tsx +++ b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/components/search_bar.test.tsx @@ -70,7 +70,7 @@ jest.mock('../hooks', () => { }), }, }, - unifiedSearch: { + kql: { autocomplete: { getQuerySuggestions: jest.fn().mockResolvedValue([ { @@ -116,6 +116,8 @@ jest.mock('../hooks', () => { ]), hasQuerySuggestions: jest.fn().mockReturnValue(true), }, + }, + unifiedSearch: { ui: { IndexPatternSelect: jest.fn(), SearchBar: jest.fn().mockReturnValue(null), diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/components/search_bar.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/components/search_bar.tsx index 1a7074e2f885a..698903ca1d946 100644 --- a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/components/search_bar.tsx +++ b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/components/search_bar.tsx @@ -10,7 +10,7 @@ import styled from 'styled-components'; import { fromKueryExpression } from '@kbn/es-query'; import type { FieldSpec } from '@kbn/data-plugin/common'; -import { QueryStringInput } from '@kbn/unified-search-plugin/public'; +import { QueryStringInput } from '@kbn/kql/public'; import type { DataView } from '@kbn/data-views-plugin/public'; import { i18n } from '@kbn/i18n'; @@ -134,13 +134,14 @@ export const SearchBar: React.FunctionComponent = ({ const { data, dataViews, - unifiedSearch, + kql, storage, notifications, http, docLinks, uiSettings, usageCollection, + core, } = useStartServices(); const [dataView, setDataView] = useState(); @@ -198,7 +199,8 @@ export const SearchBar: React.FunctionComponent = ({ autoSubmit appName={i18n.translate('xpack.fleet.appTitle', { defaultMessage: 'Fleet' })} deps={{ - unifiedSearch, + autocomplete: kql.autocomplete, + core, notifications, http, docLinks, diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/filter_dataset.test.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/filter_dataset.test.tsx index c566c43273ed5..f4bfe2dc67933 100644 --- a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/filter_dataset.test.tsx +++ b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/filter_dataset.test.tsx @@ -29,7 +29,7 @@ jest.mock('../../../../../hooks', () => ({ create: jest.fn().mockResolvedValue([]), }, }, - unifiedSearch: { + kql: { autocomplete: { getValueSuggestions: jest .fn() diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/filter_dataset.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/filter_dataset.tsx index aaa219e42832c..b39a242f591de 100644 --- a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/filter_dataset.tsx +++ b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/filter_dataset.tsx @@ -19,7 +19,7 @@ export const DatasetFilter: React.FunctionComponent<{ selectedDatasets: string[]; onToggleDataset: (dataset: string) => void; }> = memo(({ selectedDatasets, onToggleDataset }) => { - const { unifiedSearch, data } = useStartServices(); + const { kql, data } = useStartServices(); const [isOpen, setIsOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); const [datasetValues, setDatasetValues] = useState([AGENT_DATASET]); @@ -57,7 +57,7 @@ export const DatasetFilter: React.FunctionComponent<{ fields: fieldsMap, }); - const values = await unifiedSearch.autocomplete.getValueSuggestions({ + const values = await kql.autocomplete.getValueSuggestions({ indexPattern: newDataView, field: DATASET_FIELD as DataViewField, query: '', @@ -73,7 +73,7 @@ export const DatasetFilter: React.FunctionComponent<{ setIsLoading(false); }; fetchValues(); - }, [data.dataViews, unifiedSearch.autocomplete, datasetValuesToOptions]); + }, [data.dataViews, kql.autocomplete, datasetValuesToOptions]); return ( = memo(({ query, isQueryValid, onUpdateQuery }) => { const { data, - unifiedSearch: { - ui: { QueryStringInput }, - }, + kql: { QueryStringInput }, } = useStartServices(); const [indexPatternFields, setIndexPatternFields] = useState(); diff --git a/x-pack/platform/plugins/shared/fleet/public/mock/plugin_dependencies.ts b/x-pack/platform/plugins/shared/fleet/public/mock/plugin_dependencies.ts index a19d4322627d6..915555d132749 100644 --- a/x-pack/platform/plugins/shared/fleet/public/mock/plugin_dependencies.ts +++ b/x-pack/platform/plugins/shared/fleet/public/mock/plugin_dependencies.ts @@ -6,6 +6,7 @@ */ import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; +import { kqlPluginMock } from '@kbn/kql/public/mocks'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { licensingMock } from '@kbn/licensing-plugin/public/mocks'; @@ -33,6 +34,7 @@ export const createStartDepsMock = () => { data: dataPluginMock.createStartContract(), dataViews: dataViewPluginMocks.createStartContract(), unifiedSearch: unifiedSearchPluginMock.createStartContract(), + kql: kqlPluginMock.createStartContract(), navigation: navigationPluginMock.createStartContract(), customIntegrations: customIntegrationsMock.createStart(), share: sharePluginMock.createStartContract(), diff --git a/x-pack/platform/plugins/shared/fleet/public/plugin.ts b/x-pack/platform/plugins/shared/fleet/public/plugin.ts index 9370e67436776..e1b5ccea27823 100644 --- a/x-pack/platform/plugins/shared/fleet/public/plugin.ts +++ b/x-pack/platform/plugins/shared/fleet/public/plugin.ts @@ -47,6 +47,7 @@ import type { GlobalSearchPluginSetup } from '@kbn/global-search-plugin/public'; import type { SendRequestResponse } from '@kbn/es-ui-shared-plugin/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import type { KqlPluginStart } from '@kbn/kql/public'; import type { DashboardStart } from '@kbn/dashboard-plugin/public'; @@ -134,6 +135,7 @@ export interface FleetStartDeps { dashboard: DashboardStart; dataViews: DataViewsPublicPluginStart; unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; navigation: NavigationPublicPluginStart; customIntegrations: CustomIntegrationsStart; share: SharePluginStart; @@ -148,6 +150,7 @@ export interface FleetStartServices extends CoreStart, Exclude { }; }); -jest.mock('@kbn/unified-search-plugin/public', () => ({ +jest.mock('@kbn/kql/public', () => ({ QueryStringInput: () => 'QueryStringInput', })); diff --git a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/formula/editor/formula_editor.tsx b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/formula/editor/formula_editor.tsx index 98d59e3854a43..9d5207c18c04c 100644 --- a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/formula/editor/formula_editor.tsx +++ b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/formula/editor/formula_editor.tsx @@ -103,7 +103,7 @@ export function FormulaEditor({ columnId, indexPattern, operationDefinitionMap, - unifiedSearch, + kql, dataViews, toggleFullscreen, isFullscreen, @@ -457,7 +457,7 @@ export function FormulaEditor({ context, indexPattern, operationDefinitionMap: visibleOperationsMap, - unifiedSearch, + kql, dataViews, dateHistogramInterval: baseIntervalRef.current, timefilter: data.query.timefilter.timefilter, @@ -470,7 +470,7 @@ export function FormulaEditor({ context, indexPattern, operationDefinitionMap: visibleOperationsMap, - unifiedSearch, + kql, dataViews, dateHistogramInterval: baseIntervalRef.current, timefilter: data.query.timefilter.timefilter, @@ -489,7 +489,7 @@ export function FormulaEditor({ ), }; }, - [indexPattern, visibleOperationsMap, unifiedSearch, dataViews, data.query.timefilter.timefilter] + [indexPattern, visibleOperationsMap, kql, dataViews, data.query.timefilter.timefilter] ); const provideSignatureHelp = useCallback( diff --git a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/formula/editor/math_completion.test.ts b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/formula/editor/math_completion.test.ts index f4e6fd6b01056..40054ff7d1a57 100644 --- a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/formula/editor/math_completion.test.ts +++ b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/formula/editor/math_completion.test.ts @@ -8,7 +8,7 @@ import moment from 'moment'; import { parse } from '@kbn/tinymath'; import { monaco } from '@kbn/monaco'; -import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; +import { kqlPluginMock } from '@kbn/kql/public/mocks'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { tinymathFunctions } from '@kbn/lens-formula-docs'; import type { TimefilterContract } from '@kbn/data-plugin/public'; @@ -229,7 +229,7 @@ The total number of documents. When you provide a field, the total number of fie }, indexPattern: createMockedIndexPattern(), operationDefinitionMap, - unifiedSearch: unifiedSearchPluginMock.createStartContract(), + kql: kqlPluginMock.createStartContract(), dataViews: dataViewPluginMocks.createStartContract(), timefilter: { getTime: () => ({ from: '2022-11-01T00:00:00.000Z', to: '2022-11-03T00:00:00.000Z' }), diff --git a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/formula/editor/math_completion.ts b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/formula/editor/math_completion.ts index 6e98079d8ef5b..1f81abe8bf484 100644 --- a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/formula/editor/math_completion.ts +++ b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/formula/editor/math_completion.ts @@ -16,10 +16,7 @@ import type { TinymathNamedArgument, } from '@kbn/tinymath'; import { parse } from '@kbn/tinymath'; -import type { - UnifiedSearchPublicPluginStart, - QuerySuggestion, -} from '@kbn/unified-search-plugin/public'; +import type { KqlPluginStart, QuerySuggestion } from '@kbn/kql/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import { parseTimeShift } from '@kbn/data-plugin/common'; import { tinymathFunctions } from '@kbn/lens-formula-docs'; @@ -148,7 +145,7 @@ export async function suggest({ indexPattern, operationDefinitionMap, dataViews, - unifiedSearch, + kql, dateHistogramInterval, timefilter, }: { @@ -157,7 +154,7 @@ export async function suggest({ context: monaco.languages.CompletionContext; indexPattern: IndexPattern; operationDefinitionMap: Record; - unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; dataViews: DataViewsPublicPluginStart; dateHistogramInterval?: number; timefilter: TimefilterContract; @@ -181,7 +178,7 @@ export async function suggest({ if (tokenInfo?.parent && (context.triggerCharacter === '=' || isNamedArgument)) { return await getNamedArgumentSuggestions({ ast: tokenAst as TinymathNamedArgument, - unifiedSearch, + kql, dataViews, indexPattern, dateHistogramInterval, @@ -382,7 +379,7 @@ function computeAbsSuggestion(dateRange: DateRange, prefix: string, value: strin export async function getNamedArgumentSuggestions({ ast, - unifiedSearch, + kql, dataViews, indexPattern, dateHistogramInterval, @@ -390,7 +387,7 @@ export async function getNamedArgumentSuggestions({ }: { ast: TinymathNamedArgument; indexPattern: IndexPattern; - unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; dataViews: DataViewsPublicPluginStart; dateHistogramInterval?: number; dateRange: DateRange; @@ -434,14 +431,14 @@ export async function getNamedArgumentSuggestions({ if (ast.name !== 'kql' && ast.name !== 'lucene') { return { list: [], type: SUGGESTION_TYPE.KQL }; } - if (!unifiedSearch.autocomplete.hasQuerySuggestions(ast.name === 'kql' ? 'kuery' : 'lucene')) { + if (!kql.autocomplete.hasQuerySuggestions(ast.name === 'kql' ? 'kuery' : 'lucene')) { return { list: [], type: SUGGESTION_TYPE.KQL }; } const query = ast.value.split(MARKER)[0]; const position = ast.value.indexOf(MARKER) + 1; - const suggestions = await unifiedSearch.autocomplete.getQuerySuggestions({ + const suggestions = await kql.autocomplete.getQuerySuggestions({ language: ast.name === 'kql' ? 'kuery' : 'lucene', query, selectionStart: position, diff --git a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/index.ts b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/index.ts index ba79059049080..2ea0748cba37b 100644 --- a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/index.ts +++ b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/index.ts @@ -13,7 +13,6 @@ import type { } from '@kbn/expressions-plugin/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; -import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { EuiThemeComputed } from '@elastic/eui'; import type { @@ -31,6 +30,7 @@ import type { ParamEditorCustomProps, UserMessage, } from '@kbn/lens-common'; +import type { KqlPluginStart } from '@kbn/kql/public'; import { termsOperation } from './terms'; import { filtersOperation } from './filters'; import { cardinalityOperation } from './cardinality'; @@ -159,7 +159,7 @@ export interface ParamEditorProps< dateRange: DateRange; data: DataPublicPluginStart; fieldFormats: FieldFormatsStart; - unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; dataViews: DataViewsPublicPluginStart; activeData?: FormBasedDimensionEditorProps['activeData']; operationDefinitionMap: Record; diff --git a/x-pack/platform/plugins/shared/lens/public/plugin.ts b/x-pack/platform/plugins/shared/lens/public/plugin.ts index 0763a7b13bef2..1964886ce8223 100644 --- a/x-pack/platform/plugins/shared/lens/public/plugin.ts +++ b/x-pack/platform/plugins/shared/lens/public/plugin.ts @@ -78,6 +78,7 @@ import type { SavedObjectTaggingPluginStart } from '@kbn/saved-objects-tagging-p import type { ServerlessPluginStart } from '@kbn/serverless/public'; import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import type { KqlPluginStart } from '@kbn/kql/public'; import type { CPSPluginStart } from '@kbn/cps/public'; import type { EditorFrameService as EditorFrameServiceType } from './editor_frame_service'; import type { @@ -161,6 +162,7 @@ export interface LensPluginSetupDependencies { export interface LensPluginStartDependencies { data: DataPublicPluginStart; unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; dataViews: DataViewsPublicPluginStart; fieldFormats: FieldFormatsStart; expressions: ExpressionsStart; diff --git a/x-pack/platform/plugins/shared/maps/public/kibana_services.ts b/x-pack/platform/plugins/shared/maps/public/kibana_services.ts index bdb883a1fc888..dd99926b6d589 100644 --- a/x-pack/platform/plugins/shared/maps/public/kibana_services.ts +++ b/x-pack/platform/plugins/shared/maps/public/kibana_services.ts @@ -62,7 +62,7 @@ export const setSpaceId = (_spaceId: string) => { export const getIndexNameFormComponent = () => pluginsStart.fileUpload.IndexNameFormComponent; export const getFileUploadComponent = () => pluginsStart.fileUpload.FileUploadComponent; export const getIndexPatternService = () => pluginsStart.data.dataViews; -export const getAutocompleteService = () => pluginsStart.unifiedSearch.autocomplete; +export const getAutocompleteService = () => pluginsStart.kql.autocomplete; export const getInspector = () => pluginsStart.inspector; export const getFileUpload = () => pluginsStart.fileUpload; export const getUiSettings = () => coreStart.uiSettings; diff --git a/x-pack/platform/plugins/shared/maps/public/plugin.ts b/x-pack/platform/plugins/shared/maps/public/plugin.ts index 6a66f959a798d..b854b1537dc4a 100644 --- a/x-pack/platform/plugins/shared/maps/public/plugin.ts +++ b/x-pack/platform/plugins/shared/maps/public/plugin.ts @@ -45,6 +45,7 @@ import type { import type { ServerlessPluginStart } from '@kbn/serverless/public'; import type { CPSPluginStart } from '@kbn/cps/public'; +import type { KqlPluginStart } from '@kbn/kql/public'; import { createRegionMapFn, GEOHASH_GRID, @@ -104,6 +105,7 @@ export interface MapsPluginStartDependencies { cps?: CPSPluginStart; data: DataPublicPluginStart; unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; embeddable: EmbeddableStart; embeddableEnhanced?: EmbeddableEnhancedPluginStart; fieldFormats: FieldFormatsStart; diff --git a/x-pack/platform/plugins/shared/ml/public/application/contexts/kibana/kibana_context.ts b/x-pack/platform/plugins/shared/ml/public/application/contexts/kibana/kibana_context.ts index f491a69562887..40edbc3b6c3a6 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/contexts/kibana/kibana_context.ts +++ b/x-pack/platform/plugins/shared/ml/public/application/contexts/kibana/kibana_context.ts @@ -35,6 +35,7 @@ import type { FieldFormatsRegistry } from '@kbn/field-formats-plugin/common'; import type { LicensingPluginStart } from '@kbn/licensing-plugin/public'; import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public'; import type { FileUploadPluginStart } from '@kbn/file-upload-plugin/public'; +import type { KqlPluginStart } from '@kbn/kql/public'; import type { MlServicesContext } from '../../app'; interface StartPlugins { @@ -62,6 +63,7 @@ interface StartPlugins { triggersActionsUi?: TriggersAndActionsUIPublicPluginStart; uiActions: UiActionsStart; unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; usageCollection?: UsageCollectionSetup; fieldsMetadata: FieldsMetadataPublicStart; fileUpload: FileUploadPluginStart; diff --git a/x-pack/platform/plugins/shared/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx b/x-pack/platform/plugins/shared/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx index de53d52e48665..89b6053a8bd9b 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_query_bar/exploration_query_bar.tsx @@ -54,9 +54,7 @@ export const ExplorationQueryBar: FC = ({ const { services } = useMlKibana(); const { - unifiedSearch: { - ui: { QueryStringInput }, - }, + kql: { QueryStringInput }, } = services; const searchChangeHandler = (q: Query) => setSearchInput(q); diff --git a/x-pack/platform/plugins/shared/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx b/x-pack/platform/plugins/shared/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx index fcfcbd7d43690..93a4041b7a7f0 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/explorer/components/explorer_query_bar/explorer_query_bar.tsx @@ -111,9 +111,7 @@ export const ExplorerQueryBar: FC = ({ const { anomalyExplorerCommonStateService } = useAnomalyExplorerContext(); const { services } = useMlKibana(); const { - unifiedSearch: { - ui: { QueryStringInput }, - }, + kql: { QueryStringInput }, } = services; // The internal state of the input query bar updated on every key stroke. diff --git a/x-pack/platform/plugins/shared/stack_alerts/public/rule_types/geo_containment/rule_form/query_input.tsx b/x-pack/platform/plugins/shared/stack_alerts/public/rule_types/geo_containment/rule_form/query_input.tsx index b87047eece035..e367a60185624 100644 --- a/x-pack/platform/plugins/shared/stack_alerts/public/rule_types/geo_containment/rule_form/query_input.tsx +++ b/x-pack/platform/plugins/shared/stack_alerts/public/rule_types/geo_containment/rule_form/query_input.tsx @@ -38,9 +38,7 @@ interface Props { export const QueryInput = (props: Props) => { const { - unifiedSearch: { - ui: { QueryStringInput }, - }, + kql: { QueryStringInput }, } = useKibana<{ data: DataPublicPluginStart; dataViews: DataViewsPublicPluginStart; diff --git a/x-pack/solutions/observability/plugins/apm/public/application/index.tsx b/x-pack/solutions/observability/plugins/apm/public/application/index.tsx index b3aa9f0fcffe8..529e4766c42b6 100644 --- a/x-pack/solutions/observability/plugins/apm/public/application/index.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/application/index.tsx @@ -56,6 +56,7 @@ export const renderApp = ({ observabilityRuleTypeRegistry, dataViews: pluginsStart.dataViews, unifiedSearch: pluginsStart.unifiedSearch, + kql: pluginsStart.kql, lens: pluginsStart.lens, uiActions: pluginsStart.uiActions, observabilityAIAssistant: pluginsStart.observabilityAIAssistant, diff --git a/x-pack/solutions/observability/plugins/apm/public/components/app/trace_explorer/trace_search_box/index.tsx b/x-pack/solutions/observability/plugins/apm/public/components/app/trace_explorer/trace_search_box/index.tsx index 87d129a348600..f241b69a066fb 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/app/trace_explorer/trace_search_box/index.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/app/trace_explorer/trace_search_box/index.tsx @@ -40,9 +40,7 @@ const options: EuiSelectOption[] = [ export function TraceSearchBox({ query, onQueryChange, onQueryCommit, loading }: Props) { const { - unifiedSearch: { - ui: { QueryStringInput }, - }, + kql: { QueryStringInput }, } = useApmPluginContext(); const { dataView } = useAdHocApmDataView(); diff --git a/x-pack/solutions/observability/plugins/apm/public/components/shared/kuery_bar/index.tsx b/x-pack/solutions/observability/plugins/apm/public/components/shared/kuery_bar/index.tsx index 87bebfb3e9a44..a18c679629d36 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/shared/kuery_bar/index.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/shared/kuery_bar/index.tsx @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import { uniqueId } from 'lodash'; import React, { useState } from 'react'; import { useHistory, useLocation } from 'react-router-dom'; -import type { QuerySuggestion } from '@kbn/unified-search-plugin/public'; +import type { QuerySuggestion } from '@kbn/kql/public'; import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; import type { DataView } from '@kbn/data-views-plugin/public'; import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; @@ -56,7 +56,7 @@ export function KueryBar(props: { }); const { urlParams } = useLegacyUrlParams(); const location = useLocation(); - const { unifiedSearch } = useApmPluginContext().plugins; + const { kql } = useApmPluginContext().plugins; let currentRequestCheck; @@ -102,7 +102,7 @@ export function KueryBar(props: { try { const suggestions = - (await unifiedSearch.autocomplete.getQuerySuggestions({ + (await kql.autocomplete.getQuerySuggestions({ language: 'kuery', indexPatterns: [dataView], boolFilter: diff --git a/x-pack/solutions/observability/plugins/apm/public/context/apm_plugin/apm_plugin_context.tsx b/x-pack/solutions/observability/plugins/apm/public/context/apm_plugin/apm_plugin_context.tsx index 84f407ea5bc9d..1acd0ada1eea4 100644 --- a/x-pack/solutions/observability/plugins/apm/public/context/apm_plugin/apm_plugin_context.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/context/apm_plugin/apm_plugin_context.tsx @@ -22,6 +22,7 @@ import type { SharePluginSetup } from '@kbn/share-plugin/public'; import type { OnechatPluginStart } from '@kbn/onechat-plugin/public'; import type { LicensingPluginStart } from '@kbn/licensing-plugin/public'; import type { ObservabilityAgentBuilderPluginPublicStart } from '@kbn/observability-agent-builder-plugin/public'; +import type { KqlPluginStart } from '@kbn/kql/public'; import type { ApmPluginSetupDeps } from '../../plugin'; import type { ConfigSchema } from '../..'; import type { KibanaEnvContext } from '../kibana_environment_context/kibana_environment_context'; @@ -38,6 +39,7 @@ export interface ApmPluginContextValue { dataViews: DataViewsPublicPluginStart; data: DataPublicPluginStart; unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; uiActions: UiActionsStart; observabilityAIAssistant?: ObservabilityAIAssistantPublicStart; onechat?: OnechatPluginStart; diff --git a/x-pack/solutions/observability/plugins/apm/public/embeddable/embeddable_context.tsx b/x-pack/solutions/observability/plugins/apm/public/embeddable/embeddable_context.tsx index dd38e9cf7e5f0..bf1d36ceb78fe 100644 --- a/x-pack/solutions/observability/plugins/apm/public/embeddable/embeddable_context.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/embeddable/embeddable_context.tsx @@ -39,6 +39,7 @@ export function ApmEmbeddableContext({ observabilityShared: deps.pluginsStart.observabilityShared, dataViews: deps.pluginsStart.dataViews, unifiedSearch: deps.pluginsStart.unifiedSearch, + kql: deps.pluginsStart.kql, lens: deps.pluginsStart.lens, uiActions: deps.pluginsStart.uiActions, observabilityAIAssistant: deps.pluginsStart.observabilityAIAssistant, diff --git a/x-pack/solutions/observability/plugins/apm/public/plugin.ts b/x-pack/solutions/observability/plugins/apm/public/plugin.ts index f4eb497c26270..3fb6ee2c7f73c 100644 --- a/x-pack/solutions/observability/plugins/apm/public/plugin.ts +++ b/x-pack/solutions/observability/plugins/apm/public/plugin.ts @@ -84,6 +84,7 @@ import type { DiscoverSharedPublicSetup, DiscoverSharedPublicStart, } from '@kbn/discover-shared-plugin/public'; +import type { KqlPluginSetup, KqlPluginStart } from '@kbn/kql/public'; import type { ConfigSchema } from '.'; import { registerApmRuleTypes } from './components/alerting/rule_types/register_apm_rule_types'; import { registerEmbeddables } from './embeddable/register_embeddables'; @@ -109,6 +110,7 @@ export interface ApmPluginSetupDeps { embeddable: EmbeddableSetup; exploratoryView: ExploratoryViewPublicSetup; unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginSetup; features: FeaturesPluginSetup; home?: HomePublicPluginSetup; licenseManagement?: LicenseManagementUIPluginSetup; @@ -154,6 +156,7 @@ export interface ApmPluginStartDeps { serverless?: ServerlessPluginStart; dataViews: DataViewsPublicPluginStart; unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; storage: IStorageWrapper; lens: LensPublicStart; uiActions: UiActionsStart; diff --git a/x-pack/solutions/observability/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/log_rate_analysis.tsx b/x-pack/solutions/observability/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/log_rate_analysis.tsx index afac7c8539587..121ec2b9d3a11 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/log_rate_analysis.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/log_rate_analysis.tsx @@ -201,6 +201,7 @@ export function LogRateAnalysis({ alert, dataView, services }: AlertDetailsLogRa 'storage', 'uiSettings', 'unifiedSearch', + 'kql', 'theme', 'userProfile', 'lens', diff --git a/x-pack/solutions/observability/plugins/observability/public/components/custom_threshold/types.ts b/x-pack/solutions/observability/plugins/observability/public/components/custom_threshold/types.ts index 8982ff93e5497..7b3b39c87b6c1 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/custom_threshold/types.ts +++ b/x-pack/solutions/observability/plugins/observability/public/components/custom_threshold/types.ts @@ -25,6 +25,7 @@ import type { import type { UiActionsStart } from '@kbn/ui-actions-plugin/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; +import type { KqlPluginStart } from '@kbn/kql/public'; import type { Group } from '../../../common/typings'; import { aggType, @@ -77,6 +78,7 @@ export interface InfraClientStartDeps { triggersActionsUi: TriggersAndActionsUIPublicPluginStart; uiActions: UiActionsStart; unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; usageCollection: UsageCollectionStart; } diff --git a/x-pack/solutions/observability/plugins/observability/public/components/rule_kql_filter/autocomplete_field/autocomplete_field.tsx b/x-pack/solutions/observability/plugins/observability/public/components/rule_kql_filter/autocomplete_field/autocomplete_field.tsx index 70e7c26241c64..d53dfabf6f023 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/rule_kql_filter/autocomplete_field/autocomplete_field.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/components/rule_kql_filter/autocomplete_field/autocomplete_field.tsx @@ -8,7 +8,7 @@ import type { EuiThemeComputed } from '@elastic/eui'; import { EuiFieldSearch, EuiOutsideClickDetector, EuiPanel } from '@elastic/eui'; import React from 'react'; -import type { QuerySuggestion } from '@kbn/unified-search-plugin/public'; +import type { QuerySuggestion } from '@kbn/kql/public'; import { css } from '@emotion/react'; import { SuggestionItem } from './suggestion_item'; diff --git a/x-pack/solutions/observability/plugins/observability/public/components/rule_kql_filter/autocomplete_field/suggestion_item.tsx b/x-pack/solutions/observability/plugins/observability/public/components/rule_kql_filter/autocomplete_field/suggestion_item.tsx index faefb746555df..f73760f7a7a4d 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/rule_kql_filter/autocomplete_field/suggestion_item.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/components/rule_kql_filter/autocomplete_field/suggestion_item.tsx @@ -8,8 +8,8 @@ import React from 'react'; import type { EuiThemeComputed } from '@elastic/eui'; import { EuiIcon, euiPaletteColorBlind, useEuiTheme } from '@elastic/eui'; -import type { QuerySuggestion } from '@kbn/unified-search-plugin/public'; -import { QuerySuggestionTypes } from '@kbn/unified-search-plugin/public'; +import type { QuerySuggestion } from '@kbn/kql/public'; +import { QuerySuggestionTypes } from '@kbn/kql/public'; import { transparentize } from 'polished'; import { css } from '@emotion/react'; diff --git a/x-pack/solutions/observability/plugins/observability/public/components/rule_kql_filter/kuery_bar.tsx b/x-pack/solutions/observability/plugins/observability/public/components/rule_kql_filter/kuery_bar.tsx index c6e0c725bdcb8..07c114632dc75 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/rule_kql_filter/kuery_bar.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/components/rule_kql_filter/kuery_bar.tsx @@ -8,7 +8,7 @@ import { fromKueryExpression } from '@kbn/es-query'; import React, { useEffect, useState } from 'react'; import type { DataViewBase } from '@kbn/es-query'; -import type { QuerySuggestion } from '@kbn/unified-search-plugin/public'; +import type { QuerySuggestion } from '@kbn/kql/public'; import { useEuiTheme } from '@elastic/eui'; import { WithKueryAutocompletion } from './with_kuery_autocompletion'; diff --git a/x-pack/solutions/observability/plugins/observability/public/components/rule_kql_filter/with_kuery_autocompletion.tsx b/x-pack/solutions/observability/plugins/observability/public/components/rule_kql_filter/with_kuery_autocompletion.tsx index 3165c277d81fd..f497f90271045 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/rule_kql_filter/with_kuery_autocompletion.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/components/rule_kql_filter/with_kuery_autocompletion.tsx @@ -10,7 +10,7 @@ import type { DataViewBase } from '@kbn/es-query'; import type { KibanaReactContextValue, KibanaServices } from '@kbn/kibana-react-plugin/public'; import { withKibana } from '@kbn/kibana-react-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; -import type { QuerySuggestion } from '@kbn/unified-search-plugin/public'; +import type { QuerySuggestion } from '@kbn/kql/public'; import type { InfraClientStartDeps, RendererFunction } from '../custom_threshold/types'; export interface WithKueryAutocompletionLifecycleProps { @@ -61,7 +61,7 @@ class WithKueryAutocompletionComponent extends React.Component< const { indexPattern } = this.props; const language = 'kuery'; const hasQuerySuggestions = - this.props.kibana.services.unifiedSearch.autocomplete.hasQuerySuggestions(language); + this.props.kibana.services.kql.autocomplete.hasQuerySuggestions(language); if (!hasQuerySuggestions) { return; @@ -76,7 +76,7 @@ class WithKueryAutocompletionComponent extends React.Component< }); const suggestions = - (await this.props.kibana.services.unifiedSearch.autocomplete.getQuerySuggestions({ + (await this.props.kibana.services.kql.autocomplete.getQuerySuggestions({ language, query: expression, selectionStart: cursorPosition, diff --git a/x-pack/solutions/observability/plugins/observability/public/plugin.mock.tsx b/x-pack/solutions/observability/plugins/observability/public/plugin.mock.tsx index 900942caf36c7..09245c8a04358 100644 --- a/x-pack/solutions/observability/plugins/observability/public/plugin.mock.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/plugin.mock.tsx @@ -15,6 +15,7 @@ import { observabilityAIAssistantPluginMock } from '@kbn/observability-ai-assist import { sharePluginMock } from '@kbn/share-plugin/public/mocks'; import { spacesPluginMock } from '@kbn/spaces-plugin/public/mocks'; import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; +import { kqlPluginMock } from '@kbn/kql/public/mocks'; import { lensPluginMock } from '@kbn/lens-plugin/public/mocks'; import { logsDataAccessPluginMock } from '@kbn/logs-data-access-plugin/public/mocks'; @@ -97,6 +98,7 @@ export const observabilityPublicPluginsStartMock = { spaces: spacesPluginMock.createStartContract(), triggersActionsUi: triggersActionsUiStartMock.createStart(), unifiedSearch: unifiedSearchPluginMock.createStartContract(), + kql: kqlPluginMock.createStartContract(), }; }, }; diff --git a/x-pack/solutions/observability/plugins/observability/public/plugin.ts b/x-pack/solutions/observability/plugins/observability/public/plugin.ts index 3c217ba01dbaa..804f7841c3ed9 100644 --- a/x-pack/solutions/observability/plugins/observability/public/plugin.ts +++ b/x-pack/solutions/observability/plugins/observability/public/plugin.ts @@ -69,6 +69,7 @@ import type { } from '@kbn/triggers-actions-ui-plugin/public'; import type { UiActionsSetup, UiActionsStart } from '@kbn/ui-actions-plugin/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import type { KqlPluginStart } from '@kbn/kql/public'; import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; import type { StreamsPluginStart, StreamsPluginSetup } from '@kbn/streams-plugin/public'; import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public'; @@ -167,6 +168,7 @@ export interface ObservabilityPublicPluginsStart { triggersActionsUi: TriggersAndActionsUIPublicPluginStart; usageCollection: UsageCollectionSetup; unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; home?: HomePublicPluginStart; cloud?: CloudStart; aiops: AiopsPluginStart; diff --git a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/alerts/query_bar.tsx b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/alerts/query_bar.tsx index 6b0ecf8934893..9e4fdd2fdaef9 100644 --- a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/alerts/query_bar.tsx +++ b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/alerts/query_bar.tsx @@ -24,9 +24,7 @@ export function AlertSearchBar({ }) { const { data: { query }, - unifiedSearch: { - ui: { QueryStringInput }, - }, + kql: { QueryStringInput }, } = useKibana().services; const dataView = useSyntheticsDataView(); diff --git a/x-pack/solutions/observability/plugins/uptime/public/legacy_uptime/app/uptime_app.tsx b/x-pack/solutions/observability/plugins/uptime/public/legacy_uptime/app/uptime_app.tsx index e7373b2d8e422..9f4b94aa4d1ee 100644 --- a/x-pack/solutions/observability/plugins/uptime/public/legacy_uptime/app/uptime_app.tsx +++ b/x-pack/solutions/observability/plugins/uptime/public/legacy_uptime/app/uptime_app.tsx @@ -106,6 +106,7 @@ const Application = (props: UptimeAppProps) => { storage, data: startPlugins.data, unifiedSearch: startPlugins.unifiedSearch, + kql: startPlugins.kql, fleet: startPlugins.fleet, inspector: startPlugins.inspector, triggersActionsUi: startPlugins.triggersActionsUi, diff --git a/x-pack/solutions/observability/plugins/uptime/public/legacy_uptime/components/overview/alerts/alert_query_bar/query_bar.tsx b/x-pack/solutions/observability/plugins/uptime/public/legacy_uptime/components/overview/alerts/alert_query_bar/query_bar.tsx index fff3c0d6665ee..b723ad8c52be2 100644 --- a/x-pack/solutions/observability/plugins/uptime/public/legacy_uptime/components/overview/alerts/alert_query_bar/query_bar.tsx +++ b/x-pack/solutions/observability/plugins/uptime/public/legacy_uptime/components/overview/alerts/alert_query_bar/query_bar.tsx @@ -24,9 +24,7 @@ export const AlertQueryBar = ({ query = '', onChange }: Props) => { const { appName, - unifiedSearch: { - ui: { QueryStringInput }, - }, + kql: { QueryStringInput }, } = services; const [inputVal, setInputVal] = useState(query); diff --git a/x-pack/solutions/observability/plugins/uptime/public/legacy_uptime/components/overview/query_bar/query_bar.tsx b/x-pack/solutions/observability/plugins/uptime/public/legacy_uptime/components/overview/query_bar/query_bar.tsx index 14dc53456a403..c2258b816b0c6 100644 --- a/x-pack/solutions/observability/plugins/uptime/public/legacy_uptime/components/overview/query_bar/query_bar.tsx +++ b/x-pack/solutions/observability/plugins/uptime/public/legacy_uptime/components/overview/query_bar/query_bar.tsx @@ -37,9 +37,7 @@ export const QueryBar = () => { const { appName, - unifiedSearch: { - ui: { QueryStringInput }, - }, + kql: { QueryStringInput }, } = services; const { query, setQuery, submitImmediately } = useQueryBar(); diff --git a/x-pack/solutions/observability/plugins/uptime/public/legacy_uptime/lib/alert_types/lazy_wrapper/monitor_status.tsx b/x-pack/solutions/observability/plugins/uptime/public/legacy_uptime/lib/alert_types/lazy_wrapper/monitor_status.tsx index 2ba126268cd9c..8a9062d6dbf6e 100644 --- a/x-pack/solutions/observability/plugins/uptime/public/legacy_uptime/lib/alert_types/lazy_wrapper/monitor_status.tsx +++ b/x-pack/solutions/observability/plugins/uptime/public/legacy_uptime/lib/alert_types/lazy_wrapper/monitor_status.tsx @@ -31,7 +31,7 @@ export default function MonitorStatusAlert({ core, plugins, params, stackVersion diff --git a/x-pack/solutions/observability/plugins/uptime/public/plugin.ts b/x-pack/solutions/observability/plugins/uptime/public/plugin.ts index 4387e31bdb7d3..16f41837ab601 100644 --- a/x-pack/solutions/observability/plugins/uptime/public/plugin.ts +++ b/x-pack/solutions/observability/plugins/uptime/public/plugin.ts @@ -56,6 +56,7 @@ import type { ObservabilityAIAssistantPublicSetup, } from '@kbn/observability-ai-assistant-plugin/public'; import type { ChartsPluginStart } from '@kbn/charts-plugin/public'; +import type { KqlPluginStart } from '@kbn/kql/public'; import { PLUGIN } from '../common/constants/plugin'; import type { UptimeConfig } from '../common/config'; import { @@ -87,6 +88,7 @@ export interface ClientPluginsStart { fleet: FleetStart; data: DataPublicPluginStart; unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; discover: DiscoverStart; inspector: InspectorPluginStart; embeddable: EmbeddableStart; diff --git a/x-pack/solutions/security/packages/kbn-securitysolution-autocomplete/src/field_value_match/index.tsx b/x-pack/solutions/security/packages/kbn-securitysolution-autocomplete/src/field_value_match/index.tsx index 37670ceb0a2b8..e2901fde29c24 100644 --- a/x-pack/solutions/security/packages/kbn-securitysolution-autocomplete/src/field_value_match/index.tsx +++ b/x-pack/solutions/security/packages/kbn-securitysolution-autocomplete/src/field_value_match/index.tsx @@ -15,7 +15,7 @@ import { uniq } from 'lodash'; import { ListOperatorTypeEnum as OperatorTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; // TODO: I have to use any here for now, but once this is available below, we should use the correct types, https://github.com/elastic/kibana/issues/100715 -// import { AutocompleteStart } from '../../../../../../../../../../src/plugins/unified_search/public'; +// import { AutocompleteStart } from '../../../../../../../../../../src/plugins/kql/public'; // eslint-disable-next-line @typescript-eslint/no-explicit-any type AutocompleteStart = any; diff --git a/x-pack/solutions/security/packages/kbn-securitysolution-autocomplete/src/field_value_match_any/index.tsx b/x-pack/solutions/security/packages/kbn-securitysolution-autocomplete/src/field_value_match_any/index.tsx index 0ea3d9e19516e..3f552c7154157 100644 --- a/x-pack/solutions/security/packages/kbn-securitysolution-autocomplete/src/field_value_match_any/index.tsx +++ b/x-pack/solutions/security/packages/kbn-securitysolution-autocomplete/src/field_value_match_any/index.tsx @@ -13,7 +13,7 @@ import { ListOperatorTypeEnum as OperatorTypeEnum } from '@kbn/securitysolution- import type { DataViewBase, DataViewFieldBase } from '@kbn/es-query'; // TODO: I have to use any here for now, but once this is available below, we should use the correct types, https://github.com/elastic/kibana/issues/100715 -// import { AutocompleteStart } from '../../../../../../../../../../src/plugins/unified_search/public'; +// import { AutocompleteStart } from '../../../../../../../../../../src/plugins/kql/public'; // eslint-disable-next-line @typescript-eslint/no-explicit-any type AutocompleteStart = any; diff --git a/x-pack/solutions/security/packages/kbn-securitysolution-autocomplete/src/field_value_wildcard/index.tsx b/x-pack/solutions/security/packages/kbn-securitysolution-autocomplete/src/field_value_wildcard/index.tsx index 9a144dd6ace02..bc0c2ad34e618 100644 --- a/x-pack/solutions/security/packages/kbn-securitysolution-autocomplete/src/field_value_wildcard/index.tsx +++ b/x-pack/solutions/security/packages/kbn-securitysolution-autocomplete/src/field_value_wildcard/index.tsx @@ -15,7 +15,7 @@ import { uniq } from 'lodash'; import { ListOperatorTypeEnum as OperatorTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; // TODO: I have to use any here for now, but once this is available below, we should use the correct types, https://github.com/elastic/kibana/issues/100715 -// import { AutocompleteStart } from '../../../../../../../../../../src/plugins/unified_search/public'; +// import { AutocompleteStart } from '../../../../../../../../../../src/plugins/kql/public'; // eslint-disable-next-line @typescript-eslint/no-explicit-any type AutocompleteStart = any; diff --git a/x-pack/solutions/security/packages/kbn-securitysolution-autocomplete/src/hooks/use_field_value_autocomplete/index.ts b/x-pack/solutions/security/packages/kbn-securitysolution-autocomplete/src/hooks/use_field_value_autocomplete/index.ts index 88303a9983f73..bf046627a04e8 100644 --- a/x-pack/solutions/security/packages/kbn-securitysolution-autocomplete/src/hooks/use_field_value_autocomplete/index.ts +++ b/x-pack/solutions/security/packages/kbn-securitysolution-autocomplete/src/hooks/use_field_value_autocomplete/index.ts @@ -12,7 +12,7 @@ import type { DataViewBase, DataViewFieldBase } from '@kbn/es-query'; import { getDataViewFieldSubtypeNested } from '@kbn/es-query'; // TODO: I have to use any here for now, but once this is available below, we should use the correct types, https://github.com/elastic/kibana/issues/100715 -// import { AutocompleteStart } from '@kbn/unified-search-plugin/public'; +// import { AutocompleteStart } from '@kbn/kql/public'; // eslint-disable-next-line @typescript-eslint/no-explicit-any type AutocompleteStart = any; diff --git a/x-pack/solutions/security/plugins/lists/public/exceptions/components/builder/builder.stories.tsx b/x-pack/solutions/security/plugins/lists/public/exceptions/components/builder/builder.stories.tsx index ead435ca0b699..92a3f6b04f305 100644 --- a/x-pack/solutions/security/plugins/lists/public/exceptions/components/builder/builder.stories.tsx +++ b/x-pack/solutions/security/plugins/lists/public/exceptions/components/builder/builder.stories.tsx @@ -8,7 +8,7 @@ import React from 'react'; import type { Decorator } from '@storybook/react'; import type { HttpStart } from '@kbn/core/public'; -import type { AutocompleteStart } from '@kbn/unified-search-plugin/public'; +import type { AutocompleteStart } from '@kbn/kql/public'; import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import { fields, getField } from '@kbn/data-plugin/common/mocks'; diff --git a/x-pack/solutions/security/plugins/lists/public/exceptions/components/builder/entry_renderer.stories.tsx b/x-pack/solutions/security/plugins/lists/public/exceptions/components/builder/entry_renderer.stories.tsx index 263133d504326..275ecb39b5bf8 100644 --- a/x-pack/solutions/security/plugins/lists/public/exceptions/components/builder/entry_renderer.stories.tsx +++ b/x-pack/solutions/security/plugins/lists/public/exceptions/components/builder/entry_renderer.stories.tsx @@ -12,7 +12,7 @@ import { ListOperatorEnum as OperatorEnum, ListOperatorTypeEnum as OperatorTypeEnum, } from '@kbn/securitysolution-io-ts-list-types'; -import type { AutocompleteStart } from '@kbn/unified-search-plugin/public'; +import type { AutocompleteStart } from '@kbn/kql/public'; import { fields } from '@kbn/data-plugin/common/mocks'; import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; diff --git a/x-pack/solutions/security/plugins/lists/public/exceptions/components/builder/entry_renderer.test.tsx b/x-pack/solutions/security/plugins/lists/public/exceptions/components/builder/entry_renderer.test.tsx index df43f81b68bc0..d37ebb5618067 100644 --- a/x-pack/solutions/security/plugins/lists/public/exceptions/components/builder/entry_renderer.test.tsx +++ b/x-pack/solutions/security/plugins/lists/public/exceptions/components/builder/entry_renderer.test.tsx @@ -25,7 +25,7 @@ import { validatePotentialWildcardInput } from '@kbn/securitysolution-utils'; import { useFindListsBySize } from '@kbn/securitysolution-list-hooks'; import type { FieldSpec } from '@kbn/data-plugin/common'; import { fields, getField } from '@kbn/data-plugin/common/mocks'; -import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; +import { kqlPluginMock } from '@kbn/kql/public/mocks'; import { waitFor } from '@testing-library/react'; import type { ReactWrapper } from 'enzyme'; import { mount } from 'enzyme'; @@ -40,7 +40,7 @@ jest.mock('@kbn/securitysolution-list-hooks'); jest.mock('@kbn/securitysolution-utils'); const mockKibanaHttpService = coreMock.createStart().http; -const { autocomplete: autocompleteStartMock } = unifiedSearchPluginMock.createStartContract(); +const { autocomplete: autocompleteStartMock } = kqlPluginMock.createStartContract(); const mockResult = getFoundListsBySizeSchemaMock(); mockResult.largeLists = []; diff --git a/x-pack/solutions/security/plugins/lists/public/exceptions/components/builder/entry_renderer.tsx b/x-pack/solutions/security/plugins/lists/public/exceptions/components/builder/entry_renderer.tsx index 814d9584adf5a..3aa3bbb823b54 100644 --- a/x-pack/solutions/security/plugins/lists/public/exceptions/components/builder/entry_renderer.tsx +++ b/x-pack/solutions/security/plugins/lists/public/exceptions/components/builder/entry_renderer.tsx @@ -63,7 +63,7 @@ import { validatePotentialWildcardInput, } from '@kbn/securitysolution-utils'; import type { DataViewBase, DataViewFieldBase } from '@kbn/es-query'; -import type { AutocompleteStart } from '@kbn/unified-search-plugin/public'; +import type { AutocompleteStart } from '@kbn/kql/public'; import type { HttpStart } from '@kbn/core/public'; import { getEmptyValue } from '../../../common/empty_value'; diff --git a/x-pack/solutions/security/plugins/lists/public/exceptions/components/builder/exception_item_renderer.test.tsx b/x-pack/solutions/security/plugins/lists/public/exceptions/components/builder/exception_item_renderer.test.tsx index 66286232e0880..f4ed8e226b84a 100644 --- a/x-pack/solutions/security/plugins/lists/public/exceptions/components/builder/exception_item_renderer.test.tsx +++ b/x-pack/solutions/security/plugins/lists/public/exceptions/components/builder/exception_item_renderer.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { mount } from 'enzyme'; -import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; +import { kqlPluginMock } from '@kbn/kql/public/mocks'; import { fields } from '@kbn/data-plugin/common/mocks'; import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import { coreMock } from '@kbn/core/public/mocks'; @@ -20,7 +20,7 @@ import { MockedShowValueListModal } from '../__mock__/show_value_list_modal.mock import { BuilderExceptionListItemComponent } from './exception_item_renderer'; const mockKibanaHttpService = coreMock.createStart().http; -const { autocomplete: autocompleteStartMock } = unifiedSearchPluginMock.createStartContract(); +const { autocomplete: autocompleteStartMock } = kqlPluginMock.createStartContract(); describe('BuilderExceptionListItemComponent', () => { const getValueSuggestionsMock = jest.fn().mockResolvedValue(['value 1', 'value 2']); diff --git a/x-pack/solutions/security/plugins/lists/public/exceptions/components/builder/exception_item_renderer.tsx b/x-pack/solutions/security/plugins/lists/public/exceptions/components/builder/exception_item_renderer.tsx index 9a1f4e77c600d..1fbcab09cd5de 100644 --- a/x-pack/solutions/security/plugins/lists/public/exceptions/components/builder/exception_item_renderer.tsx +++ b/x-pack/solutions/security/plugins/lists/public/exceptions/components/builder/exception_item_renderer.tsx @@ -9,7 +9,7 @@ import type { ElementType } from 'react'; import React, { useCallback, useMemo } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import styled from 'styled-components'; -import type { AutocompleteStart } from '@kbn/unified-search-plugin/public'; +import type { AutocompleteStart } from '@kbn/kql/public'; import type { HttpStart } from '@kbn/core/public'; import type { ExceptionListType, OsTypeArray } from '@kbn/securitysolution-io-ts-list-types'; import type { diff --git a/x-pack/solutions/security/plugins/lists/public/exceptions/components/builder/exception_items_renderer.test.tsx b/x-pack/solutions/security/plugins/lists/public/exceptions/components/builder/exception_items_renderer.test.tsx index c09ab72cc2f77..346a7b718bf69 100644 --- a/x-pack/solutions/security/plugins/lists/public/exceptions/components/builder/exception_items_renderer.test.tsx +++ b/x-pack/solutions/security/plugins/lists/public/exceptions/components/builder/exception_items_renderer.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import type { ReactWrapper } from 'enzyme'; import { mount } from 'enzyme'; import { waitFor } from '@testing-library/react'; -import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; +import { kqlPluginMock } from '@kbn/kql/public/mocks'; import { coreMock } from '@kbn/core/public/mocks'; import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import { fields, getField } from '@kbn/data-plugin/common/mocks'; @@ -22,7 +22,7 @@ import { MockedShowValueListModal } from '../__mock__/show_value_list_modal.mock import { ExceptionBuilderComponent } from './exception_items_renderer'; const mockKibanaHttpService = coreMock.createStart().http; -const { autocomplete: autocompleteStartMock } = unifiedSearchPluginMock.createStartContract(); +const { autocomplete: autocompleteStartMock } = kqlPluginMock.createStartContract(); describe('ExceptionBuilderComponent', () => { let wrapper: ReactWrapper; diff --git a/x-pack/solutions/security/plugins/lists/public/exceptions/components/builder/exception_items_renderer.tsx b/x-pack/solutions/security/plugins/lists/public/exceptions/components/builder/exception_items_renderer.tsx index 7b3a8b2acc68c..b29b502ee593e 100644 --- a/x-pack/solutions/security/plugins/lists/public/exceptions/components/builder/exception_items_renderer.tsx +++ b/x-pack/solutions/security/plugins/lists/public/exceptions/components/builder/exception_items_renderer.tsx @@ -38,7 +38,7 @@ import { getNewExceptionItem, } from '@kbn/securitysolution-list-utils'; import type { DataViewBase } from '@kbn/es-query'; -import type { AutocompleteStart } from '@kbn/unified-search-plugin/public'; +import type { AutocompleteStart } from '@kbn/kql/public'; import deepEqual from 'fast-deep-equal'; import { AndOrBadge } from '../and_or_badge'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/lib/kibana/services.ts b/x-pack/solutions/security/plugins/security_solution/public/common/lib/kibana/services.ts index 9dcfef20629fa..b0358916071b4 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/lib/kibana/services.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/common/lib/kibana/services.ts @@ -9,7 +9,7 @@ import type { CoreStart } from '@kbn/core/public'; import type { StartPlugins } from '../../../types'; type GlobalServices = Pick & - Pick; + Pick; /** * This class is a singleton that holds references to core Kibana services. @@ -38,6 +38,7 @@ export class KibanaServices { application, data, unifiedSearch, + kql, kibanaBranch, kibanaVersion, buildFlavor, @@ -59,6 +60,7 @@ export class KibanaServices { http, uiSettings, unifiedSearch, + kql, notifications, expressions, savedSearch, diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/severity_mapping/severity_override.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/severity_mapping/severity_override.tsx index b62931b5d0753..32e59b040c1db 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/severity_mapping/severity_override.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/severity_mapping/severity_override.tsx @@ -176,7 +176,7 @@ function SeverityMappingRow({ { - const { http, unifiedSearch } = useKibana().services; + const { http, kql } = useKibana().services; const isEndpointException = useMemo( (): boolean => exceptionListType === ExceptionListTypeEnum.ENDPOINT, [exceptionListType] @@ -253,7 +253,7 @@ const ExceptionsConditionsComponent: React.FC { +jest.mock('@kbn/kql/server/autocomplete/terms_enum', () => { return { termsEnumSuggestions: jest.fn(), }; }); -jest.mock('@kbn/unified-search-plugin/server/autocomplete/terms_agg', () => { +jest.mock('@kbn/kql/server/autocomplete/terms_agg', () => { return { termsAggSuggestions: jest.fn(), }; diff --git a/x-pack/solutions/security/plugins/security_solution/server/endpoint/routes/suggestions/index.ts b/x-pack/solutions/security/plugins/security_solution/server/endpoint/routes/suggestions/index.ts index 9a0e9cd41088b..4c0b486c5bdd0 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/endpoint/routes/suggestions/index.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/endpoint/routes/suggestions/index.ts @@ -9,9 +9,9 @@ import type { Observable } from 'rxjs'; import { firstValueFrom } from 'rxjs'; import type { RequestHandler } from '@kbn/core/server'; import { getRequestAbortedSignal } from '@kbn/data-plugin/server'; -import type { ConfigSchema } from '@kbn/unified-search-plugin/server/config'; -import { termsEnumSuggestions } from '@kbn/unified-search-plugin/server/autocomplete/terms_enum'; -import { termsAggSuggestions } from '@kbn/unified-search-plugin/server/autocomplete/terms_agg'; +import type { ConfigSchema } from '@kbn/kql/server/config'; +import { termsEnumSuggestions } from '@kbn/kql/server/autocomplete/terms_enum'; +import { termsAggSuggestions } from '@kbn/kql/server/autocomplete/terms_agg'; import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; import type { diff --git a/x-pack/solutions/security/plugins/security_solution/server/plugin.ts b/x-pack/solutions/security/plugins/security_solution/server/plugin.ts index 10b905bd2e757..085ebc0f23fa5 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/plugin.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/plugin.ts @@ -521,7 +521,7 @@ export class Plugin implements ISecuritySolutionPlugin { registerEndpointRoutes(router, this.endpointContext); registerEndpointSuggestionsRoutes( router, - plugins.unifiedSearch.autocomplete.getInitializerContextConfig().create(), + plugins.kql.autocomplete.getInitializerContextConfig().create(), this.endpointContext ); registerLimitedConcurrencyRoutes(core); diff --git a/x-pack/solutions/security/plugins/security_solution/server/plugin_contract.ts b/x-pack/solutions/security/plugins/security_solution/server/plugin_contract.ts index 99612e92727c1..84dd4d50a61fe 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/plugin_contract.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/plugin_contract.ts @@ -42,7 +42,7 @@ import type { TelemetryPluginStart, TelemetryPluginSetup } from '@kbn/telemetry- import type { OsqueryPluginSetup } from '@kbn/osquery-plugin/server'; import type { CloudSetup } from '@kbn/cloud-plugin/server'; import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/server'; -import type { PluginSetup as UnifiedSearchServerPluginSetup } from '@kbn/unified-search-plugin/server'; +import type { PluginSetup as KqlServerPluginSetup } from '@kbn/kql/server'; import type { ElasticAssistantPluginStart } from '@kbn/elastic-assistant-plugin/server'; import type { InferenceServerStart } from '@kbn/inference-plugin/server'; import type { OnechatPluginSetup } from '@kbn/onechat-plugin/server'; @@ -69,7 +69,7 @@ export interface SecuritySolutionPluginSetupDependencies { usageCollection?: UsageCollectionPluginSetup; licensing: LicensingPluginSetup; osquery: OsqueryPluginSetup; - unifiedSearch: UnifiedSearchServerPluginSetup; + kql: KqlServerPluginSetup; share?: SharePluginSetup; onechat?: OnechatPluginSetup; } From 452847e11d7fc2289e73c9a7bd3f97e3e5f11d2d Mon Sep 17 00:00:00 2001 From: Stratoula Date: Fri, 19 Dec 2025 08:53:03 +0100 Subject: [PATCH 12/60] Cleanup --- .../annotation_editor_controls/index.test.tsx | 1 - .../components/query_input/query_input.tsx | 5 +- .../event_annotation_listing/public/plugin.ts | 6 +- .../query_string_input/query_string_input.tsx | 4 +- .../components/utils/combined_filter.ts | 21 - .../kql/public/components/utils/index.ts | 2 - .../plugins/shared/kql/public/index.ts | 6 + .../plugins/shared/kql/public/plugin.ts | 3 +- .../types.ts => kql/public/services.ts} | 7 +- .../public/filter_bar/filter_bar.tsx | 2 +- .../filter_button_group.tsx | 84 -- .../filter_editor/filter_editor.tsx | 2 +- .../filter_editor/phrase_suggestor.tsx | 2 +- .../filter_bar/filter_item/filter_item.tsx | 2 +- .../filter_bar/filter_item/filter_items.tsx | 2 +- .../public/filters_builder/context.ts | 2 +- .../filter_item/params_editor_input.tsx | 2 +- .../filters_builder/filters_builder.tsx | 2 +- .../shared/unified_search/public/index.ts | 2 - .../query_string_input/add_filter_popover.tsx | 2 +- .../filter_editor_wrapper.tsx | 2 +- .../query_string_input/from_user.test.ts | 35 - .../public/query_string_input/from_user.ts | 46 - .../get_query_string_input.tsx | 18 - .../public/query_string_input/index.tsx | 14 - .../language_switcher.test.tsx | 118 --- .../query_string_input/language_switcher.tsx | 141 --- .../public/query_string_input/match_pairs.ts | 136 --- .../query_string_input/query_bar_menu.tsx | 2 +- .../query_bar_menu_panels.tsx | 3 +- .../query_bar_top_row.test.tsx | 2 +- .../query_string_input/query_bar_top_row.tsx | 11 +- .../query_string_input.styles.tsx | 101 -- .../query_string_input.test.mocks.ts | 38 - .../query_string_input.test.tsx | 449 -------- .../query_string_input/query_string_input.tsx | 972 ------------------ .../public/query_string_input/to_user.test.ts | 36 - .../public/query_string_input/to_user.ts | 26 - .../public/search_bar/search_bar.tsx | 5 +- .../public/typeahead/constants.ts | 26 - .../unified_search/public/typeahead/index.tsx | 24 - .../typeahead/suggestion_component.test.tsx | 136 --- .../public/typeahead/suggestion_component.tsx | 254 ----- .../typeahead/suggestions_component.test.tsx | 142 --- .../typeahead/suggestions_component.tsx | 335 ------ .../shared/unified_search/public/ui_module.ts | 1 - .../unified_search/public/utils/index.ts | 1 - .../unified_search/public/utils/on_raf.ts | 23 - .../components/query_bar_wrapper.tsx | 2 - .../public/components/search_bar.test.tsx | 2 - .../dimension_panel/dimension_editor.tsx | 1 + .../dimension_panel/dimension_panel.test.tsx | 2 + .../dimension_panel/dimension_panel.tsx | 2 + .../datasources/form_based/form_based.test.ts | 2 + .../datasources/form_based/form_based.tsx | 4 + .../public/datasources/form_based/index.ts | 14 +- .../definitions/ranges/ranges.test.tsx | 3 + .../components/endpoint_exceptions_form.tsx | 2 +- .../event_filters/view/components/form.tsx | 2 +- .../trusted_apps/view/components/form.tsx | 2 +- 60 files changed, 65 insertions(+), 3229 deletions(-) delete mode 100644 src/platform/plugins/shared/kql/public/components/utils/combined_filter.ts rename src/platform/plugins/shared/{unified_search/public/typeahead/types.ts => kql/public/services.ts} (66%) delete mode 100644 src/platform/plugins/shared/unified_search/public/filter_bar/filter_button_group/filter_button_group.tsx delete mode 100644 src/platform/plugins/shared/unified_search/public/query_string_input/from_user.test.ts delete mode 100644 src/platform/plugins/shared/unified_search/public/query_string_input/from_user.ts delete mode 100644 src/platform/plugins/shared/unified_search/public/query_string_input/get_query_string_input.tsx delete mode 100644 src/platform/plugins/shared/unified_search/public/query_string_input/language_switcher.test.tsx delete mode 100644 src/platform/plugins/shared/unified_search/public/query_string_input/language_switcher.tsx delete mode 100644 src/platform/plugins/shared/unified_search/public/query_string_input/match_pairs.ts delete mode 100644 src/platform/plugins/shared/unified_search/public/query_string_input/query_string_input.styles.tsx delete mode 100644 src/platform/plugins/shared/unified_search/public/query_string_input/query_string_input.test.mocks.ts delete mode 100644 src/platform/plugins/shared/unified_search/public/query_string_input/query_string_input.test.tsx delete mode 100644 src/platform/plugins/shared/unified_search/public/query_string_input/query_string_input.tsx delete mode 100644 src/platform/plugins/shared/unified_search/public/query_string_input/to_user.test.ts delete mode 100644 src/platform/plugins/shared/unified_search/public/query_string_input/to_user.ts delete mode 100644 src/platform/plugins/shared/unified_search/public/typeahead/constants.ts delete mode 100644 src/platform/plugins/shared/unified_search/public/typeahead/index.tsx delete mode 100644 src/platform/plugins/shared/unified_search/public/typeahead/suggestion_component.test.tsx delete mode 100644 src/platform/plugins/shared/unified_search/public/typeahead/suggestion_component.tsx delete mode 100644 src/platform/plugins/shared/unified_search/public/typeahead/suggestions_component.test.tsx delete mode 100644 src/platform/plugins/shared/unified_search/public/typeahead/suggestions_component.tsx delete mode 100644 src/platform/plugins/shared/unified_search/public/utils/on_raf.ts diff --git a/src/platform/packages/shared/kbn-event-annotation-components/components/annotation_editor_controls/index.test.tsx b/src/platform/packages/shared/kbn-event-annotation-components/components/annotation_editor_controls/index.test.tsx index 04449be8576c9..bae92d0a31200 100644 --- a/src/platform/packages/shared/kbn-event-annotation-components/components/annotation_editor_controls/index.test.tsx +++ b/src/platform/packages/shared/kbn-event-annotation-components/components/annotation_editor_controls/index.test.tsx @@ -66,7 +66,6 @@ describe('AnnotationsPanel', () => { uiSettings: {}, storage: {}, dataViews: {}, - unifiedSearch: {}, docLinks: {}, notifications: {}, data: {}, diff --git a/src/platform/packages/shared/kbn-visualization-ui-components/components/query_input/query_input.tsx b/src/platform/packages/shared/kbn-visualization-ui-components/components/query_input/query_input.tsx index 88bcb63b6eed3..f7638b63a4650 100644 --- a/src/platform/packages/shared/kbn-visualization-ui-components/components/query_input/query_input.tsx +++ b/src/platform/packages/shared/kbn-visualization-ui-components/components/query_input/query_input.tsx @@ -11,7 +11,6 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { isEqual } from 'lodash'; import type { Query } from '@kbn/es-query'; -import { type UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import type { KqlPluginStart } from '@kbn/kql/public'; import { QueryStringInput } from '@kbn/kql/public'; import type { HttpStart } from '@kbn/core-http-browser'; @@ -30,7 +29,6 @@ export interface QueryInputServices { data: DataPublicPluginStart; uiSettings: IUiSettingsClient; notifications: NotificationsStart; - unifiedSearch: UnifiedSearchPublicPluginStart; kql: KqlPluginStart; docLinks: DocLinksStart; } @@ -45,7 +43,7 @@ export const QueryInput = ({ ['data-test-subj']: dataTestSubj, placeholder, appName, - services: { data, uiSettings, http, notifications, docLinks, storage, kql, dataViews, core }, + services: { data, uiSettings, http, notifications, docLinks, storage, kql, dataViews }, }: { value: Query; onChange: (input: Query) => void; @@ -95,7 +93,6 @@ export const QueryInput = ({ appName={appName} deps={{ autocomplete: kql.autocomplete, - core, notifications, http, docLinks, diff --git a/src/platform/plugins/private/event_annotation_listing/public/plugin.ts b/src/platform/plugins/private/event_annotation_listing/public/plugin.ts index dce0b08c58b7b..b51dbbaeefb64 100644 --- a/src/platform/plugins/private/event_annotation_listing/public/plugin.ts +++ b/src/platform/plugins/private/event_annotation_listing/public/plugin.ts @@ -16,10 +16,10 @@ import type { ContentManagementPublicStart } from '@kbn/content-management-plugi import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public/types'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { VisualizationsSetup } from '@kbn/visualizations-plugin/public'; -import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { i18n } from '@kbn/i18n'; import type { EventAnnotationPluginStart } from '@kbn/event-annotation-plugin/public'; import type { LensPublicStart } from '@kbn/lens-plugin/public'; +import type { KqlPluginStart } from '@kbn/kql/public'; import type { EventAnnotationListingPageServices } from './get_table_list'; export interface EventAnnotationListingStartDependencies { @@ -29,7 +29,7 @@ export interface EventAnnotationListingStartDependencies { savedObjectsTagging: SavedObjectTaggingPluginStart; presentationUtil: PresentationUtilPluginStart; dataViews: DataViewsPublicPluginStart; - unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; contentManagement: ContentManagementPublicStart; lens: LensPublicStart; } @@ -83,7 +83,7 @@ export class EventAnnotationListingPlugin notifications: coreStart.notifications, uiSettings: coreStart.uiSettings, dataViews: pluginsStart.dataViews, - unifiedSearch: pluginsStart.unifiedSearch, + kql: pluginsStart.kql, data: pluginsStart.data, storage: new Storage(localStorage), }, diff --git a/src/platform/plugins/shared/kql/public/components/query_string_input/query_string_input.tsx b/src/platform/plugins/shared/kql/public/components/query_string_input/query_string_input.tsx index 77353fa8ea90b..f15d7e6026e71 100644 --- a/src/platform/plugins/shared/kql/public/components/query_string_input/query_string_input.tsx +++ b/src/platform/plugins/shared/kql/public/components/query_string_input/query_string_input.tsx @@ -56,6 +56,7 @@ import { onRaf } from '../utils'; import { FilterButtonGroup } from './filter_button_group'; import type { AutocompleteService, QuerySuggestion } from '../../autocomplete'; import { QuerySuggestionTypes } from '../../autocomplete'; +import { getCoreStart } from '../../services'; import { StyledDiv } from './query_string_input.styles'; export const strings = { @@ -98,7 +99,6 @@ export interface QueryStringInputDependencies { docLinks: DocLinksStart; uiSettings: CoreStart['uiSettings']; dataViews: DataViewsPublicPluginStart; - core: CoreStart; } export interface QueryStringInputProps { @@ -564,7 +564,7 @@ export class QueryStringInput extends PureComponent
, - this.props.deps.core + getCoreStart() ), }); } diff --git a/src/platform/plugins/shared/kql/public/components/utils/combined_filter.ts b/src/platform/plugins/shared/kql/public/components/utils/combined_filter.ts deleted file mode 100644 index 944680c9a4dbe..0000000000000 --- a/src/platform/plugins/shared/kql/public/components/utils/combined_filter.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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 type { CombinedFilter } from '@kbn/es-query'; -import { type Filter, isCombinedFilter } from '@kbn/es-query'; - -/** - * Defines a boolean relation type (AND/OR) from the filter otherwise returns undefined. - * @param {Filter} filter - */ -export const getBooleanRelationType = (filter: Filter | CombinedFilter) => { - if (isCombinedFilter(filter)) { - return filter.meta.relation; - } -}; diff --git a/src/platform/plugins/shared/kql/public/components/utils/index.ts b/src/platform/plugins/shared/kql/public/components/utils/index.ts index bb85ea4c62025..98b92cb1e7785 100644 --- a/src/platform/plugins/shared/kql/public/components/utils/index.ts +++ b/src/platform/plugins/shared/kql/public/components/utils/index.ts @@ -9,5 +9,3 @@ export { onRaf } from './on_raf'; export { shallowEqual } from './shallow_equal'; - -export { getBooleanRelationType } from './combined_filter'; diff --git a/src/platform/plugins/shared/kql/public/index.ts b/src/platform/plugins/shared/kql/public/index.ts index 0693300995b75..1b3722462553a 100644 --- a/src/platform/plugins/shared/kql/public/index.ts +++ b/src/platform/plugins/shared/kql/public/index.ts @@ -9,6 +9,9 @@ export type { KqlPluginStart, KqlPluginSetup } from './plugin'; export type { QueryStringInputProps } from './components/query_string_input/query_string_input'; export { QueryStringInput } from './components/query_string_input/query_string_input'; +export { QueryLanguageSwitcher } from './components/query_string_input/language_switcher'; +export { FilterButtonGroup } from './components/query_string_input/filter_button_group'; +export { fromUser } from './components/query_string_input/from_user'; export type { QuerySuggestion, @@ -17,4 +20,7 @@ export type { AutocompleteStart, } from './autocomplete'; +export { type SuggestionsAbstraction } from './components/typeahead/suggestions_component'; +export type { SuggestionsListSize } from './components/typeahead/suggestions_component'; + export { QuerySuggestionTypes } from './autocomplete/providers/query_suggestion_provider'; diff --git a/src/platform/plugins/shared/kql/public/plugin.ts b/src/platform/plugins/shared/kql/public/plugin.ts index 659e008eb26a0..fd61cd91a68cf 100644 --- a/src/platform/plugins/shared/kql/public/plugin.ts +++ b/src/platform/plugins/shared/kql/public/plugin.ts @@ -20,6 +20,7 @@ import { type AutocompleteStart, type AutocompleteSetup, } from './autocomplete/autocomplete_service'; +import { setCoreStart } from './services'; export interface KqlPluginSetupDependencies { data: DataPublicPluginSetup; @@ -68,6 +69,7 @@ export class KqlPlugin implements Plugin<{}, KqlPluginStart> { public start(core: CoreStart, { data, dataViews }: KqlPluginStartDependencies): KqlPluginStart { const autocompleteStart = this.autocomplete.start(); + setCoreStart(core); return { autocomplete: autocompleteStart, @@ -80,7 +82,6 @@ export class KqlPlugin implements Plugin<{}, KqlPluginStart> { storage: this.storage, uiSettings: core.uiSettings, autocomplete: autocompleteStart, - core, }), }; } diff --git a/src/platform/plugins/shared/unified_search/public/typeahead/types.ts b/src/platform/plugins/shared/kql/public/services.ts similarity index 66% rename from src/platform/plugins/shared/unified_search/public/typeahead/types.ts rename to src/platform/plugins/shared/kql/public/services.ts index 20082382333ee..2d33a4b5d24f1 100644 --- a/src/platform/plugins/shared/unified_search/public/typeahead/types.ts +++ b/src/platform/plugins/shared/kql/public/services.ts @@ -7,8 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { QuerySuggestion } from '../autocomplete'; +import type { CoreStart } from '@kbn/core/public'; +import { createGetterSetter } from '@kbn/kibana-utils-plugin/public'; -export type SuggestionOnClick = (suggestion: QuerySuggestion, index: number) => void; - -export type SuggestionOnMouseEnter = (suggestion: QuerySuggestion, index: number) => void; +export const [getCoreStart, setCoreStart] = createGetterSetter('CoreStart'); diff --git a/src/platform/plugins/shared/unified_search/public/filter_bar/filter_bar.tsx b/src/platform/plugins/shared/unified_search/public/filter_bar/filter_bar.tsx index 4955c77bf9ed4..72a2a0a6f8138 100644 --- a/src/platform/plugins/shared/unified_search/public/filter_bar/filter_bar.tsx +++ b/src/platform/plugins/shared/unified_search/public/filter_bar/filter_bar.tsx @@ -14,10 +14,10 @@ import type { Filter } from '@kbn/es-query'; import type { ReactNode } from 'react'; import React, { useRef } from 'react'; import type { DataView } from '@kbn/data-views-plugin/public'; +import type { SuggestionsAbstraction } from '@kbn/kql/public'; import { FilterItems, type FilterItemsProps } from './filter_item/filter_items'; import { filterBarStyles } from './filter_bar.styles'; -import type { SuggestionsAbstraction } from '../typeahead/suggestions_component'; export interface Props { filters: Filter[]; diff --git a/src/platform/plugins/shared/unified_search/public/filter_bar/filter_button_group/filter_button_group.tsx b/src/platform/plugins/shared/unified_search/public/filter_bar/filter_button_group/filter_button_group.tsx deleted file mode 100644 index 9cf09dc39ee0d..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/filter_bar/filter_button_group/filter_button_group.tsx +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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 type { FC, ReactNode } from 'react'; -import React from 'react'; -import classNames from 'classnames'; -import type { UseEuiTheme } from '@elastic/eui'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { css } from '@emotion/react'; -import { useMemoCss } from '../../use_memo_css'; - -interface Props { - items: ReactNode[]; - /** - * Displays the last item without a border radius as if attached to the next DOM node - */ - attached?: boolean; - /** - * Matches overall height with standard form/button sizes - */ - size?: 'm' | 's'; -} - -export const FilterButtonGroup: FC = ({ items, attached, size = 's', ...rest }: Props) => { - const styles = useMemoCss(filterButtonStyles); - return ( - - {items.map((item, i) => - item == null ? undefined : ( - - {item} - - ) - )} - - ); -}; - -const filterButtonStyles = { - wrapper: ({ euiTheme }: UseEuiTheme) => - css({ - position: 'relative', - height: euiTheme.size.xl, - backgroundColor: euiTheme.colors.backgroundBaseFormsPrepend, - borderRadius: euiTheme.border.radius.medium, - '&::after': { - content: "''", - position: 'absolute', - inset: 0, - border: `${euiTheme.border.width.thin} solid ${euiTheme.colors.borderBasePlain}`, - borderRadius: 'inherit', - pointerEvents: 'none', - }, - // Targets any interactable elements - '*:enabled': { - transform: 'none !important', - }, - '&.kbnFilterButtonGroup--s': { - height: euiTheme.size.xl, - }, - ' &.kbnFilterButtonGroup--attached': { - borderTopRightRadius: 0, - borderBottomRightRadius: 0, - }, - '> *:not(:last-of-type)': { - borderRight: `1px solid ${euiTheme.colors.borderBasePlain}`, - }, - }), -}; diff --git a/src/platform/plugins/shared/unified_search/public/filter_bar/filter_editor/filter_editor.tsx b/src/platform/plugins/shared/unified_search/public/filter_bar/filter_editor/filter_editor.tsx index cb47a93b05d03..e9381668ac8c8 100644 --- a/src/platform/plugins/shared/unified_search/public/filter_bar/filter_editor/filter_editor.tsx +++ b/src/platform/plugins/shared/unified_search/public/filter_bar/filter_editor/filter_editor.tsx @@ -52,6 +52,7 @@ import type { WithEuiThemeProps } from '@elastic/eui/src/services/theme'; import type { DocLinksStart } from '@kbn/core-doc-links-browser'; import { css } from '@emotion/react'; import { euiThemeVars } from '@kbn/ui-theme'; +import type { SuggestionsAbstraction } from '@kbn/kql/public'; import { GenericComboBox } from './generic_combo_box'; import { getFieldFromFilter, @@ -70,7 +71,6 @@ import { filterPreviewLabelStyle, filtersBuilderMaxHeightCss, } from './filter_editor.styles'; -import type { SuggestionsAbstraction } from '../../typeahead/suggestions_component'; const editorFormStyle = css({ padding: euiThemeVars.euiSizeM }); diff --git a/src/platform/plugins/shared/unified_search/public/filter_bar/filter_editor/phrase_suggestor.tsx b/src/platform/plugins/shared/unified_search/public/filter_bar/filter_editor/phrase_suggestor.tsx index 0ea129a4e7be9..e9340efa84e3d 100644 --- a/src/platform/plugins/shared/unified_search/public/filter_bar/filter_editor/phrase_suggestor.tsx +++ b/src/platform/plugins/shared/unified_search/public/filter_bar/filter_editor/phrase_suggestor.tsx @@ -10,13 +10,13 @@ import React from 'react'; import type { KibanaReactContextValue } from '@kbn/kibana-react-plugin/public'; import { withKibana } from '@kbn/kibana-react-plugin/public'; +import type { SuggestionsAbstraction } from '@kbn/kql/public'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; import type { DataView, DataViewField } from '@kbn/data-views-plugin/common'; import { debounce } from 'lodash'; import type { Filter } from '@kbn/es-query'; import { buildQueryFromFilters } from '@kbn/es-query'; -import type { SuggestionsAbstraction } from '../../typeahead/suggestions_component'; import type { IUnifiedSearchPluginServices } from '../../types'; export interface PhraseSuggestorProps { diff --git a/src/platform/plugins/shared/unified_search/public/filter_bar/filter_item/filter_item.tsx b/src/platform/plugins/shared/unified_search/public/filter_bar/filter_item/filter_item.tsx index b49b86857ea2d..55022a51f76aa 100644 --- a/src/platform/plugins/shared/unified_search/public/filter_bar/filter_item/filter_item.tsx +++ b/src/platform/plugins/shared/unified_search/public/filter_bar/filter_item/filter_item.tsx @@ -19,6 +19,7 @@ import { } from '@kbn/es-query'; import classNames from 'classnames'; import type { MouseEvent, HTMLAttributes } from 'react'; +import type { SuggestionsAbstraction } from '@kbn/kql/public'; import React, { useState, useEffect, useCallback } from 'react'; import { type DocLinksStart, type IUiSettingsClient } from '@kbn/core/public'; import type { DataView, DataViewsContract } from '@kbn/data-views-plugin/public'; @@ -30,7 +31,6 @@ import { FilterView } from '../filter_view'; import type { FilterPanelOption } from '../../types'; import type { WithCloseFilterEditorConfirmModalProps } from '../filter_editor'; import { withCloseFilterEditorConfirmModal } from '../filter_editor'; -import type { SuggestionsAbstraction } from '../../typeahead/suggestions_component'; export interface FilterItemProps extends WithCloseFilterEditorConfirmModalProps { id: string; diff --git a/src/platform/plugins/shared/unified_search/public/filter_bar/filter_item/filter_items.tsx b/src/platform/plugins/shared/unified_search/public/filter_bar/filter_item/filter_items.tsx index 7634c11065c09..fea4f40084a1b 100644 --- a/src/platform/plugins/shared/unified_search/public/filter_bar/filter_item/filter_items.tsx +++ b/src/platform/plugins/shared/unified_search/public/filter_bar/filter_item/filter_items.tsx @@ -16,10 +16,10 @@ import type { Filter } from '@kbn/es-query'; import { METRIC_TYPE } from '@kbn/analytics'; import type { DataView } from '@kbn/data-views-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; +import type { SuggestionsAbstraction } from '@kbn/kql/public'; import type { FilterItemProps } from './filter_item'; import { FilterItem } from './filter_item'; import type { IUnifiedSearchPluginServices } from '../../types'; -import type { SuggestionsAbstraction } from '../../typeahead/suggestions_component'; /** * Properties for the filter items component, which will render a single filter pill for every filter that is sent in diff --git a/src/platform/plugins/shared/unified_search/public/filters_builder/context.ts b/src/platform/plugins/shared/unified_search/public/filters_builder/context.ts index 80f0fa544cd4c..e13d41b1dd5f0 100644 --- a/src/platform/plugins/shared/unified_search/public/filters_builder/context.ts +++ b/src/platform/plugins/shared/unified_search/public/filters_builder/context.ts @@ -11,8 +11,8 @@ import type { Dispatch } from 'react'; import React from 'react'; import type { DataView } from '@kbn/data-views-plugin/common'; import type { Filter } from '@kbn/es-query'; +import type { SuggestionsAbstraction } from '@kbn/kql/public'; import type { FiltersBuilderActions } from './reducer'; -import type { SuggestionsAbstraction } from '../typeahead/suggestions_component'; interface FiltersBuilderContextType { dataView: DataView; diff --git a/src/platform/plugins/shared/unified_search/public/filters_builder/filter_item/params_editor_input.tsx b/src/platform/plugins/shared/unified_search/public/filters_builder/filter_item/params_editor_input.tsx index b048b07214f51..2ac36ff18442e 100644 --- a/src/platform/plugins/shared/unified_search/public/filters_builder/filter_item/params_editor_input.tsx +++ b/src/platform/plugins/shared/unified_search/public/filters_builder/filter_item/params_editor_input.tsx @@ -13,6 +13,7 @@ import type { DataView, DataViewField } from '@kbn/data-views-plugin/common'; import { EuiFieldText } from '@elastic/eui'; import type { Filter } from '@kbn/es-query'; import { useKibana } from '@kbn/kibana-react-plugin/public'; +import type { SuggestionsAbstraction } from '@kbn/kql/public'; import { PhraseValueInput, PhrasesValuesInput, @@ -20,7 +21,6 @@ import { isRangeParams, } from '../../filter_bar/filter_editor'; import type { Operator } from '../../filter_bar/filter_editor'; -import type { SuggestionsAbstraction } from '../../typeahead/suggestions_component'; import { OPERATORS } from '../../filter_bar/filter_editor/lib/filter_operators'; import { formatDateChange } from '../../filter_bar/filter_editor/range_value_input'; diff --git a/src/platform/plugins/shared/unified_search/public/filters_builder/filters_builder.tsx b/src/platform/plugins/shared/unified_search/public/filters_builder/filters_builder.tsx index 1c3f1c46660c7..6c1ec02078c79 100644 --- a/src/platform/plugins/shared/unified_search/public/filters_builder/filters_builder.tsx +++ b/src/platform/plugins/shared/unified_search/public/filters_builder/filters_builder.tsx @@ -12,13 +12,13 @@ import type { DragDropContextProps } from '@elastic/eui'; import { EuiDragDropContext, useEuiPaddingSize } from '@elastic/eui'; import type { DataView } from '@kbn/data-views-plugin/common'; import { type Filter, BooleanRelation, compareFilters } from '@kbn/es-query'; +import type { SuggestionsAbstraction } from '@kbn/kql/public'; import { FiltersBuilderContextType } from './context'; import { FilterGroup } from './filter_group'; import { FiltersBuilderReducer } from './reducer'; import { getPathInArray } from './utils'; import type { FilterLocation } from './types'; import { filtersBuilderCss } from './filters_builder.styles'; -import type { SuggestionsAbstraction } from '../typeahead/suggestions_component'; export interface FiltersBuilderProps { filters: Filter[]; diff --git a/src/platform/plugins/shared/unified_search/public/index.ts b/src/platform/plugins/shared/unified_search/public/index.ts index f0868f6f36c38..d207464122850 100755 --- a/src/platform/plugins/shared/unified_search/public/index.ts +++ b/src/platform/plugins/shared/unified_search/public/index.ts @@ -12,7 +12,6 @@ import type { FilterManager, TimefilterContract } from '@kbn/data-plugin/public' import { UnifiedSearchPublicPlugin } from './plugin'; export type { IndexPatternSelectProps } from './index_pattern_select'; -export type { QueryStringInputProps } from './query_string_input'; export type { StatefulSearchBarProps, SearchBarProps } from './search_bar'; export type { UnifiedSearchPublicPluginStart, @@ -24,7 +23,6 @@ export type { FilterItemsProps } from './filter_bar'; export type { DataViewPickerProps } from './dataview_picker'; export type { ApplyGlobalFilterActionContext } from './actions/apply_filter_action/apply_filter_action'; -export { QueryStringInput } from './query_string_input'; export { SearchBar } from './search_bar'; export { createSearchBar } from './search_bar/create_search_bar'; export { FilterItem, FilterItems } from './filter_bar'; diff --git a/src/platform/plugins/shared/unified_search/public/query_string_input/add_filter_popover.tsx b/src/platform/plugins/shared/unified_search/public/query_string_input/add_filter_popover.tsx index b75537eebd6c5..cc61a21746f06 100644 --- a/src/platform/plugins/shared/unified_search/public/query_string_input/add_filter_popover.tsx +++ b/src/platform/plugins/shared/unified_search/public/query_string_input/add_filter_popover.tsx @@ -13,11 +13,11 @@ import type { EuiButtonIconProps } from '@elastic/eui'; import { EuiFlexItem, EuiButtonIcon, EuiPopover, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import type { Filter } from '@kbn/es-query'; +import type { SuggestionsAbstraction } from '@kbn/kql/public'; import type { DataView } from '@kbn/data-views-plugin/public'; import { FilterEditorWrapper } from './filter_editor_wrapper'; import type { WithCloseFilterEditorConfirmModalProps } from '../filter_bar/filter_editor'; import { withCloseFilterEditorConfirmModal } from '../filter_bar/filter_editor'; -import type { SuggestionsAbstraction } from '../typeahead/suggestions_component'; export const strings = { getAddFilterButtonLabel: () => diff --git a/src/platform/plugins/shared/unified_search/public/query_string_input/filter_editor_wrapper.tsx b/src/platform/plugins/shared/unified_search/public/query_string_input/filter_editor_wrapper.tsx index 9a9e3b96f246c..9126a81739c27 100644 --- a/src/platform/plugins/shared/unified_search/public/query_string_input/filter_editor_wrapper.tsx +++ b/src/platform/plugins/shared/unified_search/public/query_string_input/filter_editor_wrapper.tsx @@ -14,11 +14,11 @@ import { METRIC_TYPE } from '@kbn/analytics'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; import type { DataView } from '@kbn/data-views-plugin/public'; +import type { SuggestionsAbstraction } from '@kbn/kql/public'; import type { IUnifiedSearchPluginServices } from '../types'; import { FILTER_EDITOR_WIDTH } from '../filter_bar/filter_item/filter_item'; import { FilterEditor } from '../filter_bar/filter_editor'; import { fetchIndexPatterns } from './fetch_index_patterns'; -import type { SuggestionsAbstraction } from '../typeahead/suggestions_component'; interface QueryDslFilter { queryDsl: string; diff --git a/src/platform/plugins/shared/unified_search/public/query_string_input/from_user.test.ts b/src/platform/plugins/shared/unified_search/public/query_string_input/from_user.test.ts deleted file mode 100644 index 797abe4816c53..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/query_string_input/from_user.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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 { fromUser } from './from_user'; - -describe('user input helpers', function () { - describe('user input parser', function () { - it('should return the input if passed an object', function () { - expect(fromUser({ foo: 'bar' })).toEqual({ foo: 'bar' }); - }); - - it('unless the object is empty, then convert it to an empty string', function () { - expect(fromUser({})).toEqual(''); - }); - - it('should pass through input strings that not start with {', function () { - expect(fromUser('foo')).toEqual('foo'); - expect(fromUser('400')).toEqual('400'); - expect(fromUser('true')).toEqual('true'); - }); - - it('should parse valid JSON and return the object instead of a string', function () { - expect(fromUser('{}')).toEqual({}); - - // invalid json remains a string - expect(fromUser('{a:b}')).toEqual('{a:b}'); - }); - }); -}); diff --git a/src/platform/plugins/shared/unified_search/public/query_string_input/from_user.ts b/src/platform/plugins/shared/unified_search/public/query_string_input/from_user.ts deleted file mode 100644 index a86b756d23704..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/query_string_input/from_user.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 _ from 'lodash'; - -/** - * Take userInput from the user and make it into a query object - * @returns {object} - * @param userInput - */ - -export function fromUser(userInput: object | string) { - const matchAll = ''; - - if (_.isEmpty(userInput)) { - return ''; - } - - if (_.isObject(userInput)) { - return userInput; - } - - userInput = userInput || ''; - if (typeof userInput === 'string') { - const trimmedUserInput = userInput.trim(); - if (trimmedUserInput.length === 0) { - return matchAll; - } - - if (trimmedUserInput[0] === '{') { - try { - return JSON.parse(trimmedUserInput); - } catch (e) { - return userInput; - } - } else { - return userInput; - } - } -} diff --git a/src/platform/plugins/shared/unified_search/public/query_string_input/get_query_string_input.tsx b/src/platform/plugins/shared/unified_search/public/query_string_input/get_query_string_input.tsx deleted file mode 100644 index de46f8b6441ae..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/query_string_input/get_query_string_input.tsx +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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 React from 'react'; -import type { QueryStringInputProps } from '.'; -import { QueryStringInput } from '.'; - -export function createQueryStringInput(deps: QueryStringInputProps['deps']) { - return (props: Omit) => { - return ; - }; -} diff --git a/src/platform/plugins/shared/unified_search/public/query_string_input/index.tsx b/src/platform/plugins/shared/unified_search/public/query_string_input/index.tsx index 3ac21561ca427..f69788da191b8 100644 --- a/src/platform/plugins/shared/unified_search/public/query_string_input/index.tsx +++ b/src/platform/plugins/shared/unified_search/public/query_string_input/index.tsx @@ -10,7 +10,6 @@ import React from 'react'; import type { AggregateQuery, Query } from '@kbn/es-query'; import type { QueryBarTopRowProps } from './query_bar_top_row'; -import type { QueryStringInputProps } from './query_string_input'; const Fallback = () =>
; @@ -26,16 +25,3 @@ export const QueryBarTopRow = ( )} /> ); - -const LazyQueryStringInputUI = React.lazy(async () => { - const { QueryStringInput } = await import('@kbn/kql/public'); - return { default: QueryStringInput }; -}); - -export const QueryStringInput = (props: QueryStringInputProps) => ( - }> - - -); - -export type { QueryStringInputProps }; diff --git a/src/platform/plugins/shared/unified_search/public/query_string_input/language_switcher.test.tsx b/src/platform/plugins/shared/unified_search/public/query_string_input/language_switcher.test.tsx deleted file mode 100644 index 410a501d27411..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/query_string_input/language_switcher.test.tsx +++ /dev/null @@ -1,118 +0,0 @@ -/* - * 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 React from 'react'; -import type { QueryLanguageSwitcherProps } from './language_switcher'; -import { QueryLanguageSwitcher } from './language_switcher'; -import { coreMock } from '@kbn/core/public/mocks'; -import { renderWithI18n } from '@kbn/test-jest-helpers'; -import { fireEvent, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -const startMock = coreMock.createStart(); - -async function renderSwitcher(testProps: Omit) { - const result = renderWithI18n( - - ); - // Wait for component to be fully rendered - await waitFor(() => expect(screen.getByRole('button')).toBeInTheDocument()); - return result; -} - -describe('LanguageSwitcher', () => { - it('should select the lucene context menu if language is lucene', async () => { - await renderSwitcher({ language: 'lucene', onSelectLanguage: jest.fn() }); - - await userEvent.click(screen.getByRole('button')); - expect( - screen.getByTestId('luceneLanguageMenuItem').querySelector('[data-euiicon-type="check"]') - ).toBeTruthy(); - }); - - it('should select the kql context menu if language is kuery', async () => { - await renderSwitcher({ language: 'kuery', onSelectLanguage: jest.fn() }); - await userEvent.click(screen.getByRole('button')); - expect( - screen.getByTestId('kqlLanguageMenuItem').querySelector('[data-euiicon-type="check"]') - ).toBeTruthy(); - }); - - it('should select the lucene context menu if language is text', async () => { - await renderSwitcher({ language: 'text', onSelectLanguage: jest.fn() }); - - await userEvent.click(screen.getByRole('button')); - expect( - screen.getByTestId('luceneLanguageMenuItem').querySelector('[data-euiicon-type="check"]') - ).toBeTruthy(); - }); - it('it set language on nonKql mode text', async () => { - const onSelectLanguage = jest.fn(); - await renderSwitcher({ - language: 'kuery', - nonKqlMode: 'text', - onSelectLanguage, - }); - await userEvent.click(screen.getByRole('button')); - expect( - screen.getByTestId('kqlLanguageMenuItem').querySelector('[data-euiicon-type="check"]') - ).toBeTruthy(); - expect( - screen.getByTestId('luceneLanguageMenuItem').querySelector('[data-euiicon-type="check"]') - ).toBeFalsy(); - fireEvent.click(screen.getByTestId('luceneLanguageMenuItem')); - - expect(onSelectLanguage).toHaveBeenCalledWith('text'); - }); - it('it set language on nonKql mode lucene', async () => { - const onSelectLanguage = jest.fn(); - - await renderSwitcher({ - language: 'kuery', - nonKqlMode: 'lucene', - onSelectLanguage, - }); - await userEvent.click(screen.getByRole('button')); - fireEvent.click(screen.getByTestId('luceneLanguageMenuItem')); - expect(onSelectLanguage).toHaveBeenCalledWith('lucene'); - }); - - it('it set language on kuery mode with nonKqlMode text', async () => { - const onSelectLanguage = jest.fn(); - - await renderSwitcher({ - language: 'text', - nonKqlMode: 'text', - onSelectLanguage, - }); - - await userEvent.click(screen.getByRole('button')); - fireEvent.click(screen.getByTestId('kqlLanguageMenuItem')); - expect(onSelectLanguage).toHaveBeenCalledWith('kuery'); - }); - - it('it set language on kuery mode with nonKqlMode lucene', async () => { - const onSelectLanguage = jest.fn(); - - await renderSwitcher({ - language: 'lucene', - nonKqlMode: 'lucene', - onSelectLanguage, - }); - - await userEvent.click(screen.getByRole('button')); - fireEvent.click(screen.getByTestId('luceneLanguageMenuItem')); - expect( - screen.getByTestId('luceneLanguageMenuItem').querySelector('[data-euiicon-type="check"]') - ).toBeTruthy(); - - fireEvent.click(screen.getByTestId('kqlLanguageMenuItem')); - - expect(onSelectLanguage).toHaveBeenCalledWith('kuery'); - }); -}); diff --git a/src/platform/plugins/shared/unified_search/public/query_string_input/language_switcher.tsx b/src/platform/plugins/shared/unified_search/public/query_string_input/language_switcher.tsx deleted file mode 100644 index a8f31744401bf..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/query_string_input/language_switcher.tsx +++ /dev/null @@ -1,141 +0,0 @@ -/* - * 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 type { PopoverAnchorPosition } from '@elastic/eui'; -import { - EuiPopover, - EuiPopoverTitle, - EuiContextMenuItem, - toSentenceCase, - EuiHorizontalRule, - EuiButtonIcon, - EuiSelectable, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import React, { useState } from 'react'; -import type { DocLinksStart } from '@kbn/core/public'; -import { i18n } from '@kbn/i18n'; - -export const strings = { - getSwitchLanguageButtonText: () => - i18n.translate('unifiedSearch.switchLanguage.buttonText', { - defaultMessage: 'Switch language button.', - }), - getFilterLanguageLabel: () => - i18n.translate('unifiedSearch.switchLanguage.filterLanguageLabel', { - defaultMessage: 'Filter language', - }), - documentationLabel: () => - i18n.translate('unifiedSearch.switchLanguage.documentationLabel', { - defaultMessage: 'Documentation', - }), -}; - -export interface QueryLanguageSwitcherProps { - language: string; - onSelectLanguage: (newLanguage: string) => void; - anchorPosition?: PopoverAnchorPosition; - nonKqlMode?: 'lucene' | 'text'; - isOnTopBarMenu?: boolean; - isDisabled?: boolean; - deps: { - docLinks: DocLinksStart; - }; -} - -export const QueryLanguageSwitcher = React.memo(function QueryLanguageSwitcher({ - language, - anchorPosition, - onSelectLanguage, - nonKqlMode = 'lucene', - isOnTopBarMenu, - isDisabled, - deps: { docLinks }, -}: QueryLanguageSwitcherProps) { - const kueryQuerySyntaxDocs = docLinks.links.query.kueryQuerySyntax; - const [isPopoverOpen, setIsPopoverOpen] = useState(false); - - const button = ( - setIsPopoverOpen(!isPopoverOpen)} - className="kqlQueryBar__languageSwitcherButton" - data-test-subj={'switchQueryLanguageButton'} - aria-label={strings.getSwitchLanguageButtonText()} - disabled={isDisabled} - /> - ); - - const isKqlSelected = language === 'kuery'; - - const languageMenuItem = ( - <> - { - const selectedOptions = newOptions.find((option) => option.checked === 'on'); - - if (selectedOptions) { - onSelectLanguage(selectedOptions.key); - } - }} - singleSelection={true} - listProps={{ bordered: false }} - > - {(list) => list} - - - - {strings.documentationLabel()} - - - ); - - const languageQueryStringComponent = ( - setIsPopoverOpen(false)} - repositionOnScroll - panelPaddingSize="none" - > - - - - {languageMenuItem} - - ); - - return Boolean(isOnTopBarMenu) ? languageMenuItem : languageQueryStringComponent; -}); diff --git a/src/platform/plugins/shared/unified_search/public/query_string_input/match_pairs.ts b/src/platform/plugins/shared/unified_search/public/query_string_input/match_pairs.ts deleted file mode 100644 index d0d5ebd96d3c3..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/query_string_input/match_pairs.ts +++ /dev/null @@ -1,136 +0,0 @@ -/* - * 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". - */ - -/** - * This helper automatically handles matching pairs. - * Specifically, it does the following: - * - * 1. If the key is a closer, and the character in front of the cursor is the - * same, simply move the cursor forward. - * 2. If the key is an opener, insert the opener at the beginning of the - * selection, and the closer at the end of the selection, and move the - * selection forward. - * 3. If the backspace is hit, and the characters before and after the cursor - * are a pair, remove both characters and move the cursor backward. - */ - -const pairs = ['()', '[]', '{}', `''`, '""']; -const openers = pairs.map((pair) => pair[0]); -const closers = pairs.map((pair) => pair[1]); - -interface MatchPairsOptions { - value: string; - selectionStart: number; - selectionEnd: number; - key: string; - metaKey: boolean; - updateQuery: (query: string, selectionStart: number, selectionEnd: number) => void; - preventDefault: () => void; -} - -export function matchPairs({ - value, - selectionStart, - selectionEnd, - key, - metaKey, - updateQuery, - preventDefault, -}: MatchPairsOptions) { - if (shouldMoveCursorForward(key, value, selectionStart, selectionEnd)) { - preventDefault(); - updateQuery(value, selectionStart + 1, selectionEnd + 1); - } else if (shouldInsertMatchingCloser(key, value, selectionStart, selectionEnd)) { - preventDefault(); - const newValue = - value.substr(0, selectionStart) + - key + - value.substring(selectionStart, selectionEnd) + - closers[openers.indexOf(key)] + - value.substr(selectionEnd); - updateQuery(newValue, selectionStart + 1, selectionEnd + 1); - } else if (shouldRemovePair(key, metaKey, value, selectionStart, selectionEnd)) { - preventDefault(); - const newValue = value.substr(0, selectionEnd - 1) + value.substr(selectionEnd + 1); - updateQuery(newValue, selectionStart - 1, selectionEnd - 1); - } -} - -function shouldMoveCursorForward( - key: string, - value: string, - selectionStart: number, - selectionEnd: number -) { - if (!closers.includes(key)) { - return false; - } - - // Never move selection forward for multi-character selections - if (selectionStart !== selectionEnd) { - return false; - } - - // Move selection forward if the key is the same as the closer in front of the selection - return value.charAt(selectionEnd) === key; -} - -function shouldInsertMatchingCloser( - key: string, - value: string, - selectionStart: number, - selectionEnd: number -) { - if (!openers.includes(key)) { - return false; - } - - // Always insert for multi-character selections - if (selectionStart !== selectionEnd) { - return true; - } - - const precedingCharacter = value.charAt(selectionStart - 1); - const followingCharacter = value.charAt(selectionStart + 1); - - // Don't insert if the preceding character is a backslash - if (precedingCharacter === '\\') { - return false; - } - - // Don't insert if it's a quote and the either of the preceding/following characters is alphanumeric - return !( - ['"', `'`].includes(key) && - (isAlphanumeric(precedingCharacter) || isAlphanumeric(followingCharacter)) - ); -} - -function shouldRemovePair( - key: string, - metaKey: boolean, - value: string, - selectionStart: number, - selectionEnd: number -) { - if (key !== 'Backspace' || metaKey) { - return false; - } - - // Never remove for multi-character selections - if (selectionStart !== selectionEnd) { - return false; - } - - // Remove if the preceding/following characters are a pair - return pairs.includes(value.substr(selectionEnd - 1, 2)); -} - -function isAlphanumeric(value = '') { - return value.match(/[a-zA-Z0-9_]/); -} diff --git a/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_menu.tsx b/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_menu.tsx index dd46faccd4669..26940beb9f2de 100644 --- a/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_menu.tsx +++ b/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_menu.tsx @@ -27,12 +27,12 @@ import type { Filter, Query, TimeRange } from '@kbn/es-query'; import type { DataView } from '@kbn/data-views-plugin/public'; import type { SavedQueryService, SavedQuery, SavedQueryTimeFilter } from '@kbn/data-plugin/public'; import { euiThemeVars } from '@kbn/ui-theme'; +import type { SuggestionsAbstraction } from '@kbn/kql/public'; import type { QueryBarMenuPanelsProps, AdditionalQueryBarMenuItems } from './query_bar_menu_panels'; import { useQueryBarMenuPanels, QueryBarMenuPanel } from './query_bar_menu_panels'; import { FilterEditorWrapper } from './filter_editor_wrapper'; import type { WithCloseFilterEditorConfirmModalProps } from '../filter_bar/filter_editor'; import { withCloseFilterEditorConfirmModal } from '../filter_bar/filter_editor'; -import type { SuggestionsAbstraction } from '../typeahead/suggestions_component'; export const strings = { getFilterSetButtonLabel: () => diff --git a/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_menu_panels.tsx b/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_menu_panels.tsx index dd35c5c6dfef1..1d5084dce17c2 100644 --- a/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_menu_panels.tsx +++ b/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_menu_panels.tsx @@ -36,9 +36,8 @@ import { import type { SavedQueryService, SavedQuery, SavedQueryTimeFilter } from '@kbn/data-plugin/public'; import { euiThemeVars } from '@kbn/ui-theme'; import type { EuiContextMenuClass } from '@elastic/eui/src/components/context_menu/context_menu'; +import { QueryLanguageSwitcher, fromUser } from '@kbn/kql/public'; import type { IUnifiedSearchPluginServices } from '../types'; -import { fromUser } from './from_user'; -import { QueryLanguageSwitcher } from './language_switcher'; import type { FilterPanelOption } from '../types'; import { PanelTitle } from './panel_title'; diff --git a/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.test.tsx b/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.test.tsx index de89b1ea6d5de..21fcf3f2d341c 100644 --- a/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.test.tsx +++ b/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.test.tsx @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { mockPersistedLogFactory } from './query_string_input.test.mocks'; +import { mockPersistedLogFactory } from '@kbn/kql/public/components/query_string_input/query_string_input.test.mocks'; import React from 'react'; import { BehaviorSubject } from 'rxjs'; diff --git a/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx b/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx index 25b9d48d4637f..9163319e07211 100644 --- a/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx +++ b/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx @@ -51,15 +51,12 @@ import { useKibana } from '@kbn/kibana-react-plugin/public'; import { SplitButton } from '@kbn/split-button'; import { useMemoCss } from '@kbn/css-utils/public/use_memo_css'; import { QueryStringInput } from '@kbn/kql/public'; +import { FilterButtonGroup } from '@kbn/kql/public'; +import type { SuggestionsAbstraction, SuggestionsListSize } from '@kbn/kql/public'; import { AddFilterPopover } from './add_filter_popover'; import type { DataViewPickerProps } from '../dataview_picker'; import { DataViewPicker } from '../dataview_picker'; -import { FilterButtonGroup } from '../filter_bar/filter_button_group/filter_button_group'; import { NoDataPopover } from './no_data_popover'; -import type { - SuggestionsAbstraction, - SuggestionsListSize, -} from '../typeahead/suggestions_component'; import type { IUnifiedSearchPluginServices, UnifiedSearchDraft } from '../types'; import { shallowEqual } from '../utils/shallow_equal'; @@ -321,7 +318,7 @@ export const QueryBarTopRow = React.memo( appName, data, usageCollection, - unifiedSearch, + kql, notifications, docLinks, http, @@ -885,7 +882,7 @@ export const QueryBarTopRow = React.memo( submitOnBlur={props.submitOnBlur} bubbleSubmitEvent={props.bubbleSubmitEvent} deps={{ - unifiedSearch, + autocomplete: kql.autocomplete, data, storage, usageCollection, diff --git a/src/platform/plugins/shared/unified_search/public/query_string_input/query_string_input.styles.tsx b/src/platform/plugins/shared/unified_search/public/query_string_input/query_string_input.styles.tsx deleted file mode 100644 index 88b7e779e1835..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/query_string_input/query_string_input.styles.tsx +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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 React from 'react'; -import type { UseEuiTheme } from '@elastic/eui'; -import { euiThemeVars } from '@kbn/ui-theme'; -import { css } from '@emotion/react'; -import { useMemoCss } from '../use_memo_css'; - -const queryStringInputStyles = { - container: ({ euiTheme }: UseEuiTheme) => - css({ - width: '100%', - zIndex: euiThemeVars.euiZContentMenu, - height: euiTheme.size.xl, - display: 'flex', - '> [aria-expanded="true"]': { - // Using filter allows it to adhere the children's bounds - filter: `drop-shadow(0 ${euiTheme.size.s} ${euiTheme.size.base} rgba(${euiTheme.colors.shadow}, 0.05))`, - }, - '.kbnQueryBar__textareaWrapOuter': { - position: 'relative', - width: '100%', - zIndex: euiTheme.levels.flyout, - }, - '.kbnQueryBar__textareaWrap': { - position: 'relative', - overflow: 'visible !important', // Override EUI form control - display: 'flex', - flex: '1 1 100%', - '&.kbnQueryBar__textareaWrap--withSuggestionVisible .kbnQueryBar__textarea': { - borderBottomRightRadius: 0, - borderBottomLeftRadius: 0, - }, - '> .euiFormControlLayoutIcons': { - maxHeight: euiTheme.size.xxl, - }, - }, - '.kbnQueryBar__textarea': { - zIndex: euiTheme.levels.content, - height: euiTheme.size.xl, - // Unlike most inputs within layout control groups, the text area still needs a border - // for multi-line content. These adjusts help it sit above the control groups - // shadow to line up correctly. - padding: euiTheme.size.xs, - paddingTop: `calc(${euiTheme.size.xs} + 2px)`, - paddingLeft: euiTheme.size.xxl, // Account for search icon - // Firefox adds margin to textarea - margin: 0, - - '&.kbnQueryBar__textarea--isClearable': { - paddingRight: euiTheme.size.xxl, // Account for clear button - }, - - '&:not(.kbnQueryBar__textarea--autoHeight)': { - overflowY: 'hidden', - overflowX: 'hidden', - }, - - // When focused, let it scroll - '&.kbnQueryBar__textarea--autoHeight': { - overflowX: 'auto', - overflowY: 'auto', - whiteSpace: `pre-wrap`, - maxHeight: `calc(35vh - 100px)`, - minHeight: euiTheme.size.xl, - }, - - '~.euiFormControlLayoutIcons': { - // By default form control layout icon is vertically centered, but our textarea - // can expand to be multi-line, so we position it with padding that matches - // the parent textarea padding - zIndex: euiTheme.levels.flyout, - top: euiTheme.size.m, - bottom: 'unset', - }, - - '&.kbnQueryBar__textarea--withPrepend': { - borderTopLeftRadius: 0, - borderBottomLeftRadius: 0, - marginLeft: '-1px', - width: 'calc(100% + 1px)', - }, - }, - }), -}; - -export const StyledDiv = ({ children, ...props }: React.HTMLAttributes) => { - const styles = useMemoCss(queryStringInputStyles); - return ( -
- {children} -
- ); -}; diff --git a/src/platform/plugins/shared/unified_search/public/query_string_input/query_string_input.test.mocks.ts b/src/platform/plugins/shared/unified_search/public/query_string_input/query_string_input.test.mocks.ts deleted file mode 100644 index 60ba2e3f45bd2..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/query_string_input/query_string_input.test.mocks.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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 { stubIndexPattern } from '@kbn/data-plugin/public/stubs'; - -export const mockPersistedLog = { - add: jest.fn(), - get: jest.fn(() => ['response:200']), -}; - -export const mockPersistedLogFactory = jest.fn, any>(() => { - return mockPersistedLog; -}); - -export const mockFetchIndexPatterns = jest - .fn() - .mockReturnValue(Promise.resolve([stubIndexPattern])); - -jest.mock('@kbn/data-plugin/public/query/persisted_log', () => ({ - PersistedLog: mockPersistedLogFactory, -})); - -jest.mock('./fetch_index_patterns', () => ({ - fetchIndexPatterns: mockFetchIndexPatterns, -})); - -import _ from 'lodash'; -// Using doMock to avoid hoisting so that I can override only the debounce method in lodash -jest.doMock('lodash', () => ({ - ..._, - debounce: (func: () => any) => func, -})); diff --git a/src/platform/plugins/shared/unified_search/public/query_string_input/query_string_input.test.tsx b/src/platform/plugins/shared/unified_search/public/query_string_input/query_string_input.test.tsx deleted file mode 100644 index 9146cd6de53fd..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/query_string_input/query_string_input.test.tsx +++ /dev/null @@ -1,449 +0,0 @@ -/* - * 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 { - mockFetchIndexPatterns, - mockPersistedLog, - mockPersistedLogFactory, -} from './query_string_input.test.mocks'; - -import React from 'react'; -import { I18nProvider } from '@kbn/i18n-react'; -import { waitFor, render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; - -import { coreMock } from '@kbn/core/public/mocks'; -import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; -import { stubIndexPattern } from '@kbn/data-plugin/public/stubs'; -import { QueryStringInput } from './query_string_input'; -import { unifiedSearchPluginMock } from '../mocks'; - -jest.useFakeTimers({ legacyFakeTimers: true }); - -const startMock = coreMock.createStart(); - -const noop = () => { - return; -}; - -const kqlQuery = { - query: 'response:200', - language: 'kuery', -}; - -const luceneQuery = { - query: 'response:200', - language: 'lucene', -}; - -const createMockWebStorage = () => ({ - clear: jest.fn(), - getItem: jest.fn(), - key: jest.fn(), - removeItem: jest.fn(), - setItem: jest.fn(), - length: 0, -}); - -const createMockStorage = () => ({ - storage: createMockWebStorage(), - get: jest.fn(), - set: jest.fn(), - remove: jest.fn(), - clear: jest.fn(), -}); - -function wrapQueryStringInputInContext(testProps: any, storage?: any) { - const mockDataPlugin = dataPluginMock.createStartContract(); - - const defaultOptions = { - screenTitle: 'Another Screen', - intl: null as any, - deps: { - unifiedSearch: unifiedSearchPluginMock.createStartContract(), - data: mockDataPlugin, - appName: testProps.appName || 'test', - storage: storage || createMockStorage(), - usageCollection: { reportUiCounter: () => {} }, - uiSettings: startMock.uiSettings, - http: startMock.http, - docLinks: startMock.docLinks, - }, - }; - - return ( - - - - ); -} - -describe('QueryStringInput', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('Should render the given query', async () => { - const { getByText } = render( - wrapQueryStringInputInContext({ - query: kqlQuery, - onSubmit: noop, - indexPatterns: [stubIndexPattern], - }) - ); - - await waitFor(() => getByText(kqlQuery.query), { timeout: 3000 }); - }); - - it('Should pass the query language to the language switcher', async () => { - render( - wrapQueryStringInputInContext({ - query: luceneQuery, - onSubmit: noop, - indexPatterns: [stubIndexPattern], - }) - ); - - await waitFor(() => { - expect(screen.getByText(luceneQuery.query)).toBeInTheDocument(); - }); - }); - - it('Should disable autoFocus on EuiTextArea when disableAutoFocus prop is true', async () => { - render( - wrapQueryStringInputInContext({ - query: kqlQuery, - onSubmit: noop, - indexPatterns: [stubIndexPattern], - disableAutoFocus: true, - }) - ); - - await waitFor(() => { - expect(screen.getByDisplayValue(kqlQuery.query)).toBeInTheDocument(); - const textarea = document.querySelector('textarea'); - expect(textarea).not.toHaveAttribute('autofocus'); - }); - }); - - it('Should create a unique PersistedLog based on the appName and query language', async () => { - mockPersistedLogFactory.mockClear(); - - render( - wrapQueryStringInputInContext({ - query: kqlQuery, - onSubmit: noop, - indexPatterns: [stubIndexPattern], - disableAutoFocus: true, - appName: 'discover', - }) - ); - - await waitFor(() => { - expect(mockPersistedLogFactory.mock.calls[0][0]).toBe('typeahead:discover-kuery'); - }); - }); - - it("On language selection, should store the user's preference in localstorage and reset the query", async () => { - const mockStorage = createMockStorage(); - const mockCallback = jest.fn(); - - render( - wrapQueryStringInputInContext( - { - query: kqlQuery, - onSubmit: mockCallback, - indexPatterns: [stubIndexPattern], - disableAutoFocus: true, - appName: 'discover', - }, - mockStorage - ) - ); - - await waitFor(() => { - expect(screen.getByTestId('switchQueryLanguageButton')).toBeInTheDocument(); - expect(screen.getByDisplayValue(kqlQuery.query)).toBeInTheDocument(); - }); - - expect(mockStorage).toBeDefined(); - expect(screen.getByTestId('switchQueryLanguageButton')).toBeInTheDocument(); - }); - - it('Should not show the language switcher when disabled', async () => { - render( - wrapQueryStringInputInContext({ - query: luceneQuery, - onSubmit: noop, - indexPatterns: [stubIndexPattern], - disableLanguageSwitcher: true, - }) - ); - - await waitFor(() => { - expect(screen.getByDisplayValue(luceneQuery.query)).toBeInTheDocument(); - expect(screen.queryByTestId('switchQueryLanguageButton')).not.toBeInTheDocument(); - }); - }); - - it('Should show an icon when an iconType is specified', async () => { - render( - wrapQueryStringInputInContext({ - query: luceneQuery, - onSubmit: noop, - indexPatterns: [stubIndexPattern], - iconType: 'search', - }) - ); - - await waitFor(() => { - expect(screen.getByDisplayValue(luceneQuery.query)).toBeInTheDocument(); - const icon = document.querySelector('[data-euiicon-type="search"]'); - expect(icon).toBeInTheDocument(); - }); - }); - - it('Should call onSubmit when the user hits enter inside the query bar', async () => { - const mockCallback = jest.fn(); - - render( - wrapQueryStringInputInContext({ - query: kqlQuery, - onSubmit: mockCallback, - indexPatterns: [stubIndexPattern], - disableAutoFocus: true, - }) - ); - - const textarea = await screen.findByDisplayValue(kqlQuery.query); - expect(textarea).toBeInTheDocument(); - expect(mockCallback).toBeDefined(); - }); - - it('Should fire onBlur callback on input blur', async () => { - const mockCallback = jest.fn(); - const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); - - render( - wrapQueryStringInputInContext({ - query: kqlQuery, - onBlur: mockCallback, - indexPatterns: [stubIndexPattern], - disableAutoFocus: true, - }) - ); - - const textarea = screen.getByDisplayValue(kqlQuery.query); - await user.click(textarea); - await user.tab(); - - expect(mockCallback).toHaveBeenCalledTimes(1); - expect(mockCallback).toHaveBeenCalledWith(); - }); - - it('Should fire onChangeQueryInputFocus after a delay', async () => { - const mockCallback = jest.fn(); - - render( - wrapQueryStringInputInContext({ - query: kqlQuery, - onChangeQueryInputFocus: mockCallback, - indexPatterns: [stubIndexPattern], - disableAutoFocus: true, - }) - ); - - const textarea = await screen.findByDisplayValue(kqlQuery.query); - expect(mockCallback).toBeDefined(); - expect(textarea).toBeInTheDocument(); - }); - - it('Should not fire onChangeQueryInputFocus if input is focused back', async () => { - const mockCallback = jest.fn(); - const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); - - render( - wrapQueryStringInputInContext({ - query: kqlQuery, - onChangeQueryInputFocus: mockCallback, - indexPatterns: [stubIndexPattern], - disableAutoFocus: true, - }) - ); - - mockCallback.mockClear(); - - const textarea = screen.getByDisplayValue(kqlQuery.query); - await user.click(textarea); - await user.tab(); - - jest.advanceTimersByTime(5); - const callCountAfterBlur = mockCallback.mock.calls.length; - - await user.click(textarea); - expect(mockCallback).toHaveBeenCalledWith(true); - - jest.advanceTimersByTime(100); - const finalCallCount = mockCallback.mock.calls.length; - expect(finalCallCount).toBeGreaterThanOrEqual(callCountAfterBlur + 1); - }); - - it('Should call onSubmit after a delay when submitOnBlur is on and blurs input', async () => { - const mockCallback = jest.fn(); - - render( - wrapQueryStringInputInContext({ - query: kqlQuery, - onSubmit: mockCallback, - indexPatterns: [stubIndexPattern], - disableAutoFocus: true, - submitOnBlur: true, - }) - ); - const textarea = await screen.findByDisplayValue(kqlQuery.query); - expect(textarea).toBeInTheDocument(); - expect(mockCallback).toBeDefined(); - }); - - it("Shouldn't call onSubmit on blur by default", async () => { - const mockCallback = jest.fn(); - const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); - - render( - wrapQueryStringInputInContext({ - query: kqlQuery, - onSubmit: mockCallback, - indexPatterns: [stubIndexPattern], - disableAutoFocus: true, - }) - ); - - const textarea = screen.getByDisplayValue(kqlQuery.query); - - await user.click(textarea); - await user.tab(); - - jest.advanceTimersByTime(100); - expect(mockCallback).toHaveBeenCalledTimes(0); - }); - - it('Should use PersistedLog for recent search suggestions', async () => { - const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); - - render( - wrapQueryStringInputInContext({ - query: kqlQuery, - onSubmit: noop, - indexPatterns: [stubIndexPattern], - disableAutoFocus: true, - persistedLog: mockPersistedLog, - }) - ); - - const textarea = await screen.findByDisplayValue(kqlQuery.query); - expect(mockPersistedLog.get).toHaveBeenCalled(); - - mockPersistedLog.get.mockClear(); - - await user.clear(textarea); - await user.type(textarea, 'extensi'); - - await waitFor(() => { - expect(mockPersistedLog.get).toHaveBeenCalled(); - }); - }); - - it('Should accept index pattern strings and fetch the full object', async () => { - const patternStrings = ['logstash-*']; - mockFetchIndexPatterns.mockClear(); - - render( - wrapQueryStringInputInContext({ - query: kqlQuery, - onSubmit: noop, - indexPatterns: patternStrings, - disableAutoFocus: true, - }) - ); - - await waitFor(() => { - expect(mockFetchIndexPatterns.mock.calls[0][1]).toEqual( - patternStrings.map((value) => ({ type: 'title', value })) - ); - }); - }); - - it('Should accept index pattern ids and fetch the full object', async () => { - const idStrings = [{ type: 'id', value: '1' }]; - mockFetchIndexPatterns.mockClear(); - - render( - wrapQueryStringInputInContext({ - query: kqlQuery, - onSubmit: noop, - indexPatterns: idStrings, - disableAutoFocus: true, - }) - ); - - await waitFor(() => { - expect(mockFetchIndexPatterns.mock.calls[0][1]).toEqual(idStrings); - }); - }); - - it('Should accept a mix of full objects, title and ids and fetch only missing index pattern objects', async () => { - const patternStrings = [ - 'logstash-*', - { type: 'id', value: '1' }, - { type: 'title', value: 'my-fake-index-pattern' }, - stubIndexPattern, - ]; - mockFetchIndexPatterns.mockClear(); - - render( - wrapQueryStringInputInContext({ - query: kqlQuery, - onSubmit: noop, - indexPatterns: patternStrings, - disableAutoFocus: true, - }) - ); - - await waitFor(() => { - expect(mockFetchIndexPatterns.mock.calls[0][1]).toEqual([ - { type: 'title', value: 'logstash-*' }, - { type: 'id', value: '1' }, - { type: 'title', value: 'my-fake-index-pattern' }, - ]); - }); - }); - - it('Should convert non-breaking spaces into regular spaces', async () => { - const mockCallback = jest.fn(); - const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); - - render( - wrapQueryStringInputInContext({ - query: { query: '', language: 'kuery' }, - onChange: mockCallback, - indexPatterns: [stubIndexPattern], - disableAutoFocus: true, - }) - ); - - const textarea = screen.getByRole('textbox'); - expect(textarea).toBeInTheDocument(); - - await user.type(textarea, 'test'); - - await waitFor(() => { - expect(mockCallback).toHaveBeenCalled(); - }); - }); -}); diff --git a/src/platform/plugins/shared/unified_search/public/query_string_input/query_string_input.tsx b/src/platform/plugins/shared/unified_search/public/query_string_input/query_string_input.tsx deleted file mode 100644 index da52a43cbf70a..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/query_string_input/query_string_input.tsx +++ /dev/null @@ -1,972 +0,0 @@ -/* - * 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 React, { PureComponent } from 'react'; -import classNames from 'classnames'; -import { METRIC_TYPE } from '@kbn/analytics'; - -import type { EuiIconProps, PopoverAnchorPosition } from '@elastic/eui'; -import { - EuiButton, - EuiFlexGroup, - EuiFlexItem, - EuiFormControlLayoutIcons, - EuiLink, - EuiOutsideClickDetector, - EuiPortal, - EuiTextArea, - htmlIdGenerator, - toSentenceCase, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { compact, debounce, isEmpty, isEqual, isFunction, partition } from 'lodash'; -import type { CoreStart, DocLinksStart, Toast } from '@kbn/core/public'; -import type { Query, Filter } from '@kbn/es-query'; -import { getQueryLog } from '@kbn/data-plugin/public'; -import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; -import { type DataView } from '@kbn/data-views-plugin/public'; -import type { PersistedLog, DataPublicPluginStart } from '@kbn/data-plugin/public'; -import { - getFieldSubtypeNested, - KIBANA_USER_QUERY_LANGUAGE_KEY, - KQL_TELEMETRY_ROUTE_LATEST_VERSION, -} from '@kbn/data-plugin/common'; -import { toMountPoint } from '@kbn/react-kibana-mount'; -import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; -import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; -import { buildQueryFromFilters } from '@kbn/es-query'; -import { matchPairs } from './match_pairs'; -import { toUser } from './to_user'; -import { fromUser } from './from_user'; -import { type DataViewByIdOrTitle, fetchIndexPatterns } from './fetch_index_patterns'; -import { QueryLanguageSwitcher } from './language_switcher'; -import type { - SuggestionsAbstraction, - SuggestionsListSize, -} from '../typeahead/suggestions_component'; -import { SuggestionsComponent } from '../typeahead'; -import { onRaf } from '../utils'; -import { FilterButtonGroup } from '../filter_bar/filter_button_group/filter_button_group'; -import type { AutocompleteService, QuerySuggestion } from '../autocomplete'; -import { QuerySuggestionTypes } from '../autocomplete'; -import { getCoreStart } from '../services'; -import { StyledDiv } from './query_string_input.styles'; - -export const strings = { - getSearchInputPlaceholderForText: () => - i18n.translate('unifiedSearch.query.queryBar.searchInputPlaceholderForText', { - defaultMessage: 'Filter your data', - }), - getSearchInputPlaceholder: (language: string) => - i18n.translate('unifiedSearch.query.queryBar.searchInputPlaceholder', { - defaultMessage: 'Filter your data using {language} syntax', - values: { language }, - }), - getQueryBarComboboxAriaLabel: (pageType: string) => - i18n.translate('unifiedSearch.query.queryBar.comboboxAriaLabel', { - defaultMessage: 'Search and filter the {pageType} page', - values: { pageType }, - }), - getQueryBarSearchInputAriaLabel: (pageType: string) => - i18n.translate('unifiedSearch.query.queryBar.searchInputAriaLabel', { - defaultMessage: 'Start typing to search and filter the {pageType} page', - values: { pageType }, - }), - getQueryBarClearInputLabel: () => - i18n.translate('unifiedSearch.query.queryBar.clearInputLabel', { - defaultMessage: 'Clear input', - }), - getKQLNestedQuerySyntaxInfoTitle: () => - i18n.translate('unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoTitle', { - defaultMessage: 'KQL nested query syntax', - }), -}; - -export interface QueryStringInputDependencies { - unifiedSearch: { - autocomplete: ReturnType; - }; - usageCollection?: UsageCollectionStart; - data: DataPublicPluginStart; - storage: IStorageWrapper; - notifications: CoreStart['notifications']; - http: CoreStart['http']; - docLinks: DocLinksStart; - uiSettings: CoreStart['uiSettings']; - dataViews: DataViewsPublicPluginStart; -} - -export interface QueryStringInputProps { - indexPatterns: Array; - query: Query; - disableAutoFocus?: boolean; - screenTitle?: string; - prepend?: any; - persistedLog?: PersistedLog; - bubbleSubmitEvent?: boolean; - placeholder?: string; - disableLanguageSwitcher?: boolean; - languageSwitcherPopoverAnchorPosition?: PopoverAnchorPosition; - onBlur?: () => void; - onChange?: (query: Query) => void; - onChangeQueryInputFocus?: (isFocused: boolean) => void; - onSubmit?: (query: Query) => void; - submitOnBlur?: boolean; - dataTestSubj?: string; - size?: SuggestionsListSize; - suggestionsAbstraction?: SuggestionsAbstraction; - className?: string; - isInvalid?: boolean; - isClearable?: boolean; - iconType?: EuiIconProps['type']; - isDisabled?: boolean; - appName: string; - deps: QueryStringInputDependencies; - - /** - * @param nonKqlMode by default if language switch is enabled, user can switch between kql and lucene syntax mode - * this params add another option text, which is just a simple keyword search mode, the way a simple search box works - */ - nonKqlMode?: 'lucene' | 'text'; - /** - * @param autoSubmit if user selects a value, in that case kuery will be auto submitted - */ - autoSubmit?: boolean; - /** - * @param storageKey this key is used to use user preference between kql and non-kql mode - */ - storageKey?: string; - - /** - * Override whether autocomplete suggestions are restricted by time range. - */ - timeRangeForSuggestionsOverride?: boolean; - - /** - * Add additional filters used for suggestions - */ - filtersForSuggestions?: Filter[]; -} - -interface State { - isSuggestionsVisible: boolean; - index: number | null; - suggestions: QuerySuggestion[]; - suggestionLimit: number; - selectionStart: number | null; - selectionEnd: number | null; - indexPatterns: DataView[]; - - /** - * Part of state because passed down to child components - */ - queryBarInputDiv: HTMLDivElement | null; -} - -const KEY_CODES = { - LEFT: 37, - UP: 38, - RIGHT: 39, - DOWN: 40, - ENTER: 13, - ESC: 27, - TAB: 9, - HOME: 36, - END: 35, -}; - -export class QueryStringInput extends PureComponent { - static defaultProps = { - storageKey: KIBANA_USER_QUERY_LANGUAGE_KEY, - iconType: 'search', - isClearable: true, - }; - - public state: State = { - isSuggestionsVisible: false, - index: null, - suggestions: [], - suggestionLimit: 50, - selectionStart: null, - selectionEnd: null, - indexPatterns: [], - queryBarInputDiv: null, - }; - - public inputRef: HTMLTextAreaElement | null = null; - - private persistedLog: PersistedLog | undefined; - private abortController?: AbortController; - private fetchIndexPatternsAbortController?: AbortController; - - private reportUiCounter = this.props.deps.usageCollection?.reportUiCounter.bind( - this.props.deps.usageCollection, - this.props.appName - ); - private componentIsUnmounting = false; - private hasScrollListener = false; - - /** - * If any element within the container is currently focused - * @internal - */ - private isFocusWithin = false; - - private getQueryString = () => { - return toUser(this.props.query.query); - }; - - private fetchIndexPatterns = debounce(async () => { - const [objectPatterns = [], stringPatterns = []] = partition< - QueryStringInputProps['indexPatterns'][number], - DataView - >(this.props.indexPatterns || [], (indexPattern): indexPattern is DataView => { - return ( - typeof indexPattern === 'object' && - Object.hasOwn(indexPattern, 'fields') && - Object.hasOwn(indexPattern, 'title') - ); - }); - const idOrTitlePatterns = stringPatterns.map((sp) => - typeof sp === 'string' ? { type: 'title', value: sp } : sp - ) as DataViewByIdOrTitle[]; - - // abort the previous fetch to avoid overriding with outdated data - // issue https://github.com/elastic/kibana/issues/80831 - if (this.fetchIndexPatternsAbortController) this.fetchIndexPatternsAbortController.abort(); - this.fetchIndexPatternsAbortController = new AbortController(); - const currentAbortController = this.fetchIndexPatternsAbortController; - - const objectPatternsFromStrings = await fetchIndexPatterns( - this.props.deps.data.dataViews, - idOrTitlePatterns - ); - - if (!currentAbortController.signal.aborted) { - this.setState({ - indexPatterns: [...objectPatterns, ...objectPatternsFromStrings], - }); - - this.updateSuggestions(); - } - }, 200); - - private getSuggestions = async () => { - if (!this.inputRef) { - return; - } - - const language = this.props.query.language; - const queryString = this.getQueryString(); - - const recentSearchSuggestions = this.getRecentSearchSuggestions(queryString); - - const hasQuerySuggestions = - this.props.deps.unifiedSearch.autocomplete.hasQuerySuggestions(language); - - if ( - !hasQuerySuggestions || - !Array.isArray(this.state.indexPatterns) || - compact(this.state.indexPatterns).length === 0 - ) { - return recentSearchSuggestions; - } - - const indexPatterns = this.state.indexPatterns; - - const { selectionStart, selectionEnd } = this.inputRef; - if (selectionStart === null || selectionEnd === null) { - return; - } - - try { - if (this.abortController) this.abortController.abort(); - this.abortController = new AbortController(); - const suggestions = - (await this.props.deps.unifiedSearch.autocomplete.getQuerySuggestions({ - language, - indexPatterns, - query: queryString, - selectionStart, - selectionEnd, - signal: this.abortController.signal, - useTimeRange: this.props.timeRangeForSuggestionsOverride, - boolFilter: buildQueryFromFilters(this.props.filtersForSuggestions, undefined).filter, - method: this.props.filtersForSuggestions?.length ? 'terms_agg' : undefined, - suggestionsAbstraction: this.props.suggestionsAbstraction, - })) || []; - return [...suggestions, ...recentSearchSuggestions]; - } catch (e) { - // TODO: Waiting on https://github.com/elastic/kibana/issues/51406 for a properly typed error - // Ignore aborted requests - if (e.message === 'The user aborted a request.') return; - - this.reportUiCounter?.(METRIC_TYPE.LOADED, `query_string:suggestions_error`); - - throw e; - } - }; - - private getRecentSearchSuggestions = (query: string) => { - if (!this.persistedLog) { - return []; - } - const recentSearches = this.persistedLog.get(); - const matchingRecentSearches = recentSearches.filter((recentQuery) => { - const recentQueryString = typeof recentQuery === 'object' ? toUser(recentQuery) : recentQuery; - return recentQueryString !== '' && recentQueryString.includes(query); - }); - return matchingRecentSearches.map((recentSearch) => { - const text = toUser(recentSearch); - const start = 0; - const end = query.length; - return { type: QuerySuggestionTypes.RecentSearch, text, start, end }; - }); - }; - - private updateSuggestions = debounce(async () => { - const suggestions = (await this.getSuggestions()) || []; - if (!this.componentIsUnmounting) { - this.setState({ suggestions }); - } - }, 100); - - private onSubmit = (query: Query) => { - if (this.props.onSubmit) { - if (this.persistedLog) { - this.persistedLog.add(query.query); - } - - this.props.onSubmit({ query: fromUser(query.query), language: query.language }); - } - }; - - private onChange = (query: Query) => { - this.updateSuggestions(); - - if (this.props.onChange) { - this.props.onChange({ query: fromUser(query.query), language: query.language }); - } - }; - - private onQueryStringChange = (value: string) => { - this.setState({ - isSuggestionsVisible: true, - index: null, - suggestionLimit: 50, - }); - - if (this.props.query.query !== value) { - this.onChange({ query: value, language: this.props.query.language }); - } - }; - - private onInputChange = (event: React.ChangeEvent) => { - const value = this.formatTextAreaValue(event.target.value); - this.onQueryStringChange(value); - if (event.target.value === '') { - this.handleRemoveHeight(); - } else { - this.handleAutoHeight(); - } - }; - - private onClickInput = (event: React.MouseEvent) => { - if (event.target instanceof HTMLTextAreaElement) { - const value = this.formatTextAreaValue(event.target.value); - this.onQueryStringChange(value); - } - }; - - private onKeyUp = (event: React.KeyboardEvent) => { - if ([KEY_CODES.LEFT, KEY_CODES.RIGHT, KEY_CODES.HOME, KEY_CODES.END].includes(event.keyCode)) { - this.setState({ isSuggestionsVisible: true }); - if (event.target instanceof HTMLTextAreaElement) { - const value = this.formatTextAreaValue(event.target.value); - this.onQueryStringChange(value); - } - } - }; - - private onKeyDown = (event: React.KeyboardEvent) => { - if (event.target instanceof HTMLTextAreaElement) { - const { isSuggestionsVisible, index } = this.state; - const preventDefault = event.preventDefault.bind(event); - const { target, key, metaKey } = event; - const { value, selectionStart, selectionEnd } = target; - const updateQuery = (query: string, newSelectionStart: number, newSelectionEnd: number) => { - this.onQueryStringChange(query); - - if ( - this.inputRef?.selectionStart !== newSelectionStart || - this.inputRef?.selectionEnd !== newSelectionEnd - ) { - this.setState({ - selectionStart: newSelectionStart, - selectionEnd: newSelectionEnd, - }); - } - }; - - switch (event.keyCode) { - case KEY_CODES.DOWN: - if (isSuggestionsVisible && index !== null) { - event.preventDefault(); - this.incrementIndex(index); - // Note to engineers. `isSuggestionVisible` does not mean the suggestions are visible. - // This should likely be fixed, it's more that suggestions can be shown. - } else if ((isSuggestionsVisible && index == null) || this.getQueryString() === '') { - event.preventDefault(); - this.setState({ isSuggestionsVisible: true, index: 0 }); - } - break; - case KEY_CODES.UP: - if (isSuggestionsVisible && index !== null) { - event.preventDefault(); - this.decrementIndex(index); - } - break; - case KEY_CODES.ENTER: - if (!this.props.bubbleSubmitEvent) { - event.preventDefault(); - } - if (isSuggestionsVisible && index !== null && this.state.suggestions[index]) { - event.preventDefault(); - this.selectSuggestion(this.state.suggestions[index], index); - } else { - this.onSubmit(this.props.query); - this.setState({ - isSuggestionsVisible: false, - }); - } - break; - case KEY_CODES.ESC: - if (isSuggestionsVisible) { - event.preventDefault(); - } - this.setState({ isSuggestionsVisible: false, index: null }); - break; - case KEY_CODES.TAB: - this.setState({ isSuggestionsVisible: false, index: null }); - break; - default: - if (selectionStart !== null && selectionEnd !== null) { - matchPairs({ - value, - selectionStart, - selectionEnd, - key, - metaKey, - updateQuery, - preventDefault, - }); - } - - break; - } - } - }; - - private selectSuggestion = (suggestion: QuerySuggestion, listIndex: number) => { - if (!this.inputRef) { - return; - } - const { type, text, start, end, cursorIndex } = suggestion; - - this.handleNestedFieldSyntaxNotification(suggestion); - - const query = this.getQueryString(); - const { selectionStart, selectionEnd } = this.inputRef; - if (selectionStart === null || selectionEnd === null) { - return; - } - - const value = query.substr(0, selectionStart) + query.substr(selectionEnd); - const newQueryString = value.substr(0, start) + text + value.substr(end); - - this.reportUiCounter?.( - METRIC_TYPE.CLICK, - `query_string:${type}:suggestions_select_position_${listIndex}` - ); - this.reportUiCounter?.( - METRIC_TYPE.CLICK, - `query_string:${type}:suggestions_select_q_length_${end - start}` - ); - - this.onQueryStringChange(newQueryString); - - this.setState({ - selectionStart: start + (cursorIndex ? cursorIndex : text.length), - selectionEnd: start + (cursorIndex ? cursorIndex : text.length), - }); - const isTypeRecentSearch = type === QuerySuggestionTypes.RecentSearch; - - const isAutoSubmitAndValid = - this.props.autoSubmit && - (type === QuerySuggestionTypes.Value || [':*', ': *'].includes(value.trim())); - - if (isTypeRecentSearch || isAutoSubmitAndValid) { - this.setState({ isSuggestionsVisible: false, index: null }); - this.onSubmit({ query: newQueryString, language: this.props.query.language }); - } - }; - - private handleNestedFieldSyntaxNotification = (suggestion: QuerySuggestion) => { - const subTypeNested = 'field' in suggestion && getFieldSubtypeNested(suggestion.field); - if ( - subTypeNested && - subTypeNested.nested && - !this.props.deps.storage.get('kibana.KQLNestedQuerySyntaxInfoOptOut') - ) { - const { notifications, docLinks } = this.props.deps; - - const onKQLNestedQuerySyntaxInfoOptOut = (toast: Toast) => { - if (!this.props.deps.storage) return; - this.props.deps.storage.set('kibana.KQLNestedQuerySyntaxInfoOptOut', true); - notifications!.toasts.remove(toast); - }; - - if (notifications && docLinks) { - const toast = notifications.toasts.add({ - title: strings.getKQLNestedQuerySyntaxInfoTitle(), - text: toMountPoint( -
-

- - - - ), - }} - /> -

- - - onKQLNestedQuerySyntaxInfoOptOut(toast)}> - - - - -
, - getCoreStart() - ), - }); - } - } - }; - - private increaseLimit = () => { - this.setState({ - suggestionLimit: this.state.suggestionLimit + 50, - }); - }; - - private incrementIndex = (currentIndex: number) => { - let nextIndex = currentIndex + 1; - if (currentIndex === null || nextIndex >= this.state.suggestions.length) { - nextIndex = 0; - } - this.setState({ index: nextIndex }); - }; - - private decrementIndex = (currentIndex: number) => { - const previousIndex = currentIndex - 1; - if (previousIndex < 0) { - this.setState({ index: this.state.suggestions.length - 1 }); - } else { - this.setState({ index: previousIndex }); - } - }; - - private onSelectLanguage = (language: string) => { - // Send telemetry info every time the user opts in or out of kuery - // As a result it is important this function only ever gets called in the - // UI component's change handler. - this.props.deps.http.post('/internal/kql_opt_in_stats', { - version: KQL_TELEMETRY_ROUTE_LATEST_VERSION, - body: JSON.stringify({ opt_in: language === 'kuery' }), - }); - - const storageKey = this.props.storageKey; - this.props.deps.storage.set(storageKey!, language); - - const newQuery = { query: '', language }; - this.onChange(newQuery); - this.onSubmit(newQuery); - this.reportUiCounter?.( - METRIC_TYPE.LOADED, - storageKey ? `${storageKey}:language:${language}` : `query_string:language:${language}` - ); - }; - - private onOutsideClick = () => { - if (this.state.isSuggestionsVisible) { - this.setState({ isSuggestionsVisible: false, index: null }); - this.scheduleOnInputBlur(); - } - }; - - private blurTimeoutHandle: number | undefined; - /** - * Notify parent about input's blur after a delay only - * if the focus didn't get back inside the input container - * and if suggestions were closed - * https://github.com/elastic/kibana/issues/92040 - */ - private scheduleOnInputBlur = () => { - clearTimeout(this.blurTimeoutHandle); - this.blurTimeoutHandle = window.setTimeout(() => { - if (!this.isFocusWithin && !this.state.isSuggestionsVisible && !this.componentIsUnmounting) { - this.handleBlurHeight(); - if (this.props.onChangeQueryInputFocus) { - this.props.onChangeQueryInputFocus(false); - } - - if (this.props.submitOnBlur) { - this.onSubmit(this.props.query); - } - } - }, 50); - }; - - private onInputBlur = () => { - if (isFunction(this.props.onBlur)) { - this.props.onBlur(); - } - }; - - private handleResize = () => { - this.handleAutoHeight(); - this.handleBlurOnScroll(); - }; - - private onClickSuggestion = (suggestion: QuerySuggestion, index: number) => { - if (!this.inputRef) { - return; - } - this.selectSuggestion(suggestion, index); - this.inputRef.focus(); - }; - - private initPersistedLog = () => { - const { uiSettings } = this.props.deps; - const { appName } = this.props; - this.persistedLog = this.props.persistedLog - ? this.props.persistedLog - : getQueryLog(uiSettings, this.props.deps.storage, appName, this.props.query.language); - }; - - public onMouseEnterSuggestion = (_suggestion: QuerySuggestion, index: number) => { - this.setState({ index }); - }; - - textareaId = htmlIdGenerator()(); - - public componentDidMount() { - const parsedQuery = fromUser(toUser(this.props.query.query)); - if (!isEqual(this.props.query.query, parsedQuery)) { - this.onChange({ ...this.props.query, query: parsedQuery }); - } - - this.initPersistedLog(); - this.fetchIndexPatterns(); - this.handleAutoHeight(); - - window.addEventListener('resize', this.handleResize); - - this.handleBlurOnScroll(); - } - - public componentDidUpdate(prevProps: QueryStringInputProps) { - const parsedQuery = fromUser(toUser(this.props.query.query)); - if (!isEqual(this.props.query.query, parsedQuery)) { - this.onChange({ ...this.props.query, query: parsedQuery }); - } - - this.initPersistedLog(); - - if (!isEqual(prevProps.indexPatterns, this.props.indexPatterns)) { - this.fetchIndexPatterns(); - } else if (!isEqual(prevProps.query, this.props.query)) { - this.updateSuggestions(); - } - - if (this.state.selectionStart !== null && this.state.selectionEnd !== null) { - if (this.inputRef != null) { - this.inputRef.setSelectionRange(this.state.selectionStart, this.state.selectionEnd); - } - this.setState({ - selectionStart: null, - selectionEnd: null, - }); - } - - if (document.activeElement !== null && document.activeElement.id === this.textareaId) { - this.handleAutoHeight(); - } else { - this.handleRemoveHeight(); - } - } - - public componentWillUnmount() { - if (this.abortController) this.abortController.abort(); - if (this.updateSuggestions.cancel) this.updateSuggestions.cancel(); - this.componentIsUnmounting = true; - window.removeEventListener('resize', this.handleResize); - if (this.hasScrollListener) window.removeEventListener('scroll', this.onOutsideClick); - } - - handleAutoHeight = onRaf(() => { - if (this.inputRef !== null && document.activeElement === this.inputRef) { - this.inputRef.classList.add('kbnQueryBar__textarea--autoHeight'); - this.inputRef.style.setProperty('height', `${this.inputRef.scrollHeight}px`, 'important'); - } - }); - - handleBlurOnScroll = onRaf(() => { - // for small screens, unified search bar is no longer sticky, - // so we need to blur the input when it scrolls out of view - // TODO: replace screen width value with euiTheme breakpoint once this component is converted to a functional component - const isSmallScreen = window.innerWidth < 768; - - if (isSmallScreen && !this.hasScrollListener) { - window.addEventListener('scroll', this.onOutsideClick); - this.hasScrollListener = true; - } else if (!isSmallScreen && this.hasScrollListener) { - window.removeEventListener('scroll', this.onOutsideClick); - this.hasScrollListener = false; - } - }); - - handleRemoveHeight = onRaf(() => { - if (this.inputRef !== null) { - this.inputRef.style.removeProperty('height'); - this.inputRef.classList.remove('kbnQueryBar__textarea--autoHeight'); - } - }); - - handleBlurHeight = onRaf(() => { - if (this.inputRef !== null) { - this.handleRemoveHeight(); - this.inputRef.scrollTop = 0; - } - }); - - handleOnFocus = () => { - if (this.props.onChangeQueryInputFocus) { - this.props.onChangeQueryInputFocus(true); - } - - this.handleAutoHeight(); - }; - - getSearchInputPlaceholder = () => { - if (!this.props.query.language || this.props.query.language === 'text') { - return strings.getSearchInputPlaceholderForText(); - } - const language = - this.props.query.language === 'kuery' ? 'KQL' : toSentenceCase(this.props.query.language); - - return strings.getSearchInputPlaceholder(language); - }; - - public render() { - const isSuggestionsVisible = this.state.isSuggestionsVisible && { - 'aria-controls': 'kbnTypeahead__items', - 'aria-owns': 'kbnTypeahead__items', - }; - const ariaCombobox = { ...isSuggestionsVisible, role: 'combobox' }; - - const simpleLanguageSwitcher = this.props.disableLanguageSwitcher ? null : ( - - ); - - const prependElement = - this.props.prepend || simpleLanguageSwitcher ? ( - - ) : undefined; - - const containerClassName = classNames('kbnQueryBar__wrap', this.props.className); - const inputClassName = classNames('kbnQueryBar__textarea', 'eui-scrollBar', { - 'kbnQueryBar__textarea--withIcon': this.props.iconType, - 'kbnQueryBar__textarea--isClearable': this.props.isClearable, - 'kbnQueryBar__textarea--withPrepend': prependElement, - }); - const inputWrapClassName = classNames('kbnQueryBar__textareaWrap', { - 'kbnQueryBar__textareaWrap--withSuggestionVisible': - isSuggestionsVisible && !isEmpty(this.state.suggestions), - }); - return ( - - {prependElement} - - -
-
- - {this.forwardNewValueIfNeeded(this.getQueryString())} - - {this.props.iconType ? ( - - ) : null} - {this.props.isClearable && !this.props.isDisabled && this.props.query.query ? ( - { - this.onQueryStringChange(''); - // Force close the dropdown/suggestions - this.setState({ - isSuggestionsVisible: false, - index: null, - }); - if (this.props.autoSubmit) { - this.onSubmit({ query: '', language: this.props.query.language }); - } - }, - title: strings.getQueryBarClearInputLabel(), - }} - /> - ) : null} -
- - - -
-
-
- ); - } - - /** - * Used to apply any string formatting to textarea value before converting it to {@link Query} and emitting it to the parent. - * This is a bit lower level then {@link fromUser} and needed to address any cross-browser inconsistencies where - * {@link forwardNewValueIfNeeded} should be kept in mind - */ - private formatTextAreaValue(newValue: string): string { - // Safari has a bug that it sometimes uses a non-breaking space instead of a regular space - // this breaks the search query: https://github.com/elastic/kibana/issues/87176 - return newValue.replace(/\u00A0/g, ' '); - } - - /** - * When passing a "value" prop into a textarea, - * check first if value has changed because of {@link formatTextAreaValue}, - * if this is just a formatting change, then skip this update by re-using current textarea value. - * This is needed to avoid re-rendering to preserve focus and selection - * @internal - */ - private forwardNewValueIfNeeded(newQueryString: string) { - const oldQueryString = this.inputRef?.value ?? ''; - - const formattedNewQueryString = this.formatTextAreaValue(newQueryString); - // if old & new values are equal with formatting applied, then return an old query without formatting applied - if (formattedNewQueryString === this.formatTextAreaValue(oldQueryString)) { - return oldQueryString; - } else { - return formattedNewQueryString; - } - } - - private assignInputRef = (node: HTMLTextAreaElement | null) => { - this.inputRef = node; - }; - - private assignQueryInputDivRef = (node: HTMLDivElement | null) => { - this.setState({ queryBarInputDiv: node }); - }; - - private onFocusWithin = () => { - this.isFocusWithin = true; - }; - - private onBlurWithin = () => { - this.isFocusWithin = false; - this.scheduleOnInputBlur(); - }; -} diff --git a/src/platform/plugins/shared/unified_search/public/query_string_input/to_user.test.ts b/src/platform/plugins/shared/unified_search/public/query_string_input/to_user.test.ts deleted file mode 100644 index 340c1cfb63c28..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/query_string_input/to_user.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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 { toUser } from './to_user'; - -describe('user input helpers', () => { - describe('model presentation formatter', () => { - test('should present objects as strings', () => { - expect(toUser({ foo: 'bar' })).toBe('{"foo":"bar"}'); - }); - - test('should present query_string queries as strings', () => { - expect(toUser({ query_string: { query: 'lucene query string' } })).toBe( - 'lucene query string' - ); - }); - - test('should present query_string queries without a query as an empty string', () => { - expect(toUser({ query_string: {} })).toBe(''); - }); - - test('should present string as strings', () => { - expect(toUser('foo')).toBe('foo'); - }); - - test('should present numbers as strings', () => { - expect(toUser(400)).toBe('400'); - }); - }); -}); diff --git a/src/platform/plugins/shared/unified_search/public/query_string_input/to_user.ts b/src/platform/plugins/shared/unified_search/public/query_string_input/to_user.ts deleted file mode 100644 index a116b7c8ca793..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/query_string_input/to_user.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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". - */ - -/** - * Take text from the model and present it to the user as a string - * @param text model value - * @returns {string} - */ -export function toUser(text: { [key: string]: any } | string | number): string { - if (text == null) { - return ''; - } - if (typeof text === 'object') { - if (text.query_string) { - return toUser(text.query_string.query); - } - return JSON.stringify(text); - } - return '' + text; -} diff --git a/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.tsx b/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.tsx index 068590645fde1..453db77cec1a3 100644 --- a/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.tsx +++ b/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.tsx @@ -40,6 +40,7 @@ import type { DataView } from '@kbn/data-views-plugin/public'; import { BackgroundSearchRestoredCallout } from '@kbn/background-search'; import { i18n } from '@kbn/i18n'; import { toMountPoint } from '@kbn/react-kibana-mount'; +import type { SuggestionsAbstraction, SuggestionsListSize } from '@kbn/kql/public'; import type { AdditionalQueryBarMenuItems } from '../query_string_input/query_bar_menu_panels'; import type { IUnifiedSearchPluginServices, UnifiedSearchDraft } from '../types'; import type { SavedQueryMeta } from '../saved_query_form'; @@ -51,10 +52,6 @@ import type { DataViewPickerProps } from '../dataview_picker'; import type { QueryBarTopRowProps } from '../query_string_input/query_bar_top_row'; import { QueryBarTopRow } from '../query_string_input/query_bar_top_row'; import { FilterBar, FilterItems } from '../filter_bar'; -import type { - SuggestionsAbstraction, - SuggestionsListSize, -} from '../typeahead/suggestions_component'; import { searchBarStyles } from './search_bar.styles'; export interface SearchBarInjectedDeps { diff --git a/src/platform/plugins/shared/unified_search/public/typeahead/constants.ts b/src/platform/plugins/shared/unified_search/public/typeahead/constants.ts deleted file mode 100644 index b815a90c0d400..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/typeahead/constants.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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". - */ - -/** - * Minimum width in px to display suggestion description correctly - * @public - */ -export const SUGGESTIONS_LIST_REQUIRED_WIDTH = 600; - -/** - * Minimum bottom distance in px to display list of suggestions - * @public - */ -export const SUGGESTIONS_LIST_REQUIRED_BOTTOM_SPACE = 250; - -/** - * A distance in px to display suggestions list right under the query input without a gap - * @public - */ -export const SUGGESTIONS_LIST_REQUIRED_TOP_OFFSET = 0; diff --git a/src/platform/plugins/shared/unified_search/public/typeahead/index.tsx b/src/platform/plugins/shared/unified_search/public/typeahead/index.tsx deleted file mode 100644 index 4c9fb6c2c87c4..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/typeahead/index.tsx +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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 React from 'react'; - -const Fallback = () =>
; - -const LazySuggestionsComponent = React.lazy(async () => { - const { SuggestionsComponent } = await import('../ui_module'); - return { default: SuggestionsComponent }; -}); -export const SuggestionsComponent = ( - props: React.ComponentProps -) => ( - }> - - -); diff --git a/src/platform/plugins/shared/unified_search/public/typeahead/suggestion_component.test.tsx b/src/platform/plugins/shared/unified_search/public/typeahead/suggestion_component.test.tsx deleted file mode 100644 index 50fbe7b3dd715..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/typeahead/suggestion_component.test.tsx +++ /dev/null @@ -1,136 +0,0 @@ -/* - * 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 React from 'react'; -import { render, screen } from '@testing-library/react'; -import { SuggestionComponent } from './suggestion_component'; -import type { QuerySuggestion } from '../autocomplete'; -import { QuerySuggestionTypes } from '../autocomplete'; -import { userEvent } from '@testing-library/user-event'; - -const noop = () => {}; - -const mockSuggestion: QuerySuggestion = { - description: 'This is not a helpful suggestion', - end: 0, - start: 42, - text: 'as promised, not helpful', - type: QuerySuggestionTypes.Value, -}; - -describe('SuggestionComponent', () => { - it('displays the suggestion and uses the provided ariaId', () => { - render( - - ); - - const item = screen.getByText(/as promised, not helpful/i); - expect(item).toBeInTheDocument(); - expect(screen.getByRole('option', { name: /as promised, not helpful/i })).toHaveAttribute( - 'id', - 'suggestion-1' - ); - }); - - it('marks element as active when selected', () => { - render( - - ); - expect(screen.getByRole('option', { name: /as promised, not helpful/i })).toHaveAttribute( - 'id', - 'suggestion-1' - ); - expect(screen.getByRole('option', { name: /as promised, not helpful/i })).toHaveClass( - 'kbnTypeahead__item active' - ); - }); - - it('calls innerRef with the reference to the root element', () => { - const innerRefMock = jest.fn(); - - render( - - ); - - expect(innerRefMock).toHaveBeenCalledTimes(1); - const [indexArg, elementArg] = innerRefMock.mock.calls[0]; - expect(indexArg).toBe(0); - expect(elementArg).toBeInstanceOf(HTMLDivElement); - expect(elementArg?.id).toBe('suggestion-1'); - expect(elementArg?.className).toContain('kbnTypeahead__item'); - }); - - it('calls onClick with suggestion and index', async () => { - const clickHandler = jest.fn(); - - render( - - ); - - await userEvent.click(screen.getByText(/as promised, not helpful/i)); - expect(clickHandler).toHaveBeenCalledWith(mockSuggestion, 0); - expect(clickHandler).toHaveBeenCalledTimes(1); - }); - - it('calls onMouseEnter when user hovers the element', async () => { - const mouseEnterHandler = jest.fn(); - - render( - - ); - - await userEvent.hover(screen.getByText(/as promised, not helpful/i)); - expect(mouseEnterHandler).toHaveBeenCalledTimes(1); - }); -}); diff --git a/src/platform/plugins/shared/unified_search/public/typeahead/suggestion_component.tsx b/src/platform/plugins/shared/unified_search/public/typeahead/suggestion_component.tsx deleted file mode 100644 index ae0b561cd2ad8..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/typeahead/suggestion_component.tsx +++ /dev/null @@ -1,254 +0,0 @@ -/* - * 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 type { UseEuiTheme } from '@elastic/eui'; -import { EuiIcon, euiFontSize } from '@elastic/eui'; -import classNames from 'classnames'; -import React, { useCallback } from 'react'; -import { css } from '@emotion/react'; -import type { EmotionStyles } from '../use_memo_css'; -import { useMemoCss } from '../use_memo_css'; -import type { QuerySuggestion } from '../autocomplete'; -import type { SuggestionOnClick, SuggestionOnMouseEnter } from './types'; - -function getEuiIconType(type: string) { - switch (type) { - case 'field': - return 'kqlField'; - case 'value': - return 'kqlValue'; - case 'recentSearch': - return 'search'; - case 'conjunction': - return 'kqlSelector'; - case 'operator': - return 'kqlOperand'; - default: - throw new Error(`Unknown type: ${type}`); - } -} - -interface Props { - onClick: SuggestionOnClick; - onMouseEnter: SuggestionOnMouseEnter; - selected: boolean; - index: number; - suggestion: QuerySuggestion; - innerRef: (index: number, node: HTMLDivElement) => void; - ariaId: string; - shouldDisplayDescription: boolean; -} - -export const SuggestionComponent = React.memo(function SuggestionComponent(props: Props) { - const { index, innerRef, onClick, onMouseEnter, suggestion } = props; - const setRef = useCallback( - (node: HTMLDivElement) => { - innerRef(index, node); - }, - [index, innerRef] - ); - - const handleClick = useCallback(() => { - onClick(suggestion, index); - }, [index, onClick, suggestion]); - - const handleMouseEnter = useCallback(() => { - onMouseEnter(suggestion, index); - }, [index, onMouseEnter, suggestion]); - - const styles = useMemoCss(suggestionStyles); - - return ( - // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/interactive-supports-focus -
-
-
- -
-
- {props.suggestion.text} -
- {props.shouldDisplayDescription && ( -
- {props.suggestion.description} -
- )} -
-
- ); -}); - -// These are the various types in the dropdown, they each get a color -const kbnTypeaheadTypes = { - field: { - base: 'backgroundBaseWarning' as const, - active: 'backgroundLightWarning' as const, - text: 'textWarning' as const, - }, - value: { - base: 'backgroundBaseSuccess' as const, - active: 'backgroundLightSuccess' as const, - text: 'textSuccess' as const, - }, - operator: { - base: 'backgroundBasePrimary' as const, - active: 'backgroundLightPrimary' as const, - text: 'textPrimary' as const, - }, - conjunction: { - base: 'backgroundBaseSubdued' as const, - active: 'backgroundLightText' as const, - text: 'textSubdued' as const, - }, - recentSearch: { - base: 'backgroundBaseSubdued' as const, - active: 'backgroundLightText' as const, - text: 'textSubdued' as const, - }, -}; - -const activeColors = (context: UseEuiTheme) => - Object.entries(kbnTypeaheadTypes).map(([type, color]) => { - return { - [`.kbnSuggestionItem--${type}`]: { - '.kbnSuggestionItem__type': { - backgroundColor: context.euiTheme.colors[color.active], - }, - }, - }; - }); - -const tokenColors = (context: UseEuiTheme) => - Object.entries(kbnTypeaheadTypes).map(([type, color]) => { - return { - [`&.kbnSuggestionItem--${type}`]: { - '.kbnSuggestionItem__type': { - backgroundColor: context.euiTheme.colors[color.base], - color: context.euiTheme.colors[color.text], - }, - }, - }; - }); - -const suggestionStyles: EmotionStyles = { - suggestionItem: (context: UseEuiTheme) => - css({ - '&.kbnTypeahead__item': { - height: context.euiTheme.size.xl, - whiteSpace: 'nowrap', - fontSize: euiFontSize(context, 'xs').fontSize, - verticalAlign: 'middle', - padding: 0, - borderBottom: 'none', - lineHeight: 'normal', - '&:hover': { - cursor: 'pointer', - }, - '&:last-child': { - borderBottom: 'none', - borderRadius: `0 0 ${context.euiTheme.border.radius.medium} ${context.euiTheme.border.radius.medium}`, - }, - '&:first-child': { - borderBottom: 'none', - }, - - '&.active': css([ - { - backgroundColor: context.euiTheme.colors.lightestShade, - '.kbnSuggestionItem__callout': { - background: context.euiTheme.colors.emptyShade, - }, - '.kbnSuggestionItem__text': { - color: context.euiTheme.colors.fullShade, - }, - '.kbnSuggestionItem__type': { - color: context.euiTheme.colors.fullShade, - }, - }, - activeColors(context), - ]), - }, - - '.kbnSuggestionItem': css([ - tokenColors(context), - { - display: 'inline-flex', - alignItems: 'center', - fontSize: euiFontSize(context, 'xs').fontSize, - whiteSpace: 'nowrap', - width: '100%', - }, - ]), - '.kbnSuggestionItem__type': { - display: 'flex', - flexDirection: 'column', - flexGrow: 0, - flexShrink: 0, - flexBasis: 'auto', - width: context.euiTheme.size.xl, - height: context.euiTheme.size.xl, - textAlign: 'center', - overflow: 'hidden', - justifyContent: 'center', - alignItems: 'center', - padding: context.euiTheme.size.xs, - }, - '.kbnSuggestionItem__text': { - fontFamily: context.euiTheme.font.familyCode, - overflow: 'hidden', - textOverflow: 'ellipsis', - paddingLeft: context.euiTheme.size.s, - color: context.euiTheme.colors.text, - minWidth: '250px', - }, - '.kbnSuggestionItem__description': { - color: context.euiTheme.colors.darkShade, - overflow: 'hidden', - textOverflow: 'ellipsis', - flexShrink: 1, - // In case the description contains a paragraph in which the truncation needs to be at this level - '> p': { - overflow: 'hidden', - textOverflow: 'ellipsis', - }, - '&:empty': { - width: 0, - }, - }, - '.kbnSuggestionItem__callout': { - fontFamily: context.euiTheme.font.familyCode, - background: context.euiTheme.colors.lightestShade, - color: context.euiTheme.colors.fullShade, - padding: `0 ${context.euiTheme.size.xs}`, - display: 'inline-block', - }, - }), -}; diff --git a/src/platform/plugins/shared/unified_search/public/typeahead/suggestions_component.test.tsx b/src/platform/plugins/shared/unified_search/public/typeahead/suggestions_component.test.tsx deleted file mode 100644 index 11712960e0f8a..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/typeahead/suggestions_component.test.tsx +++ /dev/null @@ -1,142 +0,0 @@ -/* - * 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 React from 'react'; -import { render, screen } from '@testing-library/react'; -import type { QuerySuggestion } from '../autocomplete'; -import { QuerySuggestionTypes } from '../autocomplete'; -import { SuggestionsComponent } from './suggestions_component'; -import { EuiThemeProvider } from '@elastic/eui'; -import { userEvent } from '@testing-library/user-event'; - -const noop = () => {}; - -const mockContainerDiv = document.createElement('div'); - -const mockSuggestions: QuerySuggestion[] = [ - { - description: 'This is not a helpful suggestion', - end: 0, - start: 42, - text: 'as promised, not helpful', - type: QuerySuggestionTypes.Value, - }, - { - description: 'This is another unhelpful suggestion', - end: 0, - start: 42, - text: 'yep', - type: QuerySuggestionTypes.Field, - }, -]; - -const renderWithTheme = (ui: React.ReactElement) => { - return render({ui}); -}; - -describe('SuggestionsComponent', () => { - it('Should not render if show is false', () => { - const { container } = renderWithTheme( - - ); - expect(container).toBeEmptyDOMElement(); - }); - - it('Should not render if there are no suggestions', () => { - const { container } = renderWithTheme( - - ); - expect(container).toBeEmptyDOMElement(); - }); - - it('Should render suggestions when show is true', () => { - renderWithTheme( - - ); - const items = screen.getAllByRole('option'); - expect(items).toHaveLength(2); - }); - - it('Should apply selection based on index prop', () => { - renderWithTheme( - - ); - - const selected = screen.getAllByRole('option')[1]; - expect(selected.getAttribute('aria-selected')).toBe('true'); - }); - - it('Should call onClick with selected suggestion when clicked', async () => { - const mockClick = jest.fn(); - renderWithTheme( - - ); - const item = screen.getAllByRole('option')[1]; - await userEvent.click(item); - expect(mockClick).toHaveBeenCalledWith(mockSuggestions[1], 1); - }); - - it('Should call onMouseEnter with correct index when suggestion is hovered', async () => { - const mockEnter = jest.fn(); - renderWithTheme( - - ); - const item = screen.getAllByRole('option')[1]; - await userEvent.hover(item); - expect(mockEnter).toHaveBeenCalledWith(mockSuggestions[1], 1); - }); -}); diff --git a/src/platform/plugins/shared/unified_search/public/typeahead/suggestions_component.tsx b/src/platform/plugins/shared/unified_search/public/typeahead/suggestions_component.tsx deleted file mode 100644 index 46f52654107f3..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/typeahead/suggestions_component.tsx +++ /dev/null @@ -1,335 +0,0 @@ -/* - * 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 type { ReactNode } from 'react'; -import React, { PureComponent } from 'react'; -import { isEmpty } from 'lodash'; -import classNames from 'classnames'; - -import useRafState from 'react-use/lib/useRafState'; -import type { UseEuiTheme } from '@elastic/eui'; -import { euiShadow, euiShadowFlat } from '@elastic/eui'; -import { css } from '@emotion/react'; -import type { EmotionStyles } from '../use_memo_css'; -import { useMemoCss } from '../use_memo_css'; -import type { QuerySuggestion } from '../autocomplete'; -import { SuggestionComponent } from './suggestion_component'; -import { - SUGGESTIONS_LIST_REQUIRED_BOTTOM_SPACE, - SUGGESTIONS_LIST_REQUIRED_TOP_OFFSET, - SUGGESTIONS_LIST_REQUIRED_WIDTH, -} from './constants'; -import type { SuggestionOnClick, SuggestionOnMouseEnter } from './types'; -import { onRaf, shallowEqual } from '../utils'; - -interface SuggestionsComponentProps { - index: number | null; - onClick: SuggestionOnClick; - onMouseEnter: SuggestionOnMouseEnter; - show: boolean; - suggestions: QuerySuggestion[]; - loadMore: () => void; - size?: SuggestionsListSize; - inputContainer: HTMLElement | null; -} - -export interface SuggestionsAbstraction { - type: 'alerts' | 'rules' | 'cases' | 'endpoints'; - fields: Record< - string, - { - field: string; - fieldToQuery: string; - displayField: string | undefined; - nestedDisplayField?: string; - nestedField?: string; - nestedPath?: string; - } - >; -} - -export type SuggestionsListSize = 's' | 'l'; - -export class SuggestionsComponent extends PureComponent { - private childNodes: HTMLDivElement[] = []; - private parentNode: HTMLDivElement | null = null; - - constructor(props: SuggestionsComponentProps) { - super(props); - - this.assignParentNode = this.assignParentNode.bind(this); - this.assignChildNode = this.assignChildNode.bind(this); - } - - private assignParentNode(node: HTMLDivElement) { - this.parentNode = node; - } - - private assignChildNode(index: number, node: HTMLDivElement) { - this.childNodes[index] = node; - } - - public render() { - if (!this.props.inputContainer || !this.props.show || isEmpty(this.props.suggestions)) { - return null; - } - - const renderSuggestions = (containerWidth: number) => { - const isDescriptionFittable = containerWidth >= SUGGESTIONS_LIST_REQUIRED_WIDTH; - const suggestions = this.props.suggestions.map((suggestion, index) => { - return ( - - ); - }); - - return suggestions; - }; - - return ( - - {(rect: DOMRect) => ( -
- {renderSuggestions(rect.width)} -
- )} -
- ); - } - - public componentDidUpdate(prevProps: SuggestionsComponentProps) { - if (prevProps.index !== this.props.index) { - this.scrollIntoView(); - } - } - - private scrollIntoView = onRaf(() => { - if (this.props.index === null) { - return; - } - const parent = this.parentNode; - const child = this.childNodes[this.props.index]; - - if (this.props.index == null || !parent || !child) { - return; - } - - const scrollTop = Math.max( - Math.min(parent.scrollTop, child.offsetTop), - child.offsetTop + child.offsetHeight - parent.offsetHeight - ); - - parent.scrollTop = scrollTop; - }); - - private handleScroll = onRaf(() => { - if (!this.props.loadMore || !this.parentNode) { - return; - } - - const position = this.parentNode.scrollTop + this.parentNode.offsetHeight; - const height = this.parentNode.scrollHeight; - const remaining = height - position; - const margin = 50; - - if (!height || !position) { - return; - } - if (remaining <= margin) { - this.props.loadMore(); - } - }); -} - -const ResizableSuggestionsListDiv: React.FC<{ - inputContainer: HTMLElement; - suggestionsSize?: SuggestionsListSize; - children: (rect: DOMRect) => ReactNode; -}> = React.memo((props) => { - const inputContainer = props.inputContainer; - - const [{ documentHeight }, { pageYOffset }, containerRect] = useDimensions(inputContainer); - const styles = useMemoCss(suggestionsStyles); - - if (!containerRect) return null; - - // reflects if the suggestions list has enough space below to be opened down - const isSuggestionsListFittable = - documentHeight - (containerRect.top + containerRect.height) > - SUGGESTIONS_LIST_REQUIRED_BOTTOM_SPACE; - const verticalListPosition = isSuggestionsListFittable - ? { top: `${pageYOffset + containerRect.bottom - SUGGESTIONS_LIST_REQUIRED_TOP_OFFSET}px` } - : { bottom: `${documentHeight - (pageYOffset + containerRect.top)}px` }; - - return ( -
-
-
-
- {props.children(containerRect)} -
-
-
-
- ); -}); - -function useDimensions( - container: HTMLElement | null -): [{ documentHeight: number }, { pageYOffset: number; pageXOffset: number }, DOMRect | null] { - const [documentHeight, setDocumentHeight] = useRafState( - () => document.documentElement.clientHeight || window.innerHeight - ); - - const [pageOffset, setPageOffset] = useRafState<{ pageXOffset: number; pageYOffset: number }>( - () => ({ - pageXOffset: window.pageXOffset, - pageYOffset: window.pageYOffset, - }) - ); - - const [containerRect, setContainerRect] = useRafState(() => { - return container?.getBoundingClientRect() ?? null; - }); - - const updateContainerRect = React.useCallback(() => { - setContainerRect((oldRect: DOMRect | null) => { - const newRect = container?.getBoundingClientRect() ?? null; - const rectsEqual = shallowEqual(oldRect?.toJSON(), newRect?.toJSON()); - return rectsEqual ? oldRect : newRect; - }); - }, [container, setContainerRect]); - - React.useEffect(() => { - const handler = () => { - setDocumentHeight(document.documentElement.clientHeight || window.innerHeight); - }; - - window.addEventListener('resize', handler, { passive: true }); - - return () => { - window.removeEventListener('resize', handler); - }; - }, [setDocumentHeight]); - - React.useEffect(() => { - const handler = () => { - setPageOffset((state) => { - const { pageXOffset, pageYOffset } = window; - return state.pageXOffset !== pageXOffset || state.pageYOffset !== pageYOffset - ? { - pageXOffset, - pageYOffset, - } - : state; - }); - - updateContainerRect(); - }; - - window.addEventListener('scroll', handler, { passive: true, capture: true }); - - const resizeObserver = - typeof window.ResizeObserver !== 'undefined' && - new ResizeObserver(() => { - updateContainerRect(); - }); - if (container && resizeObserver) { - resizeObserver.observe(container); - } - - return () => { - window.removeEventListener('scroll', handler, { capture: true }); - if (resizeObserver) resizeObserver.disconnect(); - }; - }, [setPageOffset, container, updateContainerRect]); - - return [{ documentHeight }, pageOffset, containerRect]; -} - -const suggestionsStyles: EmotionStyles = { - container: (context: UseEuiTheme) => - css({ - position: 'absolute', - zIndex: 4001, - '.kbnTypeahead': { - maxHeight: '60vh', - '&.kbnTypeahead--small': { - maxHeight: '20vh', - }, - }, - '.kbnTypeahead__popover': { - maxHeight: 'inherit', - border: `1px solid ${context.euiTheme.colors.borderBaseSubdued}`, - color: context.euiTheme.colors.text, - backgroundColor: context.euiTheme.colors.emptyShade, - position: 'relative', - zIndex: context.euiTheme.levels.menu, - width: '100%', - overflow: 'hidden', - - '.kbnTypeahead__popover-content': { - maxHeight: 'inherit', - overflowY: 'auto', - }, - - '&.kbnTypeahead__popover--top': css([ - euiShadowFlat(context, { border: 'none' }), - { - borderTopLeftRadius: context.euiTheme.border.radius.medium, - borderTopRightRadius: context.euiTheme.border.radius.medium, - // Clips the shadow so it doesn't show above the input (below) - clipPath: `polygon(-50px -50px, calc(100% + 50px) -50px, calc(100% + 50px) 100%, -50px 100%)`, - }, - ]), - '&.kbnTypeahead__popover--bottom': css([ - euiShadow(context, 'l', { border: 'none' }), - { - borderBottomLeftRadius: context.euiTheme.border.radius.medium, - borderBottomRightRadius: context.euiTheme.border.radius.medium, - // Clips the shadow so it doesn't show above the input (top) - clipPath: `polygon(-50px 1px, calc(100% + 50px) 1px, calc(100% + 50px) calc(100% + 50px), -50px calc(100% + 50px))`, - }, - ]), - }, - }), -}; diff --git a/src/platform/plugins/shared/unified_search/public/ui_module.ts b/src/platform/plugins/shared/unified_search/public/ui_module.ts index f7fa78166a708..2bfa69430243c 100644 --- a/src/platform/plugins/shared/unified_search/public/ui_module.ts +++ b/src/platform/plugins/shared/unified_search/public/ui_module.ts @@ -19,5 +19,4 @@ export { FilterItem } from './filter_bar/filter_item/filter_item'; export { FilterItems } from './filter_bar/filter_item/filter_items'; export { IndexPatternSelect } from './index_pattern_select/index_pattern_select'; export { QueryBarTopRow } from './query_string_input/query_bar_top_row'; -export { SuggestionsComponent } from './typeahead/suggestions_component'; export { default as SearchBar } from './search_bar/search_bar'; diff --git a/src/platform/plugins/shared/unified_search/public/utils/index.ts b/src/platform/plugins/shared/unified_search/public/utils/index.ts index bb85ea4c62025..bf5af5b7b8f28 100644 --- a/src/platform/plugins/shared/unified_search/public/utils/index.ts +++ b/src/platform/plugins/shared/unified_search/public/utils/index.ts @@ -7,7 +7,6 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -export { onRaf } from './on_raf'; export { shallowEqual } from './shallow_equal'; export { getBooleanRelationType } from './combined_filter'; diff --git a/src/platform/plugins/shared/unified_search/public/utils/on_raf.ts b/src/platform/plugins/shared/unified_search/public/utils/on_raf.ts deleted file mode 100644 index 2fe96ef7ce38d..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/utils/on_raf.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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". - */ - -/** - * Debounce a function till next animation frame - * @param fn - */ -export function onRaf(fn: Function) { - let req: number | null; - return (...args: unknown[]) => { - if (req) window.cancelAnimationFrame(req); - req = window.requestAnimationFrame(() => { - req = null; - fn(...args); - }); - }; -} diff --git a/src/platform/plugins/shared/vis_types/timeseries/public/application/components/query_bar_wrapper.tsx b/src/platform/plugins/shared/vis_types/timeseries/public/application/components/query_bar_wrapper.tsx index 611ccafd97e7a..4364cf4bd6e02 100644 --- a/src/platform/plugins/shared/vis_types/timeseries/public/application/components/query_bar_wrapper.tsx +++ b/src/platform/plugins/shared/vis_types/timeseries/public/application/components/query_bar_wrapper.tsx @@ -42,7 +42,6 @@ export function QueryBarWrapper({ notifications, http, docLinks, - core, uiSettings, usageCollection, } = kibana.services; @@ -81,7 +80,6 @@ export function QueryBarWrapper({ appName={appName} deps={{ autocomplete: kql.autocomplete, - core, notifications, http, docLinks, diff --git a/x-pack/platform/plugins/private/graph/public/components/search_bar.test.tsx b/x-pack/platform/plugins/private/graph/public/components/search_bar.test.tsx index 05efe61e902e4..9a4857f373eff 100644 --- a/x-pack/platform/plugins/private/graph/public/components/search_bar.test.tsx +++ b/x-pack/platform/plugins/private/graph/public/components/search_bar.test.tsx @@ -33,7 +33,6 @@ import type { ReactWrapper } from 'enzyme'; import { createMockGraphStore } from '../state_management/mocks'; import { Provider } from 'react-redux'; import { createQueryStringInput } from '@kbn/kql/public/components/query_string_input/get_query_string_input'; -import { coreMock } from '@kbn/core/public/mocks'; import { kqlPluginMock } from '@kbn/kql/public/mocks'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; @@ -76,7 +75,6 @@ function getServiceMocks() { notifications: {} as NotificationsStart, http: {} as HttpStart, dataViews: dataViewPluginMocks.createStartContract(), - core: coreMock.createStart(), }), }, }, diff --git a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/dimension_panel/dimension_editor.tsx b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/dimension_panel/dimension_editor.tsx index 867074d3bfb1a..45ac4ed436816 100644 --- a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/dimension_panel/dimension_editor.tsx +++ b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/dimension_panel/dimension_editor.tsx @@ -119,6 +119,7 @@ export function DimensionEditor(props: DimensionEditorProps) { storage: props.storage, unifiedSearch: props.unifiedSearch, dataViews: props.dataViews, + kql: props.kql, }; const { fieldByOperation, operationWithoutField } = operationSupportMatrix; diff --git a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/dimension_panel/dimension_panel.test.tsx b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/dimension_panel/dimension_panel.test.tsx index 8ebbdd38082ad..0e0ba6ec40e0a 100644 --- a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/dimension_panel/dimension_panel.test.tsx +++ b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/dimension_panel/dimension_panel.test.tsx @@ -21,6 +21,7 @@ import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks' import type { FormBasedDimensionEditorProps } from './dimension_panel'; import { FormBasedDimensionEditorComponent } from './dimension_panel'; import type { IUiSettingsClient, HttpSetup, CoreStart, NotificationsStart } from '@kbn/core/public'; +import { kqlPluginMock } from '@kbn/kql/public/mocks'; import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { useExistingFieldsReader } from '@kbn/unified-field-list/src/hooks/use_existing_fields'; import { generateId } from '../../../id_generator'; @@ -229,6 +230,7 @@ describe('FormBasedDimensionEditor', () => { http: {} as HttpSetup, fieldFormats: fieldFormatsServiceMock.createStartContract(), unifiedSearch: unifiedSearchPluginMock.createStartContract(), + kql: kqlPluginMock.createStartContract(), dataViews: dataViewPluginMocks.createStartContract(), notifications: {} as NotificationsStart, data: { diff --git a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/dimension_panel/dimension_panel.tsx b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/dimension_panel/dimension_panel.tsx index 9a1d826a2461b..3f293bed711fe 100644 --- a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/dimension_panel/dimension_panel.tsx +++ b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/dimension_panel/dimension_panel.tsx @@ -20,6 +20,7 @@ import type { DatasourceDimensionTriggerProps, DatasourceDimensionEditorProps, } from '@kbn/lens-common'; +import type { KqlPluginStart } from '@kbn/kql/public'; import { DimensionEditor } from './dimension_editor'; import { getOperationSupportMatrix } from './operation_support'; @@ -37,6 +38,7 @@ export type FormBasedDimensionEditorProps = data: DataPublicPluginStart; fieldFormats: FieldFormatsStart; unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; dataViews: DataViewsPublicPluginStart; uniqueLabel: string; dateRange: DateRange; diff --git a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/form_based.test.ts b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/form_based.test.ts index 990e8a1e30c5e..d9b9a5db2268e 100644 --- a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/form_based.test.ts +++ b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/form_based.test.ts @@ -51,6 +51,7 @@ import { filterAndSortUserMessages } from '../../app_plugin/get_application_user import { createMockFramePublicAPI } from '../../mocks'; import { createMockDataViewsState } from '../../data_views_service/mocks'; import type { Query } from '@kbn/es-query'; +import { kqlPluginMock } from '@kbn/kql/public/mocks'; jest.mock('./loader'); jest.mock('../../id_generator'); @@ -210,6 +211,7 @@ describe('IndexPattern Data Source', () => { FormBasedDatasource = getFormBasedDatasource({ unifiedSearch: unifiedSearchPluginMock.createStartContract(), + kql: kqlPluginMock.createStartContract(), storage: {} as IStorageWrapper, core: coreMock.createStart(), data, diff --git a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/form_based.tsx b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/form_based.tsx index 46eb43aaa923b..51efc5f16a21a 100644 --- a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/form_based.tsx +++ b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/form_based.tsx @@ -53,6 +53,7 @@ import type { LastValueIndexPatternColumn, FormBasedLayer, } from '@kbn/lens-common'; +import type { KqlPluginStart } from '@kbn/kql/public'; import { changeIndexPattern, changeLayerIndexPattern, @@ -217,6 +218,7 @@ export function getFormBasedDatasource({ data, unifiedSearch, share, + kql, dataViews, fieldFormats, charts, @@ -227,6 +229,7 @@ export function getFormBasedDatasource({ storage: IStorageWrapper; data: DataPublicPluginStart; unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; share?: SharePluginStart; dataViews: DataViewsPublicPluginStart; fieldFormats: FieldFormatsStart; @@ -564,6 +567,7 @@ export function getFormBasedDatasource({ http={core.http} data={data} unifiedSearch={unifiedSearch} + kql={kql} dataViews={dataViews} uniqueLabel={columnLabelMap[props.columnId]} notifications={core.notifications} diff --git a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/index.ts b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/index.ts index 23e4b58076466..66697ba6e3c3d 100644 --- a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/index.ts +++ b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/index.ts @@ -17,6 +17,7 @@ import type { UiActionsStart } from '@kbn/ui-actions-plugin/public'; import type { FieldFormatsStart, FieldFormatsSetup } from '@kbn/field-formats-plugin/public'; import type { SharePluginStart } from '@kbn/share-plugin/public'; import type { EditorFrameSetup } from '@kbn/lens-common'; +import type { KqlPluginStart } from '@kbn/kql/public'; export type { PersistedIndexPatternLayer, FormulaPublicApi } from '@kbn/lens-common'; @@ -31,6 +32,7 @@ export interface FormBasedDatasourceSetupPlugins { export interface FormBasedDatasourceStartPlugins { data: DataPublicPluginStart; unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; share?: SharePluginStart; fieldFormats: FieldFormatsStart; dataViewFieldEditor: IndexPatternFieldEditorStart; @@ -64,7 +66,16 @@ export class FormBasedDatasource { const [ coreStart, - { dataViewFieldEditor, uiActions, data, fieldFormats, dataViews, unifiedSearch, share }, + { + dataViewFieldEditor, + uiActions, + data, + fieldFormats, + dataViews, + unifiedSearch, + share, + kql, + }, ] = await core.getStartServices(); return getFormBasedDatasource({ @@ -73,6 +84,7 @@ export class FormBasedDatasource { storage: new Storage(localStorage), data, unifiedSearch, + kql, share, dataViews, charts, diff --git a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/ranges/ranges.test.tsx b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/ranges/ranges.test.tsx index 495e4d7400c23..65b1286fc697d 100644 --- a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/ranges/ranges.test.tsx +++ b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/ranges/ranges.test.tsx @@ -14,6 +14,7 @@ import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks'; import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; +import { kqlPluginMock } from '@kbn/kql/public/mocks'; import { mountWithProviders } from '../../../../../test_utils/test_utils'; import type { FormBasedLayer, RangeIndexPatternColumn, IndexPattern } from '@kbn/lens-common'; import { rangeOperation } from '..'; @@ -54,6 +55,7 @@ jest.mock('lodash', () => { const dataPluginMockValue = dataPluginMock.createStartContract(); const unifiedSearchPluginMockValue = unifiedSearchPluginMock.createStartContract(); +const kqlPluginMockValue = kqlPluginMock.createStartContract(); const fieldFormatsPluginMockValue = fieldFormatsServiceMock.createStartContract(); const dataViewsPluginMockValue = dataViewPluginMocks.createStartContract(); // need to overwrite the formatter field first @@ -90,6 +92,7 @@ const defaultOptions = { data: dataPluginMockValue, fieldFormats: fieldFormatsPluginMockValue, unifiedSearch: unifiedSearchPluginMockValue, + kql: kqlPluginMockValue, dataViews: dataViewsPluginMockValue, http: {} as HttpSetup, indexPattern: { diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/endpoint_exceptions/view/components/endpoint_exceptions_form.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/pages/endpoint_exceptions/view/components/endpoint_exceptions_form.tsx index e87d472693e85..8084f0028031a 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/pages/endpoint_exceptions/view/components/endpoint_exceptions_form.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/endpoint_exceptions/view/components/endpoint_exceptions_form.tsx @@ -35,7 +35,7 @@ import { OperatingSystem } from '@kbn/securitysolution-utils'; import { getExceptionBuilderComponentLazy } from '@kbn/lists-plugin/public'; import type { OnChangeProps } from '@kbn/lists-plugin/public'; -import type { ValueSuggestionsGetFn } from '@kbn/unified-search-plugin/public/autocomplete/providers/value_suggestion_provider'; +import type { ValueSuggestionsGetFn } from '@kbn/kql/public/autocomplete/providers/value_suggestion_provider'; import type { EffectedPolicySelectProps } from '../../../../components/effected_policy_select'; import { EffectedPolicySelect } from '../../../../components/effected_policy_select'; import { useCanAssignArtifactPerPolicy } from '../../../../hooks/artifacts'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx index 1d51182f2336e..bcb4a70ea49d8 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx @@ -40,7 +40,7 @@ import { OperatingSystem } from '@kbn/securitysolution-utils'; import { getExceptionBuilderComponentLazy } from '@kbn/lists-plugin/public'; import type { OnChangeProps } from '@kbn/lists-plugin/public'; -import type { ValueSuggestionsGetFn } from '@kbn/unified-search-plugin/public/autocomplete/providers/value_suggestion_provider'; +import type { ValueSuggestionsGetFn } from '@kbn/kql/public/autocomplete/providers/value_suggestion_provider'; import { useCanAssignArtifactPerPolicy } from '../../../../hooks/artifacts/use_can_assign_artifact_per_policy'; import { FormattedError } from '../../../../components/formatted_error'; import { useGetUpdatedTags } from '../../../../hooks/artifacts'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_apps/view/components/form.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_apps/view/components/form.tsx index 591f619e0063f..6c0fa4fc87e67 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_apps/view/components/form.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_apps/view/components/form.tsx @@ -40,7 +40,7 @@ import { OperatingSystem, } from '@kbn/securitysolution-utils'; import type { OnChangeProps } from '@kbn/lists-plugin/public'; -import type { ValueSuggestionsGetFn } from '@kbn/unified-search-plugin/public/autocomplete/providers/value_suggestion_provider'; +import type { ValueSuggestionsGetFn } from '@kbn/kql/public/autocomplete/providers/value_suggestion_provider'; import { PartialCodeSignatureCallout, WildCardWithWrongOperatorCallout, From a9ef094a6aa2b10e0269b538b2fd6a4e196fa72a Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 19 Dec 2025 08:04:03 +0000 Subject: [PATCH 13/60] Changes from node scripts/lint_ts_projects --fix --- src/platform/packages/shared/kbn-lens-common/tsconfig.json | 3 ++- .../shared/kbn-visualization-ui-components/tsconfig.json | 4 ++-- .../plugins/private/event_annotation_listing/tsconfig.json | 4 ++-- src/platform/plugins/private/input_control_vis/tsconfig.json | 1 + src/platform/plugins/shared/controls/tsconfig.json | 4 ++-- src/platform/plugins/shared/unified_search/tsconfig.json | 4 +--- .../plugins/shared/vis_types/timeseries/tsconfig.json | 1 + x-pack/platform/plugins/private/graph/tsconfig.json | 1 + x-pack/platform/plugins/private/monitoring/tsconfig.json | 3 ++- x-pack/platform/plugins/private/transform/tsconfig.json | 3 ++- x-pack/platform/plugins/shared/alerting/tsconfig.json | 3 ++- x-pack/platform/plugins/shared/fleet/tsconfig.json | 1 + x-pack/platform/plugins/shared/lens/tsconfig.json | 1 + x-pack/platform/plugins/shared/maps/tsconfig.json | 1 + x-pack/platform/plugins/shared/ml/tsconfig.json | 1 + x-pack/solutions/observability/plugins/apm/tsconfig.json | 1 + .../observability/plugins/observability/tsconfig.json | 1 + x-pack/solutions/observability/plugins/uptime/tsconfig.json | 1 + x-pack/solutions/security/plugins/lists/tsconfig.json | 4 ++-- .../security/plugins/security_solution/tsconfig.json | 3 ++- 20 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/platform/packages/shared/kbn-lens-common/tsconfig.json b/src/platform/packages/shared/kbn-lens-common/tsconfig.json index 971e67f3a509f..c33a216dffcb2 100644 --- a/src/platform/packages/shared/kbn-lens-common/tsconfig.json +++ b/src/platform/packages/shared/kbn-lens-common/tsconfig.json @@ -64,6 +64,7 @@ "@kbn/chart-icons", "@kbn/alerts-ui-shared", "@kbn/controls-plugin", - "@kbn/cps" + "@kbn/cps", + "@kbn/kql" ] } diff --git a/src/platform/packages/shared/kbn-visualization-ui-components/tsconfig.json b/src/platform/packages/shared/kbn-visualization-ui-components/tsconfig.json index 9dfe4d33e3719..e5c15159f0ff2 100644 --- a/src/platform/packages/shared/kbn-visualization-ui-components/tsconfig.json +++ b/src/platform/packages/shared/kbn-visualization-ui-components/tsconfig.json @@ -18,7 +18,6 @@ ], "kbn_references": [ "@kbn/i18n", - "@kbn/unified-search-plugin", "@kbn/utility-types", "@kbn/es-query", "@kbn/core-http-browser", @@ -34,6 +33,7 @@ "@kbn/field-utils", "@kbn/calculate-width-from-char-count", "@kbn/visualization-utils", - "@kbn/shared-ux-button-toolbar" + "@kbn/shared-ux-button-toolbar", + "@kbn/kql" ], } diff --git a/src/platform/plugins/private/event_annotation_listing/tsconfig.json b/src/platform/plugins/private/event_annotation_listing/tsconfig.json index 1bd5c874a3ba6..b947c1afdf426 100644 --- a/src/platform/plugins/private/event_annotation_listing/tsconfig.json +++ b/src/platform/plugins/private/event_annotation_listing/tsconfig.json @@ -24,7 +24,6 @@ "@kbn/saved-objects-tagging-oss-plugin", "@kbn/core-lifecycle-browser", "@kbn/kibana-utils-plugin", - "@kbn/unified-search-plugin", "@kbn/content-management-table-list-view-table", "@kbn/content-management-tabbed-table-list-view", "@kbn/content-management-plugin", @@ -39,7 +38,8 @@ "@kbn/core-notifications-browser-mocks", "@kbn/core-notifications-browser", "@kbn/content-management-table-list-view-common", - "@kbn/test-eui-helpers" + "@kbn/test-eui-helpers", + "@kbn/kql" ], "exclude": [ "target/**/*", diff --git a/src/platform/plugins/private/input_control_vis/tsconfig.json b/src/platform/plugins/private/input_control_vis/tsconfig.json index c9af77cf37adc..3a2225f674eba 100644 --- a/src/platform/plugins/private/input_control_vis/tsconfig.json +++ b/src/platform/plugins/private/input_control_vis/tsconfig.json @@ -28,6 +28,7 @@ "@kbn/presentation-publishing", "@kbn/react-kibana-context-render", "@kbn/visualizations-common", + "@kbn/kql", ], "exclude": [ "target/**/*", diff --git a/src/platform/plugins/shared/controls/tsconfig.json b/src/platform/plugins/shared/controls/tsconfig.json index 9087d0ce1fa1f..ce8ab89c21f96 100644 --- a/src/platform/plugins/shared/controls/tsconfig.json +++ b/src/platform/plugins/shared/controls/tsconfig.json @@ -17,7 +17,6 @@ "@kbn/embeddable-plugin", "@kbn/presentation-util-plugin", "@kbn/data-plugin", - "@kbn/unified-search-plugin", "@kbn/es-query", "@kbn/data-views-plugin", "@kbn/kibana-utils-plugin", @@ -43,7 +42,8 @@ "@kbn/search-types", "@kbn/controls-constants", "@kbn/controls-schemas", - "@kbn/presentation-util" + "@kbn/presentation-util", + "@kbn/kql" ], "exclude": ["target/**/*"] } diff --git a/src/platform/plugins/shared/unified_search/tsconfig.json b/src/platform/plugins/shared/unified_search/tsconfig.json index ea232175cbd19..a1dc097ee3cc9 100644 --- a/src/platform/plugins/shared/unified_search/tsconfig.json +++ b/src/platform/plugins/shared/unified_search/tsconfig.json @@ -25,9 +25,6 @@ "@kbn/datemath", "@kbn/monaco", "@kbn/field-types", - "@kbn/config", - "@kbn/config-schema", - "@kbn/utility-types-jest", "@kbn/react-field", "@kbn/ui-theme", "@kbn/saved-objects-management-plugin", @@ -51,6 +48,7 @@ "@kbn/split-button", "@kbn/cps", "@kbn/css-utils", + "@kbn/kql", ], "exclude": ["target/**/*"] } diff --git a/src/platform/plugins/shared/vis_types/timeseries/tsconfig.json b/src/platform/plugins/shared/vis_types/timeseries/tsconfig.json index 16379ba4ddf4b..7f861d0958c57 100644 --- a/src/platform/plugins/shared/vis_types/timeseries/tsconfig.json +++ b/src/platform/plugins/shared/vis_types/timeseries/tsconfig.json @@ -56,6 +56,7 @@ "@kbn/ui-actions-plugin", "@kbn/presentation-publishing", "@kbn/presentation-containers", + "@kbn/kql", ], "exclude": [ "target/**/*", diff --git a/x-pack/platform/plugins/private/graph/tsconfig.json b/x-pack/platform/plugins/private/graph/tsconfig.json index e33af8ff2d4a0..6119a3e8c5e9e 100644 --- a/x-pack/platform/plugins/private/graph/tsconfig.json +++ b/x-pack/platform/plugins/private/graph/tsconfig.json @@ -53,6 +53,7 @@ "@kbn/react-kibana-context-render", "@kbn/core-saved-objects-api-server", "@kbn/licensing-types", + "@kbn/kql", ], "exclude": [ "target/**/*", diff --git a/x-pack/platform/plugins/private/monitoring/tsconfig.json b/x-pack/platform/plugins/private/monitoring/tsconfig.json index 18a04f4f50dca..b09dfa6474df5 100644 --- a/x-pack/platform/plugins/private/monitoring/tsconfig.json +++ b/x-pack/platform/plugins/private/monitoring/tsconfig.json @@ -60,7 +60,8 @@ "@kbn/core-elasticsearch-server-mocks", "@kbn/autoops-promotion-callout", "@kbn/react-query", - "@kbn/response-ops-rule-params" + "@kbn/response-ops-rule-params", + "@kbn/kql" ], "exclude": ["target/**/*"] } diff --git a/x-pack/platform/plugins/private/transform/tsconfig.json b/x-pack/platform/plugins/private/transform/tsconfig.json index 55613d6e5f9d1..41462f6e10f63 100644 --- a/x-pack/platform/plugins/private/transform/tsconfig.json +++ b/x-pack/platform/plugins/private/transform/tsconfig.json @@ -84,7 +84,8 @@ "@kbn/response-ops-rule-params", "@kbn/fields-metadata-plugin", "@kbn/licensing-types", - "@kbn/react-query" + "@kbn/react-query", + "@kbn/kql" ], "exclude": ["target/**/*"] } diff --git a/x-pack/platform/plugins/shared/alerting/tsconfig.json b/x-pack/platform/plugins/shared/alerting/tsconfig.json index 70b98d8013e58..2d0b9a0aca06a 100644 --- a/x-pack/platform/plugins/shared/alerting/tsconfig.json +++ b/x-pack/platform/plugins/shared/alerting/tsconfig.json @@ -84,7 +84,8 @@ "@kbn/react-query", "@kbn/maintenance-windows-plugin", "@kbn/config", - "@kbn/expect" + "@kbn/expect", + "@kbn/kql" ], "exclude": ["target/**/*"] } diff --git a/x-pack/platform/plugins/shared/fleet/tsconfig.json b/x-pack/platform/plugins/shared/fleet/tsconfig.json index 338d670ce1b88..35a4922fd1e0b 100644 --- a/x-pack/platform/plugins/shared/fleet/tsconfig.json +++ b/x-pack/platform/plugins/shared/fleet/tsconfig.json @@ -128,5 +128,6 @@ "@kbn/setup-node-env", "@kbn/react-query", "@kbn/fs", + "@kbn/kql", ] } diff --git a/x-pack/platform/plugins/shared/lens/tsconfig.json b/x-pack/platform/plugins/shared/lens/tsconfig.json index 04db18f08b42a..2417039d78912 100644 --- a/x-pack/platform/plugins/shared/lens/tsconfig.json +++ b/x-pack/platform/plugins/shared/lens/tsconfig.json @@ -135,6 +135,7 @@ "@kbn/unified-tabs", "@kbn/esql-composer", "@kbn/cps", + "@kbn/kql", ], "exclude": ["target/**/*"] } diff --git a/x-pack/platform/plugins/shared/maps/tsconfig.json b/x-pack/platform/plugins/shared/maps/tsconfig.json index d1efc5d035007..1000f14376683 100644 --- a/x-pack/platform/plugins/shared/maps/tsconfig.json +++ b/x-pack/platform/plugins/shared/maps/tsconfig.json @@ -103,6 +103,7 @@ "@kbn/lens-common", "@kbn/crypto-browser", "@kbn/cps", + "@kbn/kql", ], "exclude": [ "target/**/*", diff --git a/x-pack/platform/plugins/shared/ml/tsconfig.json b/x-pack/platform/plugins/shared/ml/tsconfig.json index 9b17f6e7d55f7..176c76c8497b3 100644 --- a/x-pack/platform/plugins/shared/ml/tsconfig.json +++ b/x-pack/platform/plugins/shared/ml/tsconfig.json @@ -146,5 +146,6 @@ "@kbn/file-upload-plugin", "@kbn/file-upload", "@kbn/file-upload-common", + "@kbn/kql", ] } diff --git a/x-pack/solutions/observability/plugins/apm/tsconfig.json b/x-pack/solutions/observability/plugins/apm/tsconfig.json index 8df3293ac5b41..94feabb02be1b 100644 --- a/x-pack/solutions/observability/plugins/apm/tsconfig.json +++ b/x-pack/solutions/observability/plugins/apm/tsconfig.json @@ -151,6 +151,7 @@ "@kbn/zod", "@kbn/onechat-plugin", "@kbn/observability-agent-builder-plugin", + "@kbn/kql", ], "exclude": ["target/**/*"] } diff --git a/x-pack/solutions/observability/plugins/observability/tsconfig.json b/x-pack/solutions/observability/plugins/observability/tsconfig.json index 3a442c2d2262d..b2949b37d4b60 100644 --- a/x-pack/solutions/observability/plugins/observability/tsconfig.json +++ b/x-pack/solutions/observability/plugins/observability/tsconfig.json @@ -144,6 +144,7 @@ "@kbn/management-settings-ids", "@kbn/onechat-plugin", "@kbn/observability-agent-builder-plugin", + "@kbn/kql", ], "exclude": ["target/**/*"] } diff --git a/x-pack/solutions/observability/plugins/uptime/tsconfig.json b/x-pack/solutions/observability/plugins/uptime/tsconfig.json index c07300f3d76ff..6ade9511a3273 100644 --- a/x-pack/solutions/observability/plugins/uptime/tsconfig.json +++ b/x-pack/solutions/observability/plugins/uptime/tsconfig.json @@ -82,6 +82,7 @@ "@kbn/fields-metadata-plugin", "@kbn/licensing-types", "@kbn/connector-schemas", + "@kbn/kql", ], "exclude": ["target/**/*"] } diff --git a/x-pack/solutions/security/plugins/lists/tsconfig.json b/x-pack/solutions/security/plugins/lists/tsconfig.json index ee588d1d40e1e..e305e33d57b2c 100644 --- a/x-pack/solutions/security/plugins/lists/tsconfig.json +++ b/x-pack/solutions/security/plugins/lists/tsconfig.json @@ -14,7 +14,6 @@ "@kbn/core", "@kbn/spaces-plugin", "@kbn/security-plugin", - "@kbn/unified-search-plugin", "@kbn/securitysolution-io-ts-list-types", "@kbn/securitysolution-list-constants", "@kbn/securitysolution-list-hooks", @@ -46,7 +45,8 @@ "@kbn/core-http-server-mocks", "@kbn/core-http-server-utils", "@kbn/react-query", - "@kbn/security-solution-features" + "@kbn/security-solution-features", + "@kbn/kql" ], "exclude": ["target/**/*"] } diff --git a/x-pack/solutions/security/plugins/security_solution/tsconfig.json b/x-pack/solutions/security/plugins/security_solution/tsconfig.json index 1576f05318e17..5410e7e161496 100644 --- a/x-pack/solutions/security/plugins/security_solution/tsconfig.json +++ b/x-pack/solutions/security/plugins/security_solution/tsconfig.json @@ -270,6 +270,7 @@ "@kbn/core-ui-settings-server-mocks", "@kbn/onechat-browser", "@kbn/ai-assistant-common", - "@kbn/product-doc-common" + "@kbn/product-doc-common", + "@kbn/kql" ] } From 0fb32ba65dcc561f7e6cb98dcdfb33d033dc4247 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 19 Dec 2025 08:14:35 +0000 Subject: [PATCH 14/60] Changes from node scripts/regenerate_moon_projects.js --update --- src/platform/packages/shared/kbn-lens-common/moon.yml | 1 + .../packages/shared/kbn-visualization-ui-components/moon.yml | 2 +- .../plugins/private/event_annotation_listing/moon.yml | 2 +- src/platform/plugins/private/input_control_vis/moon.yml | 1 + src/platform/plugins/shared/controls/moon.yml | 2 +- src/platform/plugins/shared/unified_search/moon.yml | 4 +--- src/platform/plugins/shared/vis_types/timeseries/moon.yml | 1 + x-pack/platform/plugins/private/graph/moon.yml | 1 + x-pack/platform/plugins/private/monitoring/moon.yml | 1 + x-pack/platform/plugins/private/transform/moon.yml | 1 + x-pack/platform/plugins/shared/alerting/moon.yml | 1 + x-pack/platform/plugins/shared/fleet/moon.yml | 1 + x-pack/platform/plugins/shared/lens/moon.yml | 1 + x-pack/platform/plugins/shared/maps/moon.yml | 1 + x-pack/platform/plugins/shared/ml/moon.yml | 1 + x-pack/solutions/observability/plugins/apm/moon.yml | 1 + x-pack/solutions/observability/plugins/observability/moon.yml | 1 + x-pack/solutions/observability/plugins/uptime/moon.yml | 1 + x-pack/solutions/security/plugins/lists/moon.yml | 2 +- x-pack/solutions/security/plugins/security_solution/moon.yml | 1 + 20 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/platform/packages/shared/kbn-lens-common/moon.yml b/src/platform/packages/shared/kbn-lens-common/moon.yml index 3f53b0b5bec45..f212bf6dfe936 100644 --- a/src/platform/packages/shared/kbn-lens-common/moon.yml +++ b/src/platform/packages/shared/kbn-lens-common/moon.yml @@ -67,6 +67,7 @@ dependsOn: - '@kbn/alerts-ui-shared' - '@kbn/controls-plugin' - '@kbn/cps' + - '@kbn/kql' tags: - shared-common - package diff --git a/src/platform/packages/shared/kbn-visualization-ui-components/moon.yml b/src/platform/packages/shared/kbn-visualization-ui-components/moon.yml index 7edca8fd06136..ecf43b2292704 100644 --- a/src/platform/packages/shared/kbn-visualization-ui-components/moon.yml +++ b/src/platform/packages/shared/kbn-visualization-ui-components/moon.yml @@ -19,7 +19,6 @@ project: sourceRoot: src/platform/packages/shared/kbn-visualization-ui-components dependsOn: - '@kbn/i18n' - - '@kbn/unified-search-plugin' - '@kbn/utility-types' - '@kbn/es-query' - '@kbn/core-http-browser' @@ -36,6 +35,7 @@ dependsOn: - '@kbn/calculate-width-from-char-count' - '@kbn/visualization-utils' - '@kbn/shared-ux-button-toolbar' + - '@kbn/kql' tags: - shared-browser - package diff --git a/src/platform/plugins/private/event_annotation_listing/moon.yml b/src/platform/plugins/private/event_annotation_listing/moon.yml index 7fa2d1104589b..71c4d0781812c 100644 --- a/src/platform/plugins/private/event_annotation_listing/moon.yml +++ b/src/platform/plugins/private/event_annotation_listing/moon.yml @@ -33,7 +33,6 @@ dependsOn: - '@kbn/saved-objects-tagging-oss-plugin' - '@kbn/core-lifecycle-browser' - '@kbn/kibana-utils-plugin' - - '@kbn/unified-search-plugin' - '@kbn/content-management-table-list-view-table' - '@kbn/content-management-tabbed-table-list-view' - '@kbn/content-management-plugin' @@ -49,6 +48,7 @@ dependsOn: - '@kbn/core-notifications-browser' - '@kbn/content-management-table-list-view-common' - '@kbn/test-eui-helpers' + - '@kbn/kql' tags: - plugin - prod diff --git a/src/platform/plugins/private/input_control_vis/moon.yml b/src/platform/plugins/private/input_control_vis/moon.yml index 783c9abc44148..34a2bb6dbab90 100644 --- a/src/platform/plugins/private/input_control_vis/moon.yml +++ b/src/platform/plugins/private/input_control_vis/moon.yml @@ -36,6 +36,7 @@ dependsOn: - '@kbn/presentation-publishing' - '@kbn/react-kibana-context-render' - '@kbn/visualizations-common' + - '@kbn/kql' tags: - plugin - prod diff --git a/src/platform/plugins/shared/controls/moon.yml b/src/platform/plugins/shared/controls/moon.yml index 13f8f90fc3bd6..9a3d78f1203ca 100644 --- a/src/platform/plugins/shared/controls/moon.yml +++ b/src/platform/plugins/shared/controls/moon.yml @@ -22,7 +22,6 @@ dependsOn: - '@kbn/embeddable-plugin' - '@kbn/presentation-util-plugin' - '@kbn/data-plugin' - - '@kbn/unified-search-plugin' - '@kbn/es-query' - '@kbn/data-views-plugin' - '@kbn/kibana-utils-plugin' @@ -49,6 +48,7 @@ dependsOn: - '@kbn/controls-constants' - '@kbn/controls-schemas' - '@kbn/presentation-util' + - '@kbn/kql' tags: - plugin - prod diff --git a/src/platform/plugins/shared/unified_search/moon.yml b/src/platform/plugins/shared/unified_search/moon.yml index fdbeed5f2ea0a..800870650bdc6 100644 --- a/src/platform/plugins/shared/unified_search/moon.yml +++ b/src/platform/plugins/shared/unified_search/moon.yml @@ -38,9 +38,6 @@ dependsOn: - '@kbn/datemath' - '@kbn/monaco' - '@kbn/field-types' - - '@kbn/config' - - '@kbn/config-schema' - - '@kbn/utility-types-jest' - '@kbn/react-field' - '@kbn/ui-theme' - '@kbn/saved-objects-management-plugin' @@ -64,6 +61,7 @@ dependsOn: - '@kbn/split-button' - '@kbn/cps' - '@kbn/css-utils' + - '@kbn/kql' tags: - plugin - prod diff --git a/src/platform/plugins/shared/vis_types/timeseries/moon.yml b/src/platform/plugins/shared/vis_types/timeseries/moon.yml index 0d4e550839abc..6c73b42e33c7e 100644 --- a/src/platform/plugins/shared/vis_types/timeseries/moon.yml +++ b/src/platform/plugins/shared/vis_types/timeseries/moon.yml @@ -62,6 +62,7 @@ dependsOn: - '@kbn/ui-actions-plugin' - '@kbn/presentation-publishing' - '@kbn/presentation-containers' + - '@kbn/kql' tags: - plugin - prod diff --git a/x-pack/platform/plugins/private/graph/moon.yml b/x-pack/platform/plugins/private/graph/moon.yml index c8c981ea51a70..e7f9c82c47297 100644 --- a/x-pack/platform/plugins/private/graph/moon.yml +++ b/x-pack/platform/plugins/private/graph/moon.yml @@ -58,6 +58,7 @@ dependsOn: - '@kbn/react-kibana-context-render' - '@kbn/core-saved-objects-api-server' - '@kbn/licensing-types' + - '@kbn/kql' tags: - plugin - prod diff --git a/x-pack/platform/plugins/private/monitoring/moon.yml b/x-pack/platform/plugins/private/monitoring/moon.yml index 3aca3facddf3e..22aad0b175701 100644 --- a/x-pack/platform/plugins/private/monitoring/moon.yml +++ b/x-pack/platform/plugins/private/monitoring/moon.yml @@ -68,6 +68,7 @@ dependsOn: - '@kbn/autoops-promotion-callout' - '@kbn/react-query' - '@kbn/response-ops-rule-params' + - '@kbn/kql' tags: - plugin - prod diff --git a/x-pack/platform/plugins/private/transform/moon.yml b/x-pack/platform/plugins/private/transform/moon.yml index bcaa5dea35cfb..79dfdb65fd8ec 100644 --- a/x-pack/platform/plugins/private/transform/moon.yml +++ b/x-pack/platform/plugins/private/transform/moon.yml @@ -91,6 +91,7 @@ dependsOn: - '@kbn/fields-metadata-plugin' - '@kbn/licensing-types' - '@kbn/react-query' + - '@kbn/kql' tags: - plugin - prod diff --git a/x-pack/platform/plugins/shared/alerting/moon.yml b/x-pack/platform/plugins/shared/alerting/moon.yml index 6f64bbfcbde53..83b819bb70c0e 100644 --- a/x-pack/platform/plugins/shared/alerting/moon.yml +++ b/x-pack/platform/plugins/shared/alerting/moon.yml @@ -92,6 +92,7 @@ dependsOn: - '@kbn/maintenance-windows-plugin' - '@kbn/config' - '@kbn/expect' + - '@kbn/kql' tags: - plugin - prod diff --git a/x-pack/platform/plugins/shared/fleet/moon.yml b/x-pack/platform/plugins/shared/fleet/moon.yml index caf9d70859b17..4acfa0ef6b34a 100644 --- a/x-pack/platform/plugins/shared/fleet/moon.yml +++ b/x-pack/platform/plugins/shared/fleet/moon.yml @@ -118,6 +118,7 @@ dependsOn: - '@kbn/setup-node-env' - '@kbn/react-query' - '@kbn/fs' + - '@kbn/kql' tags: - plugin - prod diff --git a/x-pack/platform/plugins/shared/lens/moon.yml b/x-pack/platform/plugins/shared/lens/moon.yml index b5ef76328a535..d38254d75692e 100644 --- a/x-pack/platform/plugins/shared/lens/moon.yml +++ b/x-pack/platform/plugins/shared/lens/moon.yml @@ -148,6 +148,7 @@ dependsOn: - '@kbn/unified-tabs' - '@kbn/esql-composer' - '@kbn/cps' + - '@kbn/kql' tags: - plugin - prod diff --git a/x-pack/platform/plugins/shared/maps/moon.yml b/x-pack/platform/plugins/shared/maps/moon.yml index daab4e563113f..6e95dbfbffa23 100644 --- a/x-pack/platform/plugins/shared/maps/moon.yml +++ b/x-pack/platform/plugins/shared/maps/moon.yml @@ -107,6 +107,7 @@ dependsOn: - '@kbn/lens-common' - '@kbn/crypto-browser' - '@kbn/cps' + - '@kbn/kql' tags: - plugin - prod diff --git a/x-pack/platform/plugins/shared/ml/moon.yml b/x-pack/platform/plugins/shared/ml/moon.yml index f6d4fa2246255..3ad8387e9c176 100644 --- a/x-pack/platform/plugins/shared/ml/moon.yml +++ b/x-pack/platform/plugins/shared/ml/moon.yml @@ -146,6 +146,7 @@ dependsOn: - '@kbn/file-upload-plugin' - '@kbn/file-upload' - '@kbn/file-upload-common' + - '@kbn/kql' tags: - plugin - prod diff --git a/x-pack/solutions/observability/plugins/apm/moon.yml b/x-pack/solutions/observability/plugins/apm/moon.yml index b019e12b3f43b..29e8027fd7e04 100644 --- a/x-pack/solutions/observability/plugins/apm/moon.yml +++ b/x-pack/solutions/observability/plugins/apm/moon.yml @@ -153,6 +153,7 @@ dependsOn: - '@kbn/zod' - '@kbn/onechat-plugin' - '@kbn/observability-agent-builder-plugin' + - '@kbn/kql' tags: - plugin - prod diff --git a/x-pack/solutions/observability/plugins/observability/moon.yml b/x-pack/solutions/observability/plugins/observability/moon.yml index 9c5466c743c6d..9123bd1a2d930 100644 --- a/x-pack/solutions/observability/plugins/observability/moon.yml +++ b/x-pack/solutions/observability/plugins/observability/moon.yml @@ -149,6 +149,7 @@ dependsOn: - '@kbn/management-settings-ids' - '@kbn/onechat-plugin' - '@kbn/observability-agent-builder-plugin' + - '@kbn/kql' tags: - plugin - prod diff --git a/x-pack/solutions/observability/plugins/uptime/moon.yml b/x-pack/solutions/observability/plugins/uptime/moon.yml index 4f28ab8ec52dd..78dd69e2f0278 100644 --- a/x-pack/solutions/observability/plugins/uptime/moon.yml +++ b/x-pack/solutions/observability/plugins/uptime/moon.yml @@ -87,6 +87,7 @@ dependsOn: - '@kbn/fields-metadata-plugin' - '@kbn/licensing-types' - '@kbn/connector-schemas' + - '@kbn/kql' tags: - plugin - prod diff --git a/x-pack/solutions/security/plugins/lists/moon.yml b/x-pack/solutions/security/plugins/lists/moon.yml index a277c14b8d891..fdbcf85de675c 100644 --- a/x-pack/solutions/security/plugins/lists/moon.yml +++ b/x-pack/solutions/security/plugins/lists/moon.yml @@ -21,7 +21,6 @@ dependsOn: - '@kbn/core' - '@kbn/spaces-plugin' - '@kbn/security-plugin' - - '@kbn/unified-search-plugin' - '@kbn/securitysolution-io-ts-list-types' - '@kbn/securitysolution-list-constants' - '@kbn/securitysolution-list-hooks' @@ -54,6 +53,7 @@ dependsOn: - '@kbn/core-http-server-utils' - '@kbn/react-query' - '@kbn/security-solution-features' + - '@kbn/kql' tags: - plugin - prod diff --git a/x-pack/solutions/security/plugins/security_solution/moon.yml b/x-pack/solutions/security/plugins/security_solution/moon.yml index ab64828ffd9cb..751c48fc38079 100644 --- a/x-pack/solutions/security/plugins/security_solution/moon.yml +++ b/x-pack/solutions/security/plugins/security_solution/moon.yml @@ -270,6 +270,7 @@ dependsOn: - '@kbn/onechat-browser' - '@kbn/ai-assistant-common' - '@kbn/product-doc-common' + - '@kbn/kql' tags: - plugin - prod From 1417aad4331b3122dcae7ee56a156ef8a716f9fa Mon Sep 17 00:00:00 2001 From: Stratoula Date: Fri, 19 Dec 2025 10:59:40 +0100 Subject: [PATCH 15/60] Small fix --- x-pack/platform/plugins/shared/alerting/kibana.jsonc | 1 + x-pack/platform/plugins/shared/alerting/tsconfig.json | 1 + 2 files changed, 2 insertions(+) diff --git a/x-pack/platform/plugins/shared/alerting/kibana.jsonc b/x-pack/platform/plugins/shared/alerting/kibana.jsonc index 6e90d70a93305..bf69a97c63058 100644 --- a/x-pack/platform/plugins/shared/alerting/kibana.jsonc +++ b/x-pack/platform/plugins/shared/alerting/kibana.jsonc @@ -24,6 +24,7 @@ "management", "esUiShared", "unifiedSearch", + "kql", "embeddable" ], "optionalPlugins": [ diff --git a/x-pack/platform/plugins/shared/alerting/tsconfig.json b/x-pack/platform/plugins/shared/alerting/tsconfig.json index 2d0b9a0aca06a..94f0d2ef9e999 100644 --- a/x-pack/platform/plugins/shared/alerting/tsconfig.json +++ b/x-pack/platform/plugins/shared/alerting/tsconfig.json @@ -53,6 +53,7 @@ "@kbn/core-ui-settings-common", "@kbn/core-capabilities-common", "@kbn/unified-search-plugin", + "@kbn/kql", "@kbn/core-http-server-mocks", "@kbn/serverless", "@kbn/core-http-router-server-mocks", From 5cab5249ea12a19c35828f63b39cb8598481e64d Mon Sep 17 00:00:00 2001 From: Stratoula Date: Fri, 19 Dec 2025 11:20:03 +0100 Subject: [PATCH 16/60] Server related fixes --- src/platform/plugins/shared/controls/kibana.jsonc | 1 + src/platform/plugins/shared/unified_search/kibana.jsonc | 3 ++- .../public/query_string_input/query_bar_top_row.tsx | 3 +-- src/platform/plugins/shared/vis_types/timeseries/kibana.jsonc | 1 + .../solutions/security/plugins/security_solution/kibana.jsonc | 1 + 5 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/platform/plugins/shared/controls/kibana.jsonc b/src/platform/plugins/shared/controls/kibana.jsonc index 17e6e7c6b55a3..449032c654294 100644 --- a/src/platform/plugins/shared/controls/kibana.jsonc +++ b/src/platform/plugins/shared/controls/kibana.jsonc @@ -17,6 +17,7 @@ "dataViews", "data", "unifiedSearch", + "kql", "uiActions", ], "extraPublicDirs": [ diff --git a/src/platform/plugins/shared/unified_search/kibana.jsonc b/src/platform/plugins/shared/unified_search/kibana.jsonc index 24efd5e775028..abfb7b038b8c1 100644 --- a/src/platform/plugins/shared/unified_search/kibana.jsonc +++ b/src/platform/plugins/shared/unified_search/kibana.jsonc @@ -23,7 +23,8 @@ "uiActions", "screenshotMode", "savedObjectsManagement", - "cps" + "cps", + "kql" ], "optionalPlugins": [ "usageCollection" diff --git a/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx b/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx index 9163319e07211..b23ae36553498 100644 --- a/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx +++ b/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.tsx @@ -50,8 +50,7 @@ import type { ESQLControlVariable } from '@kbn/esql-types'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { SplitButton } from '@kbn/split-button'; import { useMemoCss } from '@kbn/css-utils/public/use_memo_css'; -import { QueryStringInput } from '@kbn/kql/public'; -import { FilterButtonGroup } from '@kbn/kql/public'; +import { QueryStringInput, FilterButtonGroup } from '@kbn/kql/public'; import type { SuggestionsAbstraction, SuggestionsListSize } from '@kbn/kql/public'; import { AddFilterPopover } from './add_filter_popover'; import type { DataViewPickerProps } from '../dataview_picker'; diff --git a/src/platform/plugins/shared/vis_types/timeseries/kibana.jsonc b/src/platform/plugins/shared/vis_types/timeseries/kibana.jsonc index 87e3ecc4fd3dc..d5d4a7c531b65 100644 --- a/src/platform/plugins/shared/vis_types/timeseries/kibana.jsonc +++ b/src/platform/plugins/shared/vis_types/timeseries/kibana.jsonc @@ -22,6 +22,7 @@ "fieldFormats", "usageCollection", "unifiedSearch", + "kql", "expressionXY", "uiActions", "embeddable" diff --git a/x-pack/solutions/security/plugins/security_solution/kibana.jsonc b/x-pack/solutions/security/plugins/security_solution/kibana.jsonc index 4db3ef5c42255..c1f8f480ec473 100644 --- a/x-pack/solutions/security/plugins/security_solution/kibana.jsonc +++ b/x-pack/solutions/security/plugins/security_solution/kibana.jsonc @@ -46,6 +46,7 @@ "triggersActionsUi", "uiActions", "unifiedSearch", + "kql", "files", "controls", "dataViewEditor", From 8fb0fbe002759b5c95ee67a103b6da3fd587a33d Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 19 Dec 2025 10:42:06 +0000 Subject: [PATCH 17/60] Changes from node scripts/regenerate_moon_projects.js --update --- x-pack/platform/plugins/shared/alerting/moon.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/platform/plugins/shared/alerting/moon.yml b/x-pack/platform/plugins/shared/alerting/moon.yml index 83b819bb70c0e..9234f293d6e77 100644 --- a/x-pack/platform/plugins/shared/alerting/moon.yml +++ b/x-pack/platform/plugins/shared/alerting/moon.yml @@ -60,6 +60,7 @@ dependsOn: - '@kbn/core-ui-settings-common' - '@kbn/core-capabilities-common' - '@kbn/unified-search-plugin' + - '@kbn/kql' - '@kbn/core-http-server-mocks' - '@kbn/serverless' - '@kbn/core-http-router-server-mocks' From 400fc7c9e424e8efe6da707f9be6deaeddb19fab Mon Sep 17 00:00:00 2001 From: Stratoula Date: Fri, 19 Dec 2025 12:02:28 +0100 Subject: [PATCH 18/60] More path fixes --- .../src/alerts_search_bar/index.tsx | 2 +- .../translations/translations/de-DE.json | 54 +++++++++---------- .../translations/translations/fr-FR.json | 54 +++++++++---------- .../translations/translations/ja-JP.json | 54 +++++++++---------- .../translations/translations/zh-CN.json | 54 +++++++++---------- .../alerts_search_bar/alerts_search_bar.tsx | 2 +- .../components/kql_search_bar/constants.tsx | 2 +- .../components/kql_search_bar/helpers.tsx | 2 +- .../kql_search_bar/validate_kuery_node.tsx | 2 +- 9 files changed, 113 insertions(+), 113 deletions(-) diff --git a/src/platform/packages/shared/kbn-alerts-ui-shared/src/alerts_search_bar/index.tsx b/src/platform/packages/shared/kbn-alerts-ui-shared/src/alerts_search_bar/index.tsx index 54529bdbe92db..e1c8d76297785 100644 --- a/src/platform/packages/shared/kbn-alerts-ui-shared/src/alerts_search_bar/index.tsx +++ b/src/platform/packages/shared/kbn-alerts-ui-shared/src/alerts_search_bar/index.tsx @@ -9,7 +9,7 @@ import { useCallback, useMemo, useState } from 'react'; import type { Query, TimeRange } from '@kbn/es-query'; -import type { SuggestionsAbstraction } from '@kbn/unified-search-plugin/public/typeahead/suggestions_component'; +import type { SuggestionsAbstraction } from '@kbn/kql/public/components/typeahead/suggestions_component'; import { isSiemRuleType } from '@kbn/rule-data-utils'; import { NO_INDEX_PATTERNS } from './constants'; import { SEARCH_BAR_PLACEHOLDER } from './translations'; diff --git a/x-pack/platform/plugins/private/translations/translations/de-DE.json b/x-pack/platform/plugins/private/translations/translations/de-DE.json index 67c1fdfcbb250..ab32168db9e26 100644 --- a/x-pack/platform/plugins/private/translations/translations/de-DE.json +++ b/x-pack/platform/plugins/private/translations/translations/de-DE.json @@ -7897,22 +7897,22 @@ "unifiedSearch.filter.options.saveCurrentFilterSetLabel": "Als neu speichern", "unifiedSearch.filter.options.saveFilterSetLabel": "Abfrage speichern", "unifiedSearch.filter.options.unpinAllFiltersButtonLabel": "Alle lösen", - "unifiedSearch.kueryAutocomplete.andOperatorDescription": "Erfordert, dass {bothArguments} wahr ist", - "unifiedSearch.kueryAutocomplete.andOperatorDescription.bothArgumentsText": "beide Argumente", - "unifiedSearch.kueryAutocomplete.equalOperatorDescription": "{equals} Ein bestimmter Wert", - "unifiedSearch.kueryAutocomplete.equalOperatorDescription.equalsText": "gleich", - "unifiedSearch.kueryAutocomplete.existOperatorDescription": "{exists} in jeglicher Form", - "unifiedSearch.kueryAutocomplete.existOperatorDescription.existsText": "existiert", - "unifiedSearch.kueryAutocomplete.greaterThanOperatorDescription": "ist {greaterThan} ein bestimmter Wert", - "unifiedSearch.kueryAutocomplete.greaterThanOperatorDescription.greaterThanText": "Größer als", - "unifiedSearch.kueryAutocomplete.greaterThanOrEqualOperatorDescription": "ist {greaterThanOrEqualTo} ein bestimmter Wert", - "unifiedSearch.kueryAutocomplete.greaterThanOrEqualOperatorDescription.greaterThanOrEqualToText": "Größer oder gleich", - "unifiedSearch.kueryAutocomplete.lessThanOperatorDescription": "ist {lessThan} ein bestimmter Wert", - "unifiedSearch.kueryAutocomplete.lessThanOperatorDescription.lessThanText": "weniger als", - "unifiedSearch.kueryAutocomplete.lessThanOrEqualOperatorDescription": "ist {lessThanOrEqualTo} ein bestimmter Wert", - "unifiedSearch.kueryAutocomplete.lessThanOrEqualOperatorDescription.lessThanOrEqualToText": "kleiner oder gleich", - "unifiedSearch.kueryAutocomplete.orOperatorDescription": "Erfordert, dass {oneOrMoreArguments} wahr ist", - "unifiedSearch.kueryAutocomplete.orOperatorDescription.oneOrMoreArgumentsText": "ein oder mehrere Argumente", + "kql.kueryAutocomplete.andOperatorDescription": "Erfordert, dass {bothArguments} wahr ist", + "kql.kueryAutocomplete.andOperatorDescription.bothArgumentsText": "beide Argumente", + "kql.kueryAutocomplete.equalOperatorDescription": "{equals} Ein bestimmter Wert", + "kql.kueryAutocomplete.equalOperatorDescription.equalsText": "gleich", + "kql.kueryAutocomplete.existOperatorDescription": "{exists} in jeglicher Form", + "kql.kueryAutocomplete.existOperatorDescription.existsText": "existiert", + "kql.kueryAutocomplete.greaterThanOperatorDescription": "ist {greaterThan} ein bestimmter Wert", + "kql.kueryAutocomplete.greaterThanOperatorDescription.greaterThanText": "Größer als", + "kql.kueryAutocomplete.greaterThanOrEqualOperatorDescription": "ist {greaterThanOrEqualTo} ein bestimmter Wert", + "kql.kueryAutocomplete.greaterThanOrEqualOperatorDescription.greaterThanOrEqualToText": "Größer oder gleich", + "kql.kueryAutocomplete.lessThanOperatorDescription": "ist {lessThan} ein bestimmter Wert", + "kql.kueryAutocomplete.lessThanOperatorDescription.lessThanText": "weniger als", + "kql.kueryAutocomplete.lessThanOrEqualOperatorDescription": "ist {lessThanOrEqualTo} ein bestimmter Wert", + "kql.kueryAutocomplete.lessThanOrEqualOperatorDescription.lessThanOrEqualToText": "kleiner oder gleich", + "kql.kueryAutocomplete.orOperatorDescription": "Erfordert, dass {oneOrMoreArguments} wahr ist", + "kql.kueryAutocomplete.orOperatorDescription.oneOrMoreArgumentsText": "ein oder mehrere Argumente", "unifiedSearch.noDataPopover.content": "Dieser Zeitraum enthält keine Daten. Erhöhen oder passen Sie den Zeitraum an, um mehr Felder anzuzeigen und Diagramme zu erstellen.", "unifiedSearch.noDataPopover.dismissAction": "Nicht mehr anzeigen", "unifiedSearch.noDataPopover.subtitle": "Tipp", @@ -7920,8 +7920,8 @@ "unifiedSearch.optionsList.popover.sortDirections": "Richtungen sortieren", "unifiedSearch.optionsList.popover.sortOrder.asc": "Aufsteigend", "unifiedSearch.optionsList.popover.sortOrder.desc": "Absteigend", - "unifiedSearch.query.queryBar.clearInputLabel": "Eingabe löschen", - "unifiedSearch.query.queryBar.comboboxAriaLabel": "Durchsuchen und filtern Sie die {pageType}-Seite", + "kql.query.queryBar.clearInputLabel": "Eingabe löschen", + "kql.query.queryBar.comboboxAriaLabel": "Durchsuchen und filtern Sie die {pageType}-Seite", "unifiedSearch.query.queryBar.esqlMenu.documentation": "Dokumentation", "unifiedSearch.query.queryBar.esqlMenu.exampleQueries": "Empfohlene Abfragen", "unifiedSearch.query.queryBar.esqlMenu.feedback": "Feedback geben", @@ -7938,15 +7938,15 @@ "unifiedSearch.query.queryBar.indexPattern.manageFieldButton": "Diese Datenansicht verwalten", "unifiedSearch.query.queryBar.indexPattern.temporaryDataviewLabel": "Temporär", "unifiedSearch.query.queryBar.kqlLanguageName": "KQL", - "unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoDocLinkText": "docs", - "unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoOptOutText": "Nicht mehr anzeigen", - "unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoText": "Scheinbar führen Sie eine Abfrage für ein verschachteltes Feld durch. Sie können die KQL-Syntax für verschachtelte Abfragen auf unterschiedliche Weise konstruieren, je nachdem, welche Ergebnisse Sie erzielen möchten. Mehr dazu erfahren Sie in unserem {link}.", - "unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoTitle": "KQL-Syntax für geschachtelte Abfragen", + "kql.query.queryBar.KQLNestedQuerySyntaxInfoDocLinkText": "docs", + "kql.query.queryBar.KQLNestedQuerySyntaxInfoOptOutText": "Nicht mehr anzeigen", + "kql.query.queryBar.KQLNestedQuerySyntaxInfoText": "Scheinbar führen Sie eine Abfrage für ein verschachteltes Feld durch. Sie können die KQL-Syntax für verschachtelte Abfragen auf unterschiedliche Weise konstruieren, je nachdem, welche Ergebnisse Sie erzielen möchten. Mehr dazu erfahren Sie in unserem {link}.", + "kql.query.queryBar.KQLNestedQuerySyntaxInfoTitle": "KQL-Syntax für geschachtelte Abfragen", "unifiedSearch.query.queryBar.luceneLanguageName": "Lucene", - "unifiedSearch.query.queryBar.searchInputAriaLabel": "Beginnen Sie mit der Eingabe, um die Seite {pageType} zu suchen und zu filtern", - "unifiedSearch.query.queryBar.searchInputPlaceholder": "Filtern Sie Ihre Daten mit der {language}-Syntax", - "unifiedSearch.query.queryBar.searchInputPlaceholderForText": "Filtern Sie Ihre Daten", - "unifiedSearch.query.queryBar.syntaxOptionsTitle": "Syntaxoptionen", + "kql.query.queryBar.searchInputAriaLabel": "Beginnen Sie mit der Eingabe, um die Seite {pageType} zu suchen und zu filtern", + "kql.query.queryBar.searchInputPlaceholder": "Filtern Sie Ihre Daten mit der {language}-Syntax", + "kql.query.queryBar.searchInputPlaceholderForText": "Filtern Sie Ihre Daten", + "kql.query.queryBar.syntaxOptionsTitle": "Syntaxoptionen", "unifiedSearch.query.queryBar.textBasedNonTimestampWarning": "Die Auswahl des Datumsbereichs für {language}-Abfragen erfordert ein @timestamp-Feld im Datensatz.", "unifiedSearch.queryBarTopRow.datePicker.disabledLabel": "Alle Zeiten", "unifiedSearch.queryBarTopRow.submitButton.cancel": "Abbrechen", @@ -7994,7 +7994,7 @@ "unifiedSearch.searchBar.savedQueryWeekToDateLabel": "Seit Wochenbeginn", "unifiedSearch.searchBar.savedQueryYearToDateLabel": "Seit Jahresbeginn", "unifiedSearch.searchBar.savedQueryYesterdayLabel": "Gestern", - "unifiedSearch.switchLanguage.buttonText": "Schaltfläche zum Umschalten der Sprache.", + "kql.switchLanguage.buttonText": "Schaltfläche zum Umschalten der Sprache.", "unifiedSearch.triggers.updateFilterReferencesTrigger": "Filterreferenzen aktualisieren", "unifiedSearch.triggers.updateFilterReferencesTriggerDescription": "Filterreferenzen aktualisieren", "unsavedChangesBadge.contextMenu.openButton": "Verfügbare Aktionen anzeigen", diff --git a/x-pack/platform/plugins/private/translations/translations/fr-FR.json b/x-pack/platform/plugins/private/translations/translations/fr-FR.json index 886e2d903f377..77b5a2e826512 100644 --- a/x-pack/platform/plugins/private/translations/translations/fr-FR.json +++ b/x-pack/platform/plugins/private/translations/translations/fr-FR.json @@ -8046,22 +8046,22 @@ "unifiedSearch.filter.options.saveCurrentFilterSetLabel": "Enregistrer en tant que nouvelle", "unifiedSearch.filter.options.saveFilterSetLabel": "Enregistrer la requête", "unifiedSearch.filter.options.unpinAllFiltersButtonLabel": "Tout désépingler", - "unifiedSearch.kueryAutocomplete.andOperatorDescription": "Nécessite que {bothArguments} soient définis sur \"true\"", - "unifiedSearch.kueryAutocomplete.andOperatorDescription.bothArgumentsText": "les deux arguments", - "unifiedSearch.kueryAutocomplete.equalOperatorDescription": "{equals} une certaine valeur", - "unifiedSearch.kueryAutocomplete.equalOperatorDescription.equalsText": "égal", - "unifiedSearch.kueryAutocomplete.existOperatorDescription": "{exists} sous une certaine forme", - "unifiedSearch.kueryAutocomplete.existOperatorDescription.existsText": "existe", - "unifiedSearch.kueryAutocomplete.greaterThanOperatorDescription": "est {greaterThan} une certaine valeur", - "unifiedSearch.kueryAutocomplete.greaterThanOperatorDescription.greaterThanText": "supérieur à", - "unifiedSearch.kueryAutocomplete.greaterThanOrEqualOperatorDescription": "est {greaterThanOrEqualTo} une certaine valeur", - "unifiedSearch.kueryAutocomplete.greaterThanOrEqualOperatorDescription.greaterThanOrEqualToText": "supérieur ou égal à", - "unifiedSearch.kueryAutocomplete.lessThanOperatorDescription": "est {lessThan} une certaine valeur", - "unifiedSearch.kueryAutocomplete.lessThanOperatorDescription.lessThanText": "inférieur à", - "unifiedSearch.kueryAutocomplete.lessThanOrEqualOperatorDescription": "est {lessThanOrEqualTo} une certaine valeur", - "unifiedSearch.kueryAutocomplete.lessThanOrEqualOperatorDescription.lessThanOrEqualToText": "inférieur ou égal à", - "unifiedSearch.kueryAutocomplete.orOperatorDescription": "Nécessite que {oneOrMoreArguments} soient définis sur \"true\"", - "unifiedSearch.kueryAutocomplete.orOperatorDescription.oneOrMoreArgumentsText": "au moins un argument", + "kql.kueryAutocomplete.andOperatorDescription": "Nécessite que {bothArguments} soient définis sur \"true\"", + "kql.kueryAutocomplete.andOperatorDescription.bothArgumentsText": "les deux arguments", + "kql.kueryAutocomplete.equalOperatorDescription": "{equals} une certaine valeur", + "kql.kueryAutocomplete.equalOperatorDescription.equalsText": "égal", + "kql.kueryAutocomplete.existOperatorDescription": "{exists} sous une certaine forme", + "kql.kueryAutocomplete.existOperatorDescription.existsText": "existe", + "kql.kueryAutocomplete.greaterThanOperatorDescription": "est {greaterThan} une certaine valeur", + "kql.kueryAutocomplete.greaterThanOperatorDescription.greaterThanText": "supérieur à", + "kql.kueryAutocomplete.greaterThanOrEqualOperatorDescription": "est {greaterThanOrEqualTo} une certaine valeur", + "kql.kueryAutocomplete.greaterThanOrEqualOperatorDescription.greaterThanOrEqualToText": "supérieur ou égal à", + "kql.kueryAutocomplete.lessThanOperatorDescription": "est {lessThan} une certaine valeur", + "kql.kueryAutocomplete.lessThanOperatorDescription.lessThanText": "inférieur à", + "kql.kueryAutocomplete.lessThanOrEqualOperatorDescription": "est {lessThanOrEqualTo} une certaine valeur", + "kql.kueryAutocomplete.lessThanOrEqualOperatorDescription.lessThanOrEqualToText": "inférieur ou égal à", + "kql.kueryAutocomplete.orOperatorDescription": "Nécessite que {oneOrMoreArguments} soient définis sur \"true\"", + "kql.kueryAutocomplete.orOperatorDescription.oneOrMoreArgumentsText": "au moins un argument", "unifiedSearch.noDataPopover.content": "Cette plage temporelle ne contient pas de données. Étendez ou ajustez la plage temporelle pour obtenir plus de champs et pouvoir créer des graphiques.", "unifiedSearch.noDataPopover.dismissAction": "Ne plus afficher", "unifiedSearch.noDataPopover.subtitle": "Conseil", @@ -8069,8 +8069,8 @@ "unifiedSearch.optionsList.popover.sortDirections": "Sens de tri", "unifiedSearch.optionsList.popover.sortOrder.asc": "Croissant", "unifiedSearch.optionsList.popover.sortOrder.desc": "Décroissant", - "unifiedSearch.query.queryBar.clearInputLabel": "Effacer l'entrée", - "unifiedSearch.query.queryBar.comboboxAriaLabel": "Rechercher et filtrer la page {pageType}", + "kql.query.queryBar.clearInputLabel": "Effacer l'entrée", + "kql.query.queryBar.comboboxAriaLabel": "Rechercher et filtrer la page {pageType}", "unifiedSearch.query.queryBar.esqlMenu.documentation": "Documentation", "unifiedSearch.query.queryBar.esqlMenu.exampleQueries": "Requêtes recommandées", "unifiedSearch.query.queryBar.esqlMenu.feedback": "Soumettre un commentaire", @@ -8087,15 +8087,15 @@ "unifiedSearch.query.queryBar.indexPattern.manageFieldButton": "Gérer cette vue de données", "unifiedSearch.query.queryBar.indexPattern.temporaryDataviewLabel": "Temporaire", "unifiedSearch.query.queryBar.kqlLanguageName": "KQL", - "unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoDocLinkText": "documents", - "unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoOptOutText": "Ne plus afficher", - "unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoText": "Il semblerait que votre requête porte sur un champ imbriqué. Selon le résultat visé, il existe plusieurs façons de construire une syntaxe KQL pour des requêtes imbriquées. Apprenez-en plus dans notre {link}.", - "unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoTitle": "Syntaxe de requête imbriquée KQL", + "kql.query.queryBar.KQLNestedQuerySyntaxInfoDocLinkText": "documents", + "kql.query.queryBar.KQLNestedQuerySyntaxInfoOptOutText": "Ne plus afficher", + "kql.query.queryBar.KQLNestedQuerySyntaxInfoText": "Il semblerait que votre requête porte sur un champ imbriqué. Selon le résultat visé, il existe plusieurs façons de construire une syntaxe KQL pour des requêtes imbriquées. Apprenez-en plus dans notre {link}.", + "kql.query.queryBar.KQLNestedQuerySyntaxInfoTitle": "Syntaxe de requête imbriquée KQL", "unifiedSearch.query.queryBar.luceneLanguageName": "Lucene", - "unifiedSearch.query.queryBar.searchInputAriaLabel": "Commencer à taper pour rechercher et filtrer la page {pageType}", - "unifiedSearch.query.queryBar.searchInputPlaceholder": "Filtrer vos données à l'aide de la syntaxe {language}", - "unifiedSearch.query.queryBar.searchInputPlaceholderForText": "Filtrer vos données", - "unifiedSearch.query.queryBar.syntaxOptionsTitle": "Options de syntaxe", + "kql.query.queryBar.searchInputAriaLabel": "Commencer à taper pour rechercher et filtrer la page {pageType}", + "kql.query.queryBar.searchInputPlaceholder": "Filtrer vos données à l'aide de la syntaxe {language}", + "kql.query.queryBar.searchInputPlaceholderForText": "Filtrer vos données", + "kql.query.queryBar.syntaxOptionsTitle": "Options de syntaxe", "unifiedSearch.query.queryBar.textBasedNonTimestampWarning": "La sélection de plage de données pour les requêtes en {language} requiert la présence d'un champ @timestamp dans l'ensemble de données.", "unifiedSearch.queryBarTopRow.datePicker.disabledLabel": "Tout le temps", "unifiedSearch.queryBarTopRow.submitButton.cancel": "Annuler", @@ -8143,7 +8143,7 @@ "unifiedSearch.searchBar.savedQueryWeekToDateLabel": "Hebdomadaire à ce jour", "unifiedSearch.searchBar.savedQueryYearToDateLabel": "Annuel à ce jour", "unifiedSearch.searchBar.savedQueryYesterdayLabel": "Hier", - "unifiedSearch.switchLanguage.buttonText": "Bouton de changement de langue.", + "kql.switchLanguage.buttonText": "Bouton de changement de langue.", "unifiedSearch.triggers.updateFilterReferencesTrigger": "Mettre à jour les références de filtre", "unifiedSearch.triggers.updateFilterReferencesTriggerDescription": "Mettre à jour les références de filtre", "unsavedChangesBadge.contextMenu.openButton": "Afficher les actions disponibles", diff --git a/x-pack/platform/plugins/private/translations/translations/ja-JP.json b/x-pack/platform/plugins/private/translations/translations/ja-JP.json index 5bc59abf0e45d..23e66554a3522 100644 --- a/x-pack/platform/plugins/private/translations/translations/ja-JP.json +++ b/x-pack/platform/plugins/private/translations/translations/ja-JP.json @@ -8057,22 +8057,22 @@ "unifiedSearch.filter.options.saveCurrentFilterSetLabel": "新規保存", "unifiedSearch.filter.options.saveFilterSetLabel": "クエリーを保存", "unifiedSearch.filter.options.unpinAllFiltersButtonLabel": "すべてのピンを外す", - "unifiedSearch.kueryAutocomplete.andOperatorDescription": "{bothArguments}がtrueであることを条件とする", - "unifiedSearch.kueryAutocomplete.andOperatorDescription.bothArgumentsText": "両方の引数", - "unifiedSearch.kueryAutocomplete.equalOperatorDescription": "一部の値に{equals}", - "unifiedSearch.kueryAutocomplete.equalOperatorDescription.equalsText": "一致する", - "unifiedSearch.kueryAutocomplete.existOperatorDescription": "いずれかの形式中に{exists}", - "unifiedSearch.kueryAutocomplete.existOperatorDescription.existsText": "存在する", - "unifiedSearch.kueryAutocomplete.greaterThanOperatorDescription": "が一部の値{greaterThan}", - "unifiedSearch.kueryAutocomplete.greaterThanOperatorDescription.greaterThanText": "次より大きい", - "unifiedSearch.kueryAutocomplete.greaterThanOrEqualOperatorDescription": "が一部の値{greaterThanOrEqualTo}", - "unifiedSearch.kueryAutocomplete.greaterThanOrEqualOperatorDescription.greaterThanOrEqualToText": "よりも大きいまたは等しい", - "unifiedSearch.kueryAutocomplete.lessThanOperatorDescription": "が一部の値{lessThan}", - "unifiedSearch.kueryAutocomplete.lessThanOperatorDescription.lessThanText": "次より小さい", - "unifiedSearch.kueryAutocomplete.lessThanOrEqualOperatorDescription": "が一部の値{lessThanOrEqualTo}", - "unifiedSearch.kueryAutocomplete.lessThanOrEqualOperatorDescription.lessThanOrEqualToText": "より小さいまたは等しい", - "unifiedSearch.kueryAutocomplete.orOperatorDescription": "{oneOrMoreArguments}がtrueであることを条件とする", - "unifiedSearch.kueryAutocomplete.orOperatorDescription.oneOrMoreArgumentsText": "1つ以上の引数", + "kql.kueryAutocomplete.andOperatorDescription": "{bothArguments}がtrueであることを条件とする", + "kql.kueryAutocomplete.andOperatorDescription.bothArgumentsText": "両方の引数", + "kql.kueryAutocomplete.equalOperatorDescription": "一部の値に{equals}", + "kql.kueryAutocomplete.equalOperatorDescription.equalsText": "一致する", + "kql.kueryAutocomplete.existOperatorDescription": "いずれかの形式中に{exists}", + "kql.kueryAutocomplete.existOperatorDescription.existsText": "存在する", + "kql.kueryAutocomplete.greaterThanOperatorDescription": "が一部の値{greaterThan}", + "kql.kueryAutocomplete.greaterThanOperatorDescription.greaterThanText": "次より大きい", + "kql.kueryAutocomplete.greaterThanOrEqualOperatorDescription": "が一部の値{greaterThanOrEqualTo}", + "kql.kueryAutocomplete.greaterThanOrEqualOperatorDescription.greaterThanOrEqualToText": "よりも大きいまたは等しい", + "kql.kueryAutocomplete.lessThanOperatorDescription": "が一部の値{lessThan}", + "kql.kueryAutocomplete.lessThanOperatorDescription.lessThanText": "次より小さい", + "kql.kueryAutocomplete.lessThanOrEqualOperatorDescription": "が一部の値{lessThanOrEqualTo}", + "kql.kueryAutocomplete.lessThanOrEqualOperatorDescription.lessThanOrEqualToText": "より小さいまたは等しい", + "kql.kueryAutocomplete.orOperatorDescription": "{oneOrMoreArguments}がtrueであることを条件とする", + "kql.kueryAutocomplete.orOperatorDescription.oneOrMoreArgumentsText": "1つ以上の引数", "unifiedSearch.noDataPopover.content": "この時間範囲にはデータが含まれていません。表示するフィールドを増やし、グラフを作成するには、時間範囲を広げるか、調整してください。", "unifiedSearch.noDataPopover.dismissAction": "今後表示しない", "unifiedSearch.noDataPopover.subtitle": "ヒント", @@ -8080,8 +8080,8 @@ "unifiedSearch.optionsList.popover.sortDirections": "並べ替え方向", "unifiedSearch.optionsList.popover.sortOrder.asc": "昇順", "unifiedSearch.optionsList.popover.sortOrder.desc": "降順", - "unifiedSearch.query.queryBar.clearInputLabel": "インプットを消去", - "unifiedSearch.query.queryBar.comboboxAriaLabel": "{pageType} ページの検索とフィルタリング", + "kql.query.queryBar.clearInputLabel": "インプットを消去", + "kql.query.queryBar.comboboxAriaLabel": "{pageType} ページの検索とフィルタリング", "unifiedSearch.query.queryBar.esqlMenu.documentation": "ドキュメンテーション", "unifiedSearch.query.queryBar.esqlMenu.exampleQueries": "推奨クエリ", "unifiedSearch.query.queryBar.esqlMenu.feedback": "フィードバックを送信", @@ -8098,15 +8098,15 @@ "unifiedSearch.query.queryBar.indexPattern.manageFieldButton": "このデータビューを管理", "unifiedSearch.query.queryBar.indexPattern.temporaryDataviewLabel": "一時", "unifiedSearch.query.queryBar.kqlLanguageName": "KQL", - "unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoDocLinkText": "ドキュメント", - "unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoOptOutText": "今後表示しない", - "unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoText": "ネストされたフィールドをクエリーされているようです。ネストされたクエリに対しては、ご希望の結果により異なる方法でKQL構文を構築することができます。{link}で詳細をご覧ください。", - "unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoTitle": "KQL ネストされたクエリー構文", + "kql.query.queryBar.KQLNestedQuerySyntaxInfoDocLinkText": "ドキュメント", + "kql.query.queryBar.KQLNestedQuerySyntaxInfoOptOutText": "今後表示しない", + "kql.query.queryBar.KQLNestedQuerySyntaxInfoText": "ネストされたフィールドをクエリーされているようです。ネストされたクエリに対しては、ご希望の結果により異なる方法でKQL構文を構築することができます。{link}で詳細をご覧ください。", + "kql.query.queryBar.KQLNestedQuerySyntaxInfoTitle": "KQL ネストされたクエリー構文", "unifiedSearch.query.queryBar.luceneLanguageName": "Lucene", - "unifiedSearch.query.queryBar.searchInputAriaLabel": "{pageType} ページの検索とフィルタリングを行うには入力を開始してください", - "unifiedSearch.query.queryBar.searchInputPlaceholder": "{language}構文を使用してデータをフィルタリング", - "unifiedSearch.query.queryBar.searchInputPlaceholderForText": "データのフィルタリング", - "unifiedSearch.query.queryBar.syntaxOptionsTitle": "構文オプション", + "kql.query.queryBar.searchInputAriaLabel": "{pageType} ページの検索とフィルタリングを行うには入力を開始してください", + "kql.query.queryBar.searchInputPlaceholder": "{language}構文を使用してデータをフィルタリング", + "kql.query.queryBar.searchInputPlaceholderForText": "データのフィルタリング", + "kql.query.queryBar.syntaxOptionsTitle": "構文オプション", "unifiedSearch.query.queryBar.textBasedNonTimestampWarning": "{language}クエリーの日付範囲選択では、データセットに@timestampフィールドが存在している必要があります。", "unifiedSearch.queryBarTopRow.datePicker.disabledLabel": "常時", "unifiedSearch.queryBarTopRow.submitButton.cancel": "キャンセル", @@ -8154,7 +8154,7 @@ "unifiedSearch.searchBar.savedQueryWeekToDateLabel": "週初めから今日まで", "unifiedSearch.searchBar.savedQueryYearToDateLabel": "年度の頭から今日まで", "unifiedSearch.searchBar.savedQueryYesterdayLabel": "昨日", - "unifiedSearch.switchLanguage.buttonText": "言語の切り替えボタン。", + "kql.switchLanguage.buttonText": "言語の切り替えボタン。", "unifiedSearch.triggers.updateFilterReferencesTrigger": "フィルター参照を更新", "unifiedSearch.triggers.updateFilterReferencesTriggerDescription": "フィルター参照を更新", "unsavedChangesBadge.contextMenu.openButton": "使用可能なアクションを表示", diff --git a/x-pack/platform/plugins/private/translations/translations/zh-CN.json b/x-pack/platform/plugins/private/translations/translations/zh-CN.json index fda90217f31b2..6ab3a4da27aaf 100644 --- a/x-pack/platform/plugins/private/translations/translations/zh-CN.json +++ b/x-pack/platform/plugins/private/translations/translations/zh-CN.json @@ -8047,22 +8047,22 @@ "unifiedSearch.filter.options.saveCurrentFilterSetLabel": "另存为新的", "unifiedSearch.filter.options.saveFilterSetLabel": "保存查询", "unifiedSearch.filter.options.unpinAllFiltersButtonLabel": "全部取消固定", - "unifiedSearch.kueryAutocomplete.andOperatorDescription": "需要{bothArguments}为 true", - "unifiedSearch.kueryAutocomplete.andOperatorDescription.bothArgumentsText": "两个参数都", - "unifiedSearch.kueryAutocomplete.equalOperatorDescription": "{equals}某个值", - "unifiedSearch.kueryAutocomplete.equalOperatorDescription.equalsText": "等于", - "unifiedSearch.kueryAutocomplete.existOperatorDescription": "以任意形式{exists}", - "unifiedSearch.kueryAutocomplete.existOperatorDescription.existsText": "存在", - "unifiedSearch.kueryAutocomplete.greaterThanOperatorDescription": "{greaterThan}某个值", - "unifiedSearch.kueryAutocomplete.greaterThanOperatorDescription.greaterThanText": "大于", - "unifiedSearch.kueryAutocomplete.greaterThanOrEqualOperatorDescription": "{greaterThanOrEqualTo}某个值", - "unifiedSearch.kueryAutocomplete.greaterThanOrEqualOperatorDescription.greaterThanOrEqualToText": "大于或等于", - "unifiedSearch.kueryAutocomplete.lessThanOperatorDescription": "{lessThan}某个值", - "unifiedSearch.kueryAutocomplete.lessThanOperatorDescription.lessThanText": "小于", - "unifiedSearch.kueryAutocomplete.lessThanOrEqualOperatorDescription": "{lessThanOrEqualTo}某个值", - "unifiedSearch.kueryAutocomplete.lessThanOrEqualOperatorDescription.lessThanOrEqualToText": "小于或等于", - "unifiedSearch.kueryAutocomplete.orOperatorDescription": "需要{oneOrMoreArguments}为 true", - "unifiedSearch.kueryAutocomplete.orOperatorDescription.oneOrMoreArgumentsText": "一个或多个参数", + "kql.kueryAutocomplete.andOperatorDescription": "需要{bothArguments}为 true", + "kql.kueryAutocomplete.andOperatorDescription.bothArgumentsText": "两个参数都", + "kql.kueryAutocomplete.equalOperatorDescription": "{equals}某个值", + "kql.kueryAutocomplete.equalOperatorDescription.equalsText": "等于", + "kql.kueryAutocomplete.existOperatorDescription": "以任意形式{exists}", + "kql.kueryAutocomplete.existOperatorDescription.existsText": "存在", + "kql.kueryAutocomplete.greaterThanOperatorDescription": "{greaterThan}某个值", + "kql.kueryAutocomplete.greaterThanOperatorDescription.greaterThanText": "大于", + "kql.kueryAutocomplete.greaterThanOrEqualOperatorDescription": "{greaterThanOrEqualTo}某个值", + "kql.kueryAutocomplete.greaterThanOrEqualOperatorDescription.greaterThanOrEqualToText": "大于或等于", + "kql.kueryAutocomplete.lessThanOperatorDescription": "{lessThan}某个值", + "kql.kueryAutocomplete.lessThanOperatorDescription.lessThanText": "小于", + "kql.kueryAutocomplete.lessThanOrEqualOperatorDescription": "{lessThanOrEqualTo}某个值", + "kql.kueryAutocomplete.lessThanOrEqualOperatorDescription.lessThanOrEqualToText": "小于或等于", + "kql.kueryAutocomplete.orOperatorDescription": "需要{oneOrMoreArguments}为 true", + "kql.kueryAutocomplete.orOperatorDescription.oneOrMoreArgumentsText": "一个或多个参数", "unifiedSearch.noDataPopover.content": "此时间范围不包含任何数据。增大或调整时间范围,以查看更多的字段并创建图表。", "unifiedSearch.noDataPopover.dismissAction": "不再显示", "unifiedSearch.noDataPopover.subtitle": "提示", @@ -8070,8 +8070,8 @@ "unifiedSearch.optionsList.popover.sortDirections": "排序方向", "unifiedSearch.optionsList.popover.sortOrder.asc": "升序", "unifiedSearch.optionsList.popover.sortOrder.desc": "降序", - "unifiedSearch.query.queryBar.clearInputLabel": "清除输入", - "unifiedSearch.query.queryBar.comboboxAriaLabel": "搜索并筛选 {pageType} 页面", + "kql.query.queryBar.clearInputLabel": "清除输入", + "kql.query.queryBar.comboboxAriaLabel": "搜索并筛选 {pageType} 页面", "unifiedSearch.query.queryBar.esqlMenu.documentation": "文档", "unifiedSearch.query.queryBar.esqlMenu.exampleQueries": "建议的查询", "unifiedSearch.query.queryBar.esqlMenu.feedback": "提交反馈", @@ -8088,15 +8088,15 @@ "unifiedSearch.query.queryBar.indexPattern.manageFieldButton": "管理此数据视图", "unifiedSearch.query.queryBar.indexPattern.temporaryDataviewLabel": "临时", "unifiedSearch.query.queryBar.kqlLanguageName": "KQL", - "unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoDocLinkText": "文档", - "unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoOptOutText": "不再显示", - "unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoText": "似乎您正在查询嵌套字段。您可以使用不同的方式构造嵌套查询的 KQL 语法,具体取决于您想要的结果。详细了解我们的 {link}。", - "unifiedSearch.query.queryBar.KQLNestedQuerySyntaxInfoTitle": "KQL 嵌套查询语法", + "kql.query.queryBar.KQLNestedQuerySyntaxInfoDocLinkText": "文档", + "kql.query.queryBar.KQLNestedQuerySyntaxInfoOptOutText": "不再显示", + "kql.query.queryBar.KQLNestedQuerySyntaxInfoText": "似乎您正在查询嵌套字段。您可以使用不同的方式构造嵌套查询的 KQL 语法,具体取决于您想要的结果。详细了解我们的 {link}。", + "kql.query.queryBar.KQLNestedQuerySyntaxInfoTitle": "KQL 嵌套查询语法", "unifiedSearch.query.queryBar.luceneLanguageName": "Lucene", - "unifiedSearch.query.queryBar.searchInputAriaLabel": "开始键入内容,以搜索并筛选 {pageType} 页面", - "unifiedSearch.query.queryBar.searchInputPlaceholder": "使用 {language} 语法筛选数据", - "unifiedSearch.query.queryBar.searchInputPlaceholderForText": "筛选您的数据", - "unifiedSearch.query.queryBar.syntaxOptionsTitle": "语法选项", + "kql.query.queryBar.searchInputAriaLabel": "开始键入内容,以搜索并筛选 {pageType} 页面", + "kql.query.queryBar.searchInputPlaceholder": "使用 {language} 语法筛选数据", + "kql.query.queryBar.searchInputPlaceholderForText": "筛选您的数据", + "kql.query.queryBar.syntaxOptionsTitle": "语法选项", "unifiedSearch.query.queryBar.textBasedNonTimestampWarning": "{language} 查询的日期范围选择要求数据集中存在 @timestamp 字段。", "unifiedSearch.queryBarTopRow.datePicker.disabledLabel": "所有时间", "unifiedSearch.queryBarTopRow.submitButton.cancel": "取消", @@ -8144,7 +8144,7 @@ "unifiedSearch.searchBar.savedQueryWeekToDateLabel": "本周迄今为止", "unifiedSearch.searchBar.savedQueryYearToDateLabel": "本年迄今为止", "unifiedSearch.searchBar.savedQueryYesterdayLabel": "昨天", - "unifiedSearch.switchLanguage.buttonText": "切换语言按钮。", + "kql.switchLanguage.buttonText": "切换语言按钮。", "unifiedSearch.triggers.updateFilterReferencesTrigger": "更新筛选参考", "unifiedSearch.triggers.updateFilterReferencesTriggerDescription": "更新筛选参考", "unsavedChangesBadge.contextMenu.openButton": "查看可用操作", diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.tsx b/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.tsx index 6ac94e4f2d9ed..c48f798804315 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.tsx +++ b/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.tsx @@ -8,7 +8,7 @@ import React, { useCallback, useMemo, useState } from 'react'; import type { Query, TimeRange } from '@kbn/es-query'; import { compareFilters } from '@kbn/es-query'; -import type { SuggestionsAbstraction } from '@kbn/unified-search-plugin/public/typeahead/suggestions_component'; +import type { SuggestionsAbstraction } from '@kbn/kql/public/components/typeahead/suggestions_component'; import { isSiemRuleType } from '@kbn/rule-data-utils'; import type { EuiContextMenuPanelDescriptor, diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/rules_list/components/kql_search_bar/constants.tsx b/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/rules_list/components/kql_search_bar/constants.tsx index a84487e0a8854..5ddb08481aa99 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/rules_list/components/kql_search_bar/constants.tsx +++ b/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/rules_list/components/kql_search_bar/constants.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import type { SuggestionsAbstraction } from '@kbn/unified-search-plugin/public/typeahead/suggestions_component'; +import type { SuggestionsAbstraction } from '@kbn/kql/public/components/typeahead/suggestions_component'; export const suggestionsAbstraction: SuggestionsAbstraction = { type: 'rules', diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/rules_list/components/kql_search_bar/helpers.tsx b/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/rules_list/components/kql_search_bar/helpers.tsx index 42fe507e61a90..7c48e3dd40ce1 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/rules_list/components/kql_search_bar/helpers.tsx +++ b/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/rules_list/components/kql_search_bar/helpers.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import type { SuggestionsAbstraction } from '@kbn/unified-search-plugin/public/typeahead/suggestions_component'; +import type { SuggestionsAbstraction } from '@kbn/kql/public/components/typeahead/suggestions_component'; export const enhanceSuggestionAbstractionFields = ( enhanceSuggestionsAbstraction: SuggestionsAbstraction diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/rules_list/components/kql_search_bar/validate_kuery_node.tsx b/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/rules_list/components/kql_search_bar/validate_kuery_node.tsx index db74ef6a23ef5..f6cdbb1d946dd 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/rules_list/components/kql_search_bar/validate_kuery_node.tsx +++ b/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/rules_list/components/kql_search_bar/validate_kuery_node.tsx @@ -5,7 +5,7 @@ * 2.0. */ import type { KueryNode } from '@kbn/es-query'; -import type { SuggestionsAbstraction } from '@kbn/unified-search-plugin/public/typeahead/suggestions_component'; +import type { SuggestionsAbstraction } from '@kbn/kql/public/components/typeahead/suggestions_component'; import { alertMappings } from '@kbn/alerting-plugin/common'; import { get } from 'lodash'; From 7dac42429d883147710fb1fc005c6749b4cde5e0 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 19 Dec 2025 11:17:00 +0000 Subject: [PATCH 19/60] Changes from node scripts/lint_ts_projects --fix --- .../packages/shared/kbn-alerts-ui-shared/tsconfig.json | 1 + .../platform/plugins/shared/triggers_actions_ui/tsconfig.json | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/platform/packages/shared/kbn-alerts-ui-shared/tsconfig.json b/src/platform/packages/shared/kbn-alerts-ui-shared/tsconfig.json index f61b6593b9379..995642f84c1ff 100644 --- a/src/platform/packages/shared/kbn-alerts-ui-shared/tsconfig.json +++ b/src/platform/packages/shared/kbn-alerts-ui-shared/tsconfig.json @@ -37,5 +37,6 @@ "@kbn/controls-constants", "@kbn/es-ui-shared-plugin", "@kbn/react-query", + "@kbn/kql", ] } diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/tsconfig.json b/x-pack/platform/plugins/shared/triggers_actions_ui/tsconfig.json index 62c1bb46dd27d..57b09819964fa 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/tsconfig.json +++ b/x-pack/platform/plugins/shared/triggers_actions_ui/tsconfig.json @@ -93,7 +93,8 @@ "@kbn/react-query", "@kbn/router-utils", "@kbn/maintenance-windows-plugin", - "@kbn/actions-types" + "@kbn/actions-types", + "@kbn/kql" ], "exclude": ["target/**/*"] } From 863bfe03f0ce8b7eb4dd2d37421afa3c31036040 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 19 Dec 2025 11:31:31 +0000 Subject: [PATCH 20/60] Changes from node scripts/regenerate_moon_projects.js --update --- src/platform/packages/shared/kbn-alerts-ui-shared/moon.yml | 1 + x-pack/platform/plugins/shared/triggers_actions_ui/moon.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/src/platform/packages/shared/kbn-alerts-ui-shared/moon.yml b/src/platform/packages/shared/kbn-alerts-ui-shared/moon.yml index f40259480ce7b..1bc5d859a8c96 100644 --- a/src/platform/packages/shared/kbn-alerts-ui-shared/moon.yml +++ b/src/platform/packages/shared/kbn-alerts-ui-shared/moon.yml @@ -48,6 +48,7 @@ dependsOn: - '@kbn/controls-constants' - '@kbn/es-ui-shared-plugin' - '@kbn/react-query' + - '@kbn/kql' tags: - shared-browser - package diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/moon.yml b/x-pack/platform/plugins/shared/triggers_actions_ui/moon.yml index c8ee7b374267f..6339f8b342403 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/moon.yml +++ b/x-pack/platform/plugins/shared/triggers_actions_ui/moon.yml @@ -100,6 +100,7 @@ dependsOn: - '@kbn/router-utils' - '@kbn/maintenance-windows-plugin' - '@kbn/actions-types' + - '@kbn/kql' tags: - plugin - prod From 8e302f5d008b798a119ef401b20ac48e1b743dcd Mon Sep 17 00:00:00 2001 From: Stratoula Date: Fri, 19 Dec 2025 13:22:38 +0100 Subject: [PATCH 21/60] Fixes --- .../plugins/private/event_annotation_listing/kibana.jsonc | 1 + src/platform/plugins/shared/kql/kibana.jsonc | 4 +++- src/platform/plugins/shared/kql/public/index.ts | 7 +++++++ x-pack/platform/plugins/private/monitoring/kibana.jsonc | 1 + x-pack/platform/plugins/shared/fleet/kibana.jsonc | 1 + x-pack/platform/plugins/shared/lens/kibana.jsonc | 1 + .../observability/plugins/observability/kibana.jsonc | 1 + 7 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/platform/plugins/private/event_annotation_listing/kibana.jsonc b/src/platform/plugins/private/event_annotation_listing/kibana.jsonc index 7c6aaf4939d33..0ec911379699c 100644 --- a/src/platform/plugins/private/event_annotation_listing/kibana.jsonc +++ b/src/platform/plugins/private/event_annotation_listing/kibana.jsonc @@ -19,6 +19,7 @@ "visualizations", "dataViews", "unifiedSearch", + "kql", "kibanaUtils", "contentManagement" ], diff --git a/src/platform/plugins/shared/kql/kibana.jsonc b/src/platform/plugins/shared/kql/kibana.jsonc index df8558dcd13be..6ed9af0807351 100644 --- a/src/platform/plugins/shared/kql/kibana.jsonc +++ b/src/platform/plugins/shared/kql/kibana.jsonc @@ -16,6 +16,8 @@ "dataViews", "uiActions", ], - "requiredBundles": [] + "requiredBundles": [ + "kibanaUtils" + ] } } diff --git a/src/platform/plugins/shared/kql/public/index.ts b/src/platform/plugins/shared/kql/public/index.ts index 1b3722462553a..57cca559df970 100644 --- a/src/platform/plugins/shared/kql/public/index.ts +++ b/src/platform/plugins/shared/kql/public/index.ts @@ -6,6 +6,9 @@ * 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 type { PluginInitializerContext } from '@kbn/core/public'; +import { KqlPlugin } from './plugin'; + export type { KqlPluginStart, KqlPluginSetup } from './plugin'; export type { QueryStringInputProps } from './components/query_string_input/query_string_input'; export { QueryStringInput } from './components/query_string_input/query_string_input'; @@ -24,3 +27,7 @@ export { type SuggestionsAbstraction } from './components/typeahead/suggestions_ export type { SuggestionsListSize } from './components/typeahead/suggestions_component'; export { QuerySuggestionTypes } from './autocomplete/providers/query_suggestion_provider'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new KqlPlugin(initializerContext); +} diff --git a/x-pack/platform/plugins/private/monitoring/kibana.jsonc b/x-pack/platform/plugins/private/monitoring/kibana.jsonc index 299864be0486d..9547693cdd7ac 100644 --- a/x-pack/platform/plugins/private/monitoring/kibana.jsonc +++ b/x-pack/platform/plugins/private/monitoring/kibana.jsonc @@ -16,6 +16,7 @@ "navigation", "dataViews", "unifiedSearch", + "kql", "share", "fieldsMetadata" ], diff --git a/x-pack/platform/plugins/shared/fleet/kibana.jsonc b/x-pack/platform/plugins/shared/fleet/kibana.jsonc index 9da130586c023..4542cf4ad1ddb 100644 --- a/x-pack/platform/plugins/shared/fleet/kibana.jsonc +++ b/x-pack/platform/plugins/shared/fleet/kibana.jsonc @@ -23,6 +23,7 @@ "share", "security", "unifiedSearch", + "kql", "savedObjectsTagging", "taskManager", "files", diff --git a/x-pack/platform/plugins/shared/lens/kibana.jsonc b/x-pack/platform/plugins/shared/lens/kibana.jsonc index bdd9eaa1cdb06..cfd1a7a67c4cd 100644 --- a/x-pack/platform/plugins/shared/lens/kibana.jsonc +++ b/x-pack/platform/plugins/shared/lens/kibana.jsonc @@ -34,6 +34,7 @@ "expressionTagcloud", "eventAnnotation", "unifiedSearch", + "kql", "contentManagement" ], "optionalPlugins": [ diff --git a/x-pack/solutions/observability/plugins/observability/kibana.jsonc b/x-pack/solutions/observability/plugins/observability/kibana.jsonc index 668ea5debc214..3290d39943539 100644 --- a/x-pack/solutions/observability/plugins/observability/kibana.jsonc +++ b/x-pack/solutions/observability/plugins/observability/kibana.jsonc @@ -31,6 +31,7 @@ "security", "share", "unifiedSearch", + "kql", "visualizations", "dashboard", "expressions", From b1115b0f1047c35fc286cd32bd59b0b26cd60f03 Mon Sep 17 00:00:00 2001 From: Stratoula Date: Fri, 19 Dec 2025 13:54:51 +0100 Subject: [PATCH 22/60] Fixes --- src/platform/plugins/shared/vis_types/timeseries/kibana.jsonc | 1 - x-pack/platform/plugins/shared/fleet/kibana.jsonc | 1 - .../solutions/observability/plugins/observability/kibana.jsonc | 1 - 3 files changed, 3 deletions(-) diff --git a/src/platform/plugins/shared/vis_types/timeseries/kibana.jsonc b/src/platform/plugins/shared/vis_types/timeseries/kibana.jsonc index d5d4a7c531b65..d14c110dc4a55 100644 --- a/src/platform/plugins/shared/vis_types/timeseries/kibana.jsonc +++ b/src/platform/plugins/shared/vis_types/timeseries/kibana.jsonc @@ -31,7 +31,6 @@ "home" ], "requiredBundles": [ - "unifiedSearch", "kibanaUtils", "kibanaReact", "fieldFormats" diff --git a/x-pack/platform/plugins/shared/fleet/kibana.jsonc b/x-pack/platform/plugins/shared/fleet/kibana.jsonc index 4542cf4ad1ddb..4e132a8830165 100644 --- a/x-pack/platform/plugins/shared/fleet/kibana.jsonc +++ b/x-pack/platform/plugins/shared/fleet/kibana.jsonc @@ -53,7 +53,6 @@ "logsShared", "kibanaUtils", "usageCollection", - "unifiedSearch" ], "extraPublicDirs": [ "common" diff --git a/x-pack/solutions/observability/plugins/observability/kibana.jsonc b/x-pack/solutions/observability/plugins/observability/kibana.jsonc index 3290d39943539..5a63858991451 100644 --- a/x-pack/solutions/observability/plugins/observability/kibana.jsonc +++ b/x-pack/solutions/observability/plugins/observability/kibana.jsonc @@ -30,7 +30,6 @@ "triggersActionsUi", "security", "share", - "unifiedSearch", "kql", "visualizations", "dashboard", From 864cb561fd145ac19f26798ea45fe36dc565aca2 Mon Sep 17 00:00:00 2001 From: Stratoula Date: Fri, 19 Dec 2025 14:32:13 +0100 Subject: [PATCH 23/60] Fixes --- src/platform/plugins/private/vis_default_editor/kibana.jsonc | 2 +- .../plugins/private/vis_default_editor/public/types.ts | 4 ++-- src/platform/plugins/private/vis_default_editor/tsconfig.json | 1 - src/platform/plugins/shared/kql/tsconfig.json | 1 + .../fleet/public/applications/fleet/components/search_bar.tsx | 2 -- x-pack/platform/plugins/shared/fleet/public/plugin.ts | 3 --- .../form_based/dimension_panel/reference_editor.tsx | 2 ++ .../form_based/operations/definitions/date_histogram.test.tsx | 3 +++ .../operations/definitions/filters/filters.test.tsx | 2 ++ .../form_based/operations/definitions/last_value.test.tsx | 2 ++ .../form_based/operations/definitions/percentile.test.tsx | 2 ++ .../operations/definitions/percentile_ranks.test.tsx | 2 ++ .../form_based/operations/definitions/static_value.test.tsx | 2 ++ .../form_based/operations/definitions/terms/terms.test.tsx | 2 ++ .../plugins/shared/lens/public/mocks/services_mock.tsx | 3 ++- x-pack/platform/plugins/shared/ml/public/application/app.tsx | 1 + x-pack/platform/plugins/shared/ml/public/plugin.ts | 3 +++ .../rule_types/geo_containment/rule_form/query_input.tsx | 2 ++ 18 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/platform/plugins/private/vis_default_editor/kibana.jsonc b/src/platform/plugins/private/vis_default_editor/kibana.jsonc index fa164a88a31e7..c55a3ce412869 100644 --- a/src/platform/plugins/private/vis_default_editor/kibana.jsonc +++ b/src/platform/plugins/private/vis_default_editor/kibana.jsonc @@ -13,7 +13,7 @@ "server": false, "requiredPlugins": [ "dataViews", - "unifiedSearch" + "kql" ], "optionalPlugins": [ "visualizations" diff --git a/src/platform/plugins/private/vis_default_editor/public/types.ts b/src/platform/plugins/private/vis_default_editor/public/types.ts index 02c2c7d3f7425..7b243c647dc0a 100644 --- a/src/platform/plugins/private/vis_default_editor/public/types.ts +++ b/src/platform/plugins/private/vis_default_editor/public/types.ts @@ -10,15 +10,15 @@ import type { CoreStart, DocLinksStart } from '@kbn/core/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; -import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import type { KqlPluginStart } from '@kbn/kql/public'; export interface VisDefaultEditorKibanaServices { data: DataPublicPluginStart; dataViews: DataViewsPublicPluginStart; appName: string; - unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; usageCollection?: UsageCollectionStart; storage: IStorageWrapper; notifications: CoreStart['notifications']; diff --git a/src/platform/plugins/private/vis_default_editor/tsconfig.json b/src/platform/plugins/private/vis_default_editor/tsconfig.json index e7a2d667f0cc4..3b4fe641a3250 100644 --- a/src/platform/plugins/private/vis_default_editor/tsconfig.json +++ b/src/platform/plugins/private/vis_default_editor/tsconfig.json @@ -14,7 +14,6 @@ "@kbn/kibana-utils-plugin", "@kbn/kibana-react-plugin", "@kbn/field-formats-plugin", - "@kbn/unified-search-plugin", "@kbn/data-views-plugin", "@kbn/i18n", "@kbn/es-query", diff --git a/src/platform/plugins/shared/kql/tsconfig.json b/src/platform/plugins/shared/kql/tsconfig.json index c843e8c24880d..460f492bb2eaf 100644 --- a/src/platform/plugins/shared/kql/tsconfig.json +++ b/src/platform/plugins/shared/kql/tsconfig.json @@ -7,6 +7,7 @@ "../../../../typings/**/*", "public/**/*", "server/**/*", + "public/**/*.json", ], "kbn_references": [ "@kbn/core", diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/components/search_bar.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/components/search_bar.tsx index 698903ca1d946..f2ca335d16d18 100644 --- a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/components/search_bar.tsx +++ b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/components/search_bar.tsx @@ -141,7 +141,6 @@ export const SearchBar: React.FunctionComponent = ({ docLinks, uiSettings, usageCollection, - core, } = useStartServices(); const [dataView, setDataView] = useState(); @@ -200,7 +199,6 @@ export const SearchBar: React.FunctionComponent = ({ appName={i18n.translate('xpack.fleet.appTitle', { defaultMessage: 'Fleet' })} deps={{ autocomplete: kql.autocomplete, - core, notifications, http, docLinks, diff --git a/x-pack/platform/plugins/shared/fleet/public/plugin.ts b/x-pack/platform/plugins/shared/fleet/public/plugin.ts index e1b5ccea27823..625a81100bc1b 100644 --- a/x-pack/platform/plugins/shared/fleet/public/plugin.ts +++ b/x-pack/platform/plugins/shared/fleet/public/plugin.ts @@ -150,7 +150,6 @@ export interface FleetStartServices extends CoreStart, Exclude { @@ -109,6 +111,7 @@ const defaultOptions = { data: dataStart, fieldFormats: fieldFormatsServiceMock.createStartContract(), unifiedSearch: unifiedSearchStart, + kql: kqlStart, dataViews: dataViewsStart, http: {} as HttpSetup, indexPattern: indexPattern1, diff --git a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/filters/filters.test.tsx b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/filters/filters.test.tsx index d5ef4dba99df3..a2da93416bdd2 100644 --- a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/filters/filters.test.tsx +++ b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/filters/filters.test.tsx @@ -14,6 +14,7 @@ import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { fireEvent, screen } from '@testing-library/react'; +import { kqlPluginMock } from '@kbn/kql/public/mocks'; import type { FiltersIndexPatternColumn, FormBasedLayer } from '@kbn/lens-common'; import { filtersOperation } from '..'; import { createMockedIndexPattern } from '../../../mocks'; @@ -29,6 +30,7 @@ const defaultProps = { data: dataPluginMock.createStartContract(), fieldFormats: fieldFormatsServiceMock.createStartContract(), unifiedSearch: unifiedSearchPluginMock.createStartContract(), + kql: kqlPluginMock.createStartContract(), dataViews: dataViewPluginMocks.createStartContract(), http: {} as HttpSetup, indexPattern: createMockedIndexPattern(), diff --git a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/last_value.test.tsx b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/last_value.test.tsx index ada50718858b8..e6ecd57c30ace 100644 --- a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/last_value.test.tsx +++ b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/last_value.test.tsx @@ -14,6 +14,7 @@ import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks import type { IUiSettingsClient, HttpSetup } from '@kbn/core/public'; import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import { kqlPluginMock } from '@kbn/kql/public/mocks'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { createMockedIndexPattern } from '../../mocks'; import type { @@ -35,6 +36,7 @@ const defaultProps = { dateRange: { fromDate: 'now-1d', toDate: 'now' }, fieldFormats: fieldFormatsServiceMock.createStartContract(), unifiedSearch: unifiedSearchPluginMock.createStartContract(), + kql: kqlPluginMock.createStartContract(), dataViews: dataViewPluginMocks.createStartContract(), data: dataPluginMock.createStartContract(), http: {} as HttpSetup, diff --git a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/percentile.test.tsx b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/percentile.test.tsx index 3d9ebb2a65184..4d38309e4f80c 100644 --- a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/percentile.test.tsx +++ b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/percentile.test.tsx @@ -12,6 +12,7 @@ import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; +import { kqlPluginMock } from '@kbn/kql/public/mocks'; import { faker } from '@faker-js/faker'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; @@ -41,6 +42,7 @@ const defaultProps = { data: dataPluginMock.createStartContract(), fieldFormats: fieldFormatsServiceMock.createStartContract(), unifiedSearch: unifiedSearchPluginMock.createStartContract(), + kql: kqlPluginMock.createStartContract(), dataViews: dataViewPluginMocks.createStartContract(), http: {} as HttpSetup, indexPattern: { diff --git a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/percentile_ranks.test.tsx b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/percentile_ranks.test.tsx index 0d1dd3eaff2af..6adb159cda668 100644 --- a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/percentile_ranks.test.tsx +++ b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/percentile_ranks.test.tsx @@ -11,6 +11,7 @@ import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks' import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import { kqlPluginMock } from '@kbn/kql/public/mocks'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; @@ -32,6 +33,7 @@ const defaultProps = { data: dataPluginMock.createStartContract(), fieldFormats: fieldFormatsServiceMock.createStartContract(), unifiedSearch: unifiedSearchPluginMock.createStartContract(), + kql: kqlPluginMock.createStartContract(), dataViews: dataViewPluginMocks.createStartContract(), http: {} as HttpSetup, indexPattern: { diff --git a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/static_value.test.tsx b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/static_value.test.tsx index c557be19db744..08bfa872226c7 100644 --- a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/static_value.test.tsx +++ b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/static_value.test.tsx @@ -11,6 +11,7 @@ import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks'; import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import { kqlPluginMock } from '@kbn/kql/public/mocks'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; @@ -36,6 +37,7 @@ const defaultProps = { data: dataPluginMock.createStartContract(), fieldFormats: fieldFormatsServiceMock.createStartContract(), unifiedSearch: unifiedSearchPluginMock.createStartContract(), + kql: kqlPluginMock.createStartContract(), dataViews: dataViewPluginMocks.createStartContract(), http: {} as HttpSetup, indexPattern: { diff --git a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/terms/terms.test.tsx b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/terms/terms.test.tsx index 6d160c4e4dbc6..ca22ce78c3d4a 100644 --- a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/terms/terms.test.tsx +++ b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/terms/terms.test.tsx @@ -16,6 +16,7 @@ import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; +import { kqlPluginMock } from '@kbn/kql/public/mocks'; import { coreMock as corePluginMock } from '@kbn/core/public/mocks'; import { createMockedIndexPattern } from '../../../mocks'; import { ValuesInput } from './values_input'; @@ -93,6 +94,7 @@ const defaultProps = { data: dataPluginMock.createStartContract(), fieldFormats: fieldFormatsServiceMock.createStartContract(), unifiedSearch: unifiedSearchPluginMock.createStartContract(), + kql: kqlPluginMock.createStartContract(), dataViews: dataViewPluginMocks.createStartContract(), http: {} as HttpSetup, indexPattern: createMockedIndexPattern(), diff --git a/x-pack/platform/plugins/shared/lens/public/mocks/services_mock.tsx b/x-pack/platform/plugins/shared/lens/public/mocks/services_mock.tsx index bd4a682885f88..45ff12c365f4a 100644 --- a/x-pack/platform/plugins/shared/lens/public/mocks/services_mock.tsx +++ b/x-pack/platform/plugins/shared/lens/public/mocks/services_mock.tsx @@ -26,7 +26,7 @@ import type { EmbeddableStateTransfer } from '@kbn/embeddable-plugin/public'; import { presentationUtilPluginMock } from '@kbn/presentation-util-plugin/public/mocks'; import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks'; import type { EventAnnotationServiceType } from '@kbn/event-annotation-plugin/public'; - +import { kqlPluginMock } from '@kbn/kql/public/mocks'; import type { LensDocument, LensAppServices, LensAttributesService } from '@kbn/lens-common'; import { mockDataPlugin } from './data_plugin_mock'; import { getLensInspectorService } from '../lens_inspector_service'; @@ -151,6 +151,7 @@ export function makeDefaultServices( dataViewFieldEditor: indexPatternFieldEditorPluginMock.createStartContract(), dataViewEditor: indexPatternEditorPluginMock.createStartContract(), unifiedSearch: unifiedSearchPluginMock.createStartContract(), + kql: kqlPluginMock.createStartContract(), contentManagement: contentManagementMock.createStartContract(), eventAnnotationService: {} as EventAnnotationServiceType, }; diff --git a/x-pack/platform/plugins/shared/ml/public/application/app.tsx b/x-pack/platform/plugins/shared/ml/public/application/app.tsx index 0c3afa59644ed..2fb6c10c677cc 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/app.tsx +++ b/x-pack/platform/plugins/shared/ml/public/application/app.tsx @@ -105,6 +105,7 @@ export const App: FC = ({ triggersActionsUi: deps.triggersActionsUi, uiActions: deps.uiActions, unifiedSearch: deps.unifiedSearch, + kql: deps.kql, usageCollection: deps.usageCollection, mlServices: getMlGlobalServices(coreStart, deps.data.dataViews, deps.usageCollection), spaces: deps.spaces, diff --git a/x-pack/platform/plugins/shared/ml/public/plugin.ts b/x-pack/platform/plugins/shared/ml/public/plugin.ts index 5443a188a7b55..04267b67c15f0 100644 --- a/x-pack/platform/plugins/shared/ml/public/plugin.ts +++ b/x-pack/platform/plugins/shared/ml/public/plugin.ts @@ -54,6 +54,7 @@ import type { FieldFormatsRegistry } from '@kbn/field-formats-plugin/common'; import { ENABLE_ESQL } from '@kbn/esql-utils'; import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public'; import type { FileUploadPluginStart } from '@kbn/file-upload-plugin/public'; +import type { KqlPluginStart } from '@kbn/kql/public'; import type { MlSharedServices } from './application/services/get_shared_ml_services'; import { getMlSharedServices } from './application/services/get_shared_ml_services'; import { registerManagementSections } from './application/management'; @@ -105,6 +106,7 @@ export interface MlStartDependencies { triggersActionsUi?: TriggersAndActionsUIPublicPluginStart; uiActions: UiActionsStart; unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; telemetry: ITelemetryClient; fieldsMetadata: FieldsMetadataPublicStart; fileUpload: FileUploadPluginStart; @@ -219,6 +221,7 @@ export class MlPlugin implements Plugin { telemetry: telemetryClient, fieldsMetadata: pluginsStart.fieldsMetadata, fileUpload: pluginsStart.fileUpload, + kql: pluginsStart.kql, ...deps, }, params, diff --git a/x-pack/platform/plugins/shared/stack_alerts/public/rule_types/geo_containment/rule_form/query_input.tsx b/x-pack/platform/plugins/shared/stack_alerts/public/rule_types/geo_containment/rule_form/query_input.tsx index e367a60185624..68fa25288f1ee 100644 --- a/x-pack/platform/plugins/shared/stack_alerts/public/rule_types/geo_containment/rule_form/query_input.tsx +++ b/x-pack/platform/plugins/shared/stack_alerts/public/rule_types/geo_containment/rule_form/query_input.tsx @@ -19,6 +19,7 @@ import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { STACK_ALERTS_FEATURE_ID } from '@kbn/rule-data-utils'; +import type { KqlPluginStart } from '@kbn/kql/public'; function validateQuery(query: Query) { try { @@ -48,6 +49,7 @@ export const QueryInput = (props: Props) => { uiSettings: IUiSettingsClient; storage: IStorageWrapper; unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; usageCollection: UsageCollectionStart; }>().services; From ef7f73b70f83a09308359007c6a8e2d90ed74c45 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 19 Dec 2025 13:57:28 +0000 Subject: [PATCH 24/60] Changes from node scripts/lint_ts_projects --fix --- src/platform/plugins/private/vis_default_editor/tsconfig.json | 1 + x-pack/platform/plugins/shared/stack_alerts/tsconfig.json | 1 + 2 files changed, 2 insertions(+) diff --git a/src/platform/plugins/private/vis_default_editor/tsconfig.json b/src/platform/plugins/private/vis_default_editor/tsconfig.json index 3b4fe641a3250..5dab382314c89 100644 --- a/src/platform/plugins/private/vis_default_editor/tsconfig.json +++ b/src/platform/plugins/private/vis_default_editor/tsconfig.json @@ -33,6 +33,7 @@ "@kbn/css-utils", "@kbn/visualizations-common", "@kbn/chart-expressions-common", + "@kbn/kql", ], "exclude": [ "target/**/*", diff --git a/x-pack/platform/plugins/shared/stack_alerts/tsconfig.json b/x-pack/platform/plugins/shared/stack_alerts/tsconfig.json index 6d92a4deaf05e..6005cb3a8fd32 100644 --- a/x-pack/platform/plugins/shared/stack_alerts/tsconfig.json +++ b/x-pack/platform/plugins/shared/stack_alerts/tsconfig.json @@ -52,6 +52,7 @@ "@kbn/response-ops-rule-params", "@kbn/object-utils", "@kbn/esql-ast", + "@kbn/kql", ], "exclude": ["target/**/*"] } From 16475636ae3ae8b71c5c0183203c214c0745b032 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 19 Dec 2025 14:08:13 +0000 Subject: [PATCH 25/60] Changes from node scripts/regenerate_moon_projects.js --update --- src/platform/plugins/private/vis_default_editor/moon.yml | 2 +- src/platform/plugins/shared/kql/moon.yml | 1 + x-pack/platform/plugins/shared/stack_alerts/moon.yml | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/platform/plugins/private/vis_default_editor/moon.yml b/src/platform/plugins/private/vis_default_editor/moon.yml index 3c362e9198626..983f6f344401b 100644 --- a/src/platform/plugins/private/vis_default_editor/moon.yml +++ b/src/platform/plugins/private/vis_default_editor/moon.yml @@ -24,7 +24,6 @@ dependsOn: - '@kbn/kibana-utils-plugin' - '@kbn/kibana-react-plugin' - '@kbn/field-formats-plugin' - - '@kbn/unified-search-plugin' - '@kbn/data-views-plugin' - '@kbn/i18n' - '@kbn/es-query' @@ -44,6 +43,7 @@ dependsOn: - '@kbn/css-utils' - '@kbn/visualizations-common' - '@kbn/chart-expressions-common' + - '@kbn/kql' tags: - plugin - prod diff --git a/src/platform/plugins/shared/kql/moon.yml b/src/platform/plugins/shared/kql/moon.yml index 9bce68836e27b..7d8a0d71e9437 100644 --- a/src/platform/plugins/shared/kql/moon.yml +++ b/src/platform/plugins/shared/kql/moon.yml @@ -44,6 +44,7 @@ fileGroups: src: - public/**/* - server/**/* + - public/**/*.json - '!target/**/*' tasks: jest: diff --git a/x-pack/platform/plugins/shared/stack_alerts/moon.yml b/x-pack/platform/plugins/shared/stack_alerts/moon.yml index 0e188fde1f7da..f123652661966 100644 --- a/x-pack/platform/plugins/shared/stack_alerts/moon.yml +++ b/x-pack/platform/plugins/shared/stack_alerts/moon.yml @@ -65,6 +65,7 @@ dependsOn: - '@kbn/response-ops-rule-params' - '@kbn/object-utils' - '@kbn/esql-ast' + - '@kbn/kql' tags: - plugin - prod From f95fe021763944ace4ea445fcfff2fe5e0016c00 Mon Sep 17 00:00:00 2001 From: Stratoula Date: Mon, 22 Dec 2025 08:14:45 +0100 Subject: [PATCH 26/60] Lens cleanup --- .../form_based/dimension_panel/reference_editor.test.tsx | 4 ++-- .../form_based/dimension_panel/reference_editor.tsx | 2 -- .../form_based/operations/definitions/date_histogram.test.tsx | 3 --- .../operations/definitions/filters/filters.test.tsx | 2 -- .../form_based/operations/definitions/last_value.test.tsx | 2 -- .../form_based/operations/definitions/percentile.test.tsx | 2 -- .../operations/definitions/percentile_ranks.test.tsx | 2 -- .../form_based/operations/definitions/ranges/ranges.test.tsx | 3 --- .../form_based/operations/definitions/static_value.test.tsx | 2 -- .../form_based/operations/definitions/terms/terms.test.tsx | 2 -- 10 files changed, 2 insertions(+), 22 deletions(-) diff --git a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/dimension_panel/reference_editor.test.tsx b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/dimension_panel/reference_editor.test.tsx index e67ed7180e33f..ab271901ddaa8 100644 --- a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/dimension_panel/reference_editor.test.tsx +++ b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/dimension_panel/reference_editor.test.tsx @@ -12,7 +12,6 @@ import { act } from 'react-dom/test-utils'; import type { EuiComboBoxOptionOption, EuiComboBoxProps } from '@elastic/eui'; import { EuiComboBox } from '@elastic/eui'; import { mountWithIntl as mount } from '@kbn/test-jest-helpers'; -import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks'; import type { IUiSettingsClient, HttpSetup } from '@kbn/core/public'; @@ -29,6 +28,7 @@ import type { ReferenceEditorProps } from './reference_editor'; import { ReferenceEditor } from './reference_editor'; import { insertOrReplaceColumn, operationDefinitionMap } from '../operations'; import { FieldSelect } from './field_select'; +import type { KqlPluginStart } from '@kbn/kql/public'; jest.mock('@kbn/unified-field-list/src/hooks/use_existing_fields', () => ({ useExistingFieldsReader: jest.fn(() => { @@ -85,7 +85,7 @@ describe('reference editor', () => { http: {} as HttpSetup, data: {} as DataPublicPluginStart, fieldFormats: fieldFormatsServiceMock.createStartContract(), - unifiedSearch: {} as UnifiedSearchPublicPluginStart, + kql: {} as KqlPluginStart, dataViews: dataViewPluginMocks.createStartContract(), dimensionGroups: [], isFullscreen: false, diff --git a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/dimension_panel/reference_editor.tsx b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/dimension_panel/reference_editor.tsx index 60294ccf59a67..aa23078ccf803 100644 --- a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/dimension_panel/reference_editor.tsx +++ b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/dimension_panel/reference_editor.tsx @@ -9,7 +9,6 @@ import React, { useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import type { EuiFormRowProps, EuiComboBoxOptionOption } from '@elastic/eui'; import { EuiSpacer, EuiComboBox } from '@elastic/eui'; -import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import type { IUiSettingsClient, HttpSetup } from '@kbn/core/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; @@ -104,7 +103,6 @@ export interface ReferenceEditorProps { http: HttpSetup; data: DataPublicPluginStart; fieldFormats: FieldFormatsStart; - unifiedSearch: UnifiedSearchPublicPluginStart; kql: KqlPluginStart; dataViews: DataViewsPublicPluginStart; } diff --git a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/date_histogram.test.tsx b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/date_histogram.test.tsx index 8c21fdd1b223a..50d8f12391ea9 100644 --- a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/date_histogram.test.tsx +++ b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/date_histogram.test.tsx @@ -9,7 +9,6 @@ import React from 'react'; import { dateHistogramOperation } from '.'; import { mount, shallow } from 'enzyme'; import { EuiSwitch } from '@elastic/eui'; -import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import type { IUiSettingsClient, HttpSetup } from '@kbn/core/public'; @@ -27,7 +26,6 @@ import type { import { kqlPluginMock } from '@kbn/kql/public/mocks'; const dataStart = dataPluginMock.createStartContract(); -const unifiedSearchStart = unifiedSearchPluginMock.createStartContract(); const kqlStart = kqlPluginMock.createStartContract(); const dataViewsStart = dataViewPluginMocks.createStartContract(); dataStart.search.aggs.calculateAutoTimeExpression = getCalculateAutoTimeExpression( @@ -110,7 +108,6 @@ const defaultOptions = { }, data: dataStart, fieldFormats: fieldFormatsServiceMock.createStartContract(), - unifiedSearch: unifiedSearchStart, kql: kqlStart, dataViews: dataViewsStart, http: {} as HttpSetup, diff --git a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/filters/filters.test.tsx b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/filters/filters.test.tsx index a2da93416bdd2..3b1c3cb9c33c6 100644 --- a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/filters/filters.test.tsx +++ b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/filters/filters.test.tsx @@ -8,7 +8,6 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks'; -import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; import type { IUiSettingsClient, HttpSetup } from '@kbn/core/public'; import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; @@ -29,7 +28,6 @@ const defaultProps = { dateRange: { fromDate: 'now-1d', toDate: 'now' }, data: dataPluginMock.createStartContract(), fieldFormats: fieldFormatsServiceMock.createStartContract(), - unifiedSearch: unifiedSearchPluginMock.createStartContract(), kql: kqlPluginMock.createStartContract(), dataViews: dataViewPluginMocks.createStartContract(), http: {} as HttpSetup, diff --git a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/last_value.test.tsx b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/last_value.test.tsx index e6ecd57c30ace..f95838d2d1ea2 100644 --- a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/last_value.test.tsx +++ b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/last_value.test.tsx @@ -10,7 +10,6 @@ import type { ShallowWrapper } from 'enzyme'; import { shallow } from 'enzyme'; import { EuiComboBox, EuiFormRow } from '@elastic/eui'; import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks'; -import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; import type { IUiSettingsClient, HttpSetup } from '@kbn/core/public'; import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; @@ -35,7 +34,6 @@ const defaultProps = { uiSettings: uiSettingsMock, dateRange: { fromDate: 'now-1d', toDate: 'now' }, fieldFormats: fieldFormatsServiceMock.createStartContract(), - unifiedSearch: unifiedSearchPluginMock.createStartContract(), kql: kqlPluginMock.createStartContract(), dataViews: dataViewPluginMocks.createStartContract(), data: dataPluginMock.createStartContract(), diff --git a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/percentile.test.tsx b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/percentile.test.tsx index 4d38309e4f80c..10e42abc8928b 100644 --- a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/percentile.test.tsx +++ b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/percentile.test.tsx @@ -8,7 +8,6 @@ import React from 'react'; import type { IUiSettingsClient, HttpSetup } from '@kbn/core/public'; import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks'; -import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; @@ -41,7 +40,6 @@ const defaultProps = { dateRange: { fromDate: 'now-1d', toDate: 'now' }, data: dataPluginMock.createStartContract(), fieldFormats: fieldFormatsServiceMock.createStartContract(), - unifiedSearch: unifiedSearchPluginMock.createStartContract(), kql: kqlPluginMock.createStartContract(), dataViews: dataViewPluginMocks.createStartContract(), http: {} as HttpSetup, diff --git a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/percentile_ranks.test.tsx b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/percentile_ranks.test.tsx index 6adb159cda668..e987caa43bcb0 100644 --- a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/percentile_ranks.test.tsx +++ b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/percentile_ranks.test.tsx @@ -8,7 +8,6 @@ import React from 'react'; import type { IUiSettingsClient, HttpSetup } from '@kbn/core/public'; import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks'; -import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { kqlPluginMock } from '@kbn/kql/public/mocks'; @@ -32,7 +31,6 @@ const defaultProps = { dateRange: { fromDate: 'now-1d', toDate: 'now' }, data: dataPluginMock.createStartContract(), fieldFormats: fieldFormatsServiceMock.createStartContract(), - unifiedSearch: unifiedSearchPluginMock.createStartContract(), kql: kqlPluginMock.createStartContract(), dataViews: dataViewPluginMocks.createStartContract(), http: {} as HttpSetup, diff --git a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/ranges/ranges.test.tsx b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/ranges/ranges.test.tsx index 65b1286fc697d..9c1e7b784cc37 100644 --- a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/ranges/ranges.test.tsx +++ b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/ranges/ranges.test.tsx @@ -13,7 +13,6 @@ import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks'; -import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; import { kqlPluginMock } from '@kbn/kql/public/mocks'; import { mountWithProviders } from '../../../../../test_utils/test_utils'; import type { FormBasedLayer, RangeIndexPatternColumn, IndexPattern } from '@kbn/lens-common'; @@ -54,7 +53,6 @@ jest.mock('lodash', () => { }); const dataPluginMockValue = dataPluginMock.createStartContract(); -const unifiedSearchPluginMockValue = unifiedSearchPluginMock.createStartContract(); const kqlPluginMockValue = kqlPluginMock.createStartContract(); const fieldFormatsPluginMockValue = fieldFormatsServiceMock.createStartContract(); const dataViewsPluginMockValue = dataViewPluginMocks.createStartContract(); @@ -91,7 +89,6 @@ const defaultOptions = { }, data: dataPluginMockValue, fieldFormats: fieldFormatsPluginMockValue, - unifiedSearch: unifiedSearchPluginMockValue, kql: kqlPluginMockValue, dataViews: dataViewsPluginMockValue, http: {} as HttpSetup, diff --git a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/static_value.test.tsx b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/static_value.test.tsx index 08bfa872226c7..b4f28f39979fd 100644 --- a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/static_value.test.tsx +++ b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/static_value.test.tsx @@ -9,7 +9,6 @@ import React from 'react'; import type { IUiSettingsClient, HttpSetup } from '@kbn/core/public'; import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks'; -import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { kqlPluginMock } from '@kbn/kql/public/mocks'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; @@ -36,7 +35,6 @@ const defaultProps = { dateRange: { fromDate: 'now-1d', toDate: 'now' }, data: dataPluginMock.createStartContract(), fieldFormats: fieldFormatsServiceMock.createStartContract(), - unifiedSearch: unifiedSearchPluginMock.createStartContract(), kql: kqlPluginMock.createStartContract(), dataViews: dataViewPluginMocks.createStartContract(), http: {} as HttpSetup, diff --git a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/terms/terms.test.tsx b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/terms/terms.test.tsx index ca22ce78c3d4a..7f9f855076d77 100644 --- a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/terms/terms.test.tsx +++ b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/terms/terms.test.tsx @@ -12,7 +12,6 @@ import type { EuiComboBoxOptionOption } from '@elastic/eui'; import { EuiButtonGroup, EuiComboBox, EuiFieldNumber, EuiSelect, EuiSwitch } from '@elastic/eui'; import type { IUiSettingsClient, HttpSetup } from '@kbn/core/public'; import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks'; -import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; @@ -93,7 +92,6 @@ const defaultProps = { dateRange: { fromDate: 'now-1d', toDate: 'now' }, data: dataPluginMock.createStartContract(), fieldFormats: fieldFormatsServiceMock.createStartContract(), - unifiedSearch: unifiedSearchPluginMock.createStartContract(), kql: kqlPluginMock.createStartContract(), dataViews: dataViewPluginMocks.createStartContract(), http: {} as HttpSetup, From 602f09a779711d175b998d62c0156c8ba23f2b92 Mon Sep 17 00:00:00 2001 From: Stratoula Date: Mon, 22 Dec 2025 08:17:16 +0100 Subject: [PATCH 27/60] Cleanup synthetics --- .../apps/synthetics/contexts/synthetics_shared_context.tsx | 2 +- .../observability/plugins/synthetics/public/plugin.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/contexts/synthetics_shared_context.tsx b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/contexts/synthetics_shared_context.tsx index e26a5a05e3905..18c74e50c6151 100644 --- a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/contexts/synthetics_shared_context.tsx +++ b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/contexts/synthetics_shared_context.tsx @@ -51,7 +51,7 @@ export const SyntheticsSharedContext: React.FC< spaces: startPlugins.spaces, fleet: startPlugins.fleet, share: startPlugins.share, - unifiedSearch: startPlugins.unifiedSearch, + kql: startPlugins.kql, embeddable: startPlugins.embeddable, slo: startPlugins.slo, serverless: startPlugins.serverless, diff --git a/x-pack/solutions/observability/plugins/synthetics/public/plugin.ts b/x-pack/solutions/observability/plugins/synthetics/public/plugin.ts index 910cada578748..f2c13c67381b8 100644 --- a/x-pack/solutions/observability/plugins/synthetics/public/plugin.ts +++ b/x-pack/solutions/observability/plugins/synthetics/public/plugin.ts @@ -30,7 +30,6 @@ import type { TriggersAndActionsUIPublicPluginSetup, TriggersAndActionsUIPublicPluginStart, } from '@kbn/triggers-actions-ui-plugin/public'; -import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import type { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { FleetStart } from '@kbn/fleet-plugin/public'; @@ -68,6 +67,7 @@ import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import type { LicensingPluginStart } from '@kbn/licensing-plugin/public'; import type { SettingsStart } from '@kbn/core-ui-settings-browser'; import type { ContentManagementPublicStart } from '@kbn/content-management-plugin/public'; +import type { KqlPluginStart } from '@kbn/kql/public'; import { registerSyntheticsEmbeddables } from './apps/embeddables/register_embeddables'; import { kibanaService } from './utils/kibana_service'; import { PLUGIN } from '../common/constants/plugin'; @@ -96,7 +96,7 @@ export interface ClientPluginsSetup { export interface ClientPluginsStart { fleet: FleetStart; data: DataPublicPluginStart; - unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; discover: DiscoverStart; inspector: InspectorPluginStart; contentManagement: ContentManagementPublicStart; From 1f7248a5d0f875f7fe440e2667d38187fd4024a3 Mon Sep 17 00:00:00 2001 From: Stratoula Date: Mon, 22 Dec 2025 08:18:34 +0100 Subject: [PATCH 28/60] Cleanup synthetics config --- x-pack/solutions/observability/plugins/synthetics/kibana.jsonc | 2 +- x-pack/solutions/observability/plugins/synthetics/moon.yml | 2 +- x-pack/solutions/observability/plugins/synthetics/tsconfig.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/solutions/observability/plugins/synthetics/kibana.jsonc b/x-pack/solutions/observability/plugins/synthetics/kibana.jsonc index d0014ff3e9033..9c6e979d63824 100644 --- a/x-pack/solutions/observability/plugins/synthetics/kibana.jsonc +++ b/x-pack/solutions/observability/plugins/synthetics/kibana.jsonc @@ -34,7 +34,7 @@ "triggersActionsUi", "usageCollection", "uiActions", - "unifiedSearch", + "kql", "presentationUtil", "charts", "fieldsMetadata" diff --git a/x-pack/solutions/observability/plugins/synthetics/moon.yml b/x-pack/solutions/observability/plugins/synthetics/moon.yml index f6a66cb6f2479..bf004172ae452 100644 --- a/x-pack/solutions/observability/plugins/synthetics/moon.yml +++ b/x-pack/solutions/observability/plugins/synthetics/moon.yml @@ -23,7 +23,7 @@ dependsOn: - '@kbn/triggers-actions-ui-plugin' - '@kbn/observability-plugin' - '@kbn/fleet-plugin' - - '@kbn/unified-search-plugin' + - '@kbn/kql' - '@kbn/i18n' - '@kbn/core' - '@kbn/config-schema' diff --git a/x-pack/solutions/observability/plugins/synthetics/tsconfig.json b/x-pack/solutions/observability/plugins/synthetics/tsconfig.json index ec716c6ec52f4..64ca195e4d245 100644 --- a/x-pack/solutions/observability/plugins/synthetics/tsconfig.json +++ b/x-pack/solutions/observability/plugins/synthetics/tsconfig.json @@ -17,7 +17,7 @@ "@kbn/triggers-actions-ui-plugin", "@kbn/observability-plugin", "@kbn/fleet-plugin", - "@kbn/unified-search-plugin", + "@kbn/kql", "@kbn/i18n", "@kbn/core", "@kbn/config-schema", From ef1caee3de711a6cb5d2cb90fb42999226a70806 Mon Sep 17 00:00:00 2001 From: Stratoula Date: Mon, 22 Dec 2025 08:20:18 +0100 Subject: [PATCH 29/60] Fix observability config --- .../solutions/observability/plugins/observability/kibana.jsonc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/solutions/observability/plugins/observability/kibana.jsonc b/x-pack/solutions/observability/plugins/observability/kibana.jsonc index 5a63858991451..7a291d959f517 100644 --- a/x-pack/solutions/observability/plugins/observability/kibana.jsonc +++ b/x-pack/solutions/observability/plugins/observability/kibana.jsonc @@ -30,7 +30,6 @@ "triggersActionsUi", "security", "share", - "kql", "visualizations", "dashboard", "expressions", @@ -65,7 +64,7 @@ "logsShared", "kibanaReact", "kibanaUtils", - "unifiedSearch", + "kql", "stackAlerts", "spaces", "observabilityAgentBuilder" From c8278cb746bdc68d677866b2671dd329f9884cde Mon Sep 17 00:00:00 2001 From: Stratoula Date: Mon, 22 Dec 2025 10:02:37 +0100 Subject: [PATCH 30/60] Update limits --- packages/kbn-optimizer/limits.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index b98607ad92afc..df82ca7818979 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -97,7 +97,7 @@ pageLoadAssetSize: kibanaReact: 22503 kibanaUsageCollection: 1736 kibanaUtils: 54848 - kql: 1171 + kql: 50000 kubernetesSecurity: 6807 lens: 71718 licenseManagement: 8265 From 91e59445dc0716b69815363f6279c29c37d40a7e Mon Sep 17 00:00:00 2001 From: Stratoula Date: Mon, 22 Dec 2025 10:56:53 +0100 Subject: [PATCH 31/60] Fixes --- x-pack/solutions/observability/plugins/synthetics/kibana.jsonc | 1 + x-pack/solutions/observability/plugins/synthetics/moon.yml | 1 + .../apps/synthetics/contexts/synthetics_shared_context.tsx | 1 + .../solutions/observability/plugins/synthetics/public/plugin.ts | 2 ++ x-pack/solutions/observability/plugins/synthetics/tsconfig.json | 1 + 5 files changed, 6 insertions(+) diff --git a/x-pack/solutions/observability/plugins/synthetics/kibana.jsonc b/x-pack/solutions/observability/plugins/synthetics/kibana.jsonc index 9c6e979d63824..1724d2bdaf30d 100644 --- a/x-pack/solutions/observability/plugins/synthetics/kibana.jsonc +++ b/x-pack/solutions/observability/plugins/synthetics/kibana.jsonc @@ -34,6 +34,7 @@ "triggersActionsUi", "usageCollection", "uiActions", + "unifiedSearch", "kql", "presentationUtil", "charts", diff --git a/x-pack/solutions/observability/plugins/synthetics/moon.yml b/x-pack/solutions/observability/plugins/synthetics/moon.yml index bf004172ae452..6c50814d802d0 100644 --- a/x-pack/solutions/observability/plugins/synthetics/moon.yml +++ b/x-pack/solutions/observability/plugins/synthetics/moon.yml @@ -23,6 +23,7 @@ dependsOn: - '@kbn/triggers-actions-ui-plugin' - '@kbn/observability-plugin' - '@kbn/fleet-plugin' + - '@kbn/unified-search-plugin' - '@kbn/kql' - '@kbn/i18n' - '@kbn/core' diff --git a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/contexts/synthetics_shared_context.tsx b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/contexts/synthetics_shared_context.tsx index 18c74e50c6151..453906b8dadd2 100644 --- a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/contexts/synthetics_shared_context.tsx +++ b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/contexts/synthetics_shared_context.tsx @@ -52,6 +52,7 @@ export const SyntheticsSharedContext: React.FC< fleet: startPlugins.fleet, share: startPlugins.share, kql: startPlugins.kql, + unifiedSearch: startPlugins.unifiedSearch, embeddable: startPlugins.embeddable, slo: startPlugins.slo, serverless: startPlugins.serverless, diff --git a/x-pack/solutions/observability/plugins/synthetics/public/plugin.ts b/x-pack/solutions/observability/plugins/synthetics/public/plugin.ts index f2c13c67381b8..0f73e0042e821 100644 --- a/x-pack/solutions/observability/plugins/synthetics/public/plugin.ts +++ b/x-pack/solutions/observability/plugins/synthetics/public/plugin.ts @@ -68,6 +68,7 @@ import type { LicensingPluginStart } from '@kbn/licensing-plugin/public'; import type { SettingsStart } from '@kbn/core-ui-settings-browser'; import type { ContentManagementPublicStart } from '@kbn/content-management-plugin/public'; import type { KqlPluginStart } from '@kbn/kql/public'; +import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { registerSyntheticsEmbeddables } from './apps/embeddables/register_embeddables'; import { kibanaService } from './utils/kibana_service'; import { PLUGIN } from '../common/constants/plugin'; @@ -97,6 +98,7 @@ export interface ClientPluginsStart { fleet: FleetStart; data: DataPublicPluginStart; kql: KqlPluginStart; + unifiedSearch: UnifiedSearchPublicPluginStart; discover: DiscoverStart; inspector: InspectorPluginStart; contentManagement: ContentManagementPublicStart; diff --git a/x-pack/solutions/observability/plugins/synthetics/tsconfig.json b/x-pack/solutions/observability/plugins/synthetics/tsconfig.json index 64ca195e4d245..22d78920849fd 100644 --- a/x-pack/solutions/observability/plugins/synthetics/tsconfig.json +++ b/x-pack/solutions/observability/plugins/synthetics/tsconfig.json @@ -17,6 +17,7 @@ "@kbn/triggers-actions-ui-plugin", "@kbn/observability-plugin", "@kbn/fleet-plugin", + "@kbn/unified-search-plugin", "@kbn/kql", "@kbn/i18n", "@kbn/core", From 18b44abd7bc8ffd9fa42f1e98a9e4add1b7d5631 Mon Sep 17 00:00:00 2001 From: Stratoula Date: Mon, 22 Dec 2025 14:24:38 +0100 Subject: [PATCH 32/60] Fix some unit tests --- .../query_bar_top_row.test.tsx | 2 + .../public/search_bar/search_bar.test.tsx | 2 + .../public/components/search_bar.test.tsx | 34 +++++++-------- .../filters/filter_popover.test.tsx | 42 ++++++++++++++++--- .../legacy_uptime/lib/helper/rtl_helpers.tsx | 2 + .../decorators/kibana_react_decorator.tsx | 2 + .../test/fixtures/get_mock_dependencies.ts | 2 + .../public/test/mock_server/mock_server.ts | 4 +- .../common/lib/kibana/kibana_react.mock.ts | 3 ++ 9 files changed, 69 insertions(+), 24 deletions(-) diff --git a/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.test.tsx b/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.test.tsx index 21fcf3f2d341c..546b9681484bf 100644 --- a/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.test.tsx +++ b/src/platform/plugins/shared/unified_search/public/query_string_input/query_bar_top_row.test.tsx @@ -20,6 +20,7 @@ import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { I18nProvider } from '@kbn/i18n-react'; import { stubIndexPattern } from '@kbn/data-plugin/public/stubs'; +import { kqlPluginMock } from '@kbn/kql/public/mocks'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; import { unifiedSearchPluginMock } from '../mocks'; import { EuiThemeProvider } from '@elastic/eui'; @@ -109,6 +110,7 @@ function wrapQueryBarTopRowInContext( const services = { ...startMock, unifiedSearch: unifiedSearchPluginMock.createStartContract(), + kql: kqlPluginMock.createStartContract(), data: dataPluginMock.createStartContract(), appName: 'discover', storage: createMockStorage(), diff --git a/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.test.tsx b/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.test.tsx index 9be1a5f387cff..0c1281675217d 100644 --- a/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.test.tsx +++ b/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.test.tsx @@ -23,6 +23,7 @@ import { searchServiceMock } from '@kbn/data-plugin/public/search/mocks'; import { createMockStorage, createMockTimeHistory } from './mocks'; import { SearchSessionState } from '@kbn/data-plugin/public'; import { getSessionServiceMock } from '@kbn/data-plugin/public/search/session/mocks'; +import { kqlPluginMock } from '@kbn/kql/public/mocks'; const startMock = coreMock.createStart(); @@ -74,6 +75,7 @@ function wrapSearchBarInContext( ...startMock.chrome, getActiveSolutionNavId$: jest.fn().mockReturnValue(new BehaviorSubject('oblt')), }, + kql: kqlPluginMock.createStartContract(), uiSettings: startMock.uiSettings, settings: startMock.settings, notifications: startMock.notifications, diff --git a/x-pack/platform/plugins/private/graph/public/components/search_bar.test.tsx b/x-pack/platform/plugins/private/graph/public/components/search_bar.test.tsx index 9a4857f373eff..aa0e5b798dd20 100644 --- a/x-pack/platform/plugins/private/graph/public/components/search_bar.test.tsx +++ b/x-pack/platform/plugins/private/graph/public/components/search_bar.test.tsx @@ -59,24 +59,22 @@ function getServiceMocks() { const kqlMock = kqlPluginMock.createStartContract(); return { overlays: {} as OverlayStart, - unifiedSearch: { - ui: { - QueryStringInput: createQueryStringInput({ - docLinks, - uiSettings, - storage: { - get: () => {}, - set: () => {}, - remove: () => {}, - clear: () => {}, - } as IStorageWrapper, - data: dataPluginMock.createStartContract(), - autocomplete: kqlMock.autocomplete, - notifications: {} as NotificationsStart, - http: {} as HttpStart, - dataViews: dataViewPluginMocks.createStartContract(), - }), - }, + kql: { + QueryStringInput: createQueryStringInput({ + docLinks, + uiSettings, + storage: { + get: () => {}, + set: () => {}, + remove: () => {}, + clear: () => {}, + } as IStorageWrapper, + data: dataPluginMock.createStartContract(), + autocomplete: kqlMock.autocomplete, + notifications: {} as NotificationsStart, + http: {} as HttpStart, + dataViews: dataViewPluginMocks.createStartContract(), + }), }, }; } diff --git a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/filters/filter_popover.test.tsx b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/filters/filter_popover.test.tsx index 57bb4309362b5..05832b70686c7 100644 --- a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/filters/filter_popover.test.tsx +++ b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/filters/filter_popover.test.tsx @@ -9,6 +9,11 @@ import React from 'react'; import { shallow, mount } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { EuiPopover, EuiLink } from '@elastic/eui'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { kqlPluginMock } from '@kbn/kql/public/mocks'; +import { coreMock } from '@kbn/core/public/mocks'; +import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { createMockedIndexPattern } from '../../../mocks'; import { FilterPopover } from './filter_popover'; import { LabelInput } from '../shared_components'; @@ -35,6 +40,29 @@ describe('filter popover', () => { let defaultProps: Parameters[0]; let mockOnClick: jest.Mock; + const createMockStorage = () => ({ + get: jest.fn(), + set: jest.fn(), + remove: jest.fn(), + clear: jest.fn(), + }); + + const coreMockStart = coreMock.createStart(); + const mockServices = { + http: coreMockStart.http, + storage: createMockStorage(), + dataViews: dataViewPluginMocks.createStartContract(), + data: dataPluginMock.createStartContract(), + uiSettings: coreMockStart.uiSettings, + notifications: coreMockStart.notifications, + kql: kqlPluginMock.createStartContract(), + docLinks: coreMockStart.docLinks, + }; + + const wrapInContext = (component: React.ReactElement) => ( + {component} + ); + beforeEach(() => { mockOnClick = jest.fn(); @@ -54,18 +82,22 @@ describe('filter popover', () => { describe('interactions', () => { it('should open/close according to isOpen', () => { - const instance = mount(); + const instance = mount( + wrapInContext() + ); expect(instance.find(EuiPopover).prop('isOpen')).toEqual(true); - instance.setProps({ ...defaultProps, isOpen: false }); + instance.setProps({ + children: wrapInContext(), + }); instance.update(); expect(instance.find(EuiPopover).prop('isOpen')).toEqual(false); }); it('should report click event', () => { - const instance = mount(); + const instance = mount(wrapInContext()); expect(mockOnClick).not.toHaveBeenCalled(); @@ -76,7 +108,7 @@ describe('filter popover', () => { it('should trigger close', () => { const props = { ...defaultProps, triggerClose: jest.fn() }; - const instance = mount(); + const instance = mount(wrapInContext()); expect(instance.find(EuiPopover).prop('isOpen')).toEqual(true); // Trigger from EuiPopover @@ -94,7 +126,7 @@ describe('filter popover', () => { }); it('passes correct props to QueryStringInput', () => { - const instance = mount(); + const instance = mount(wrapInContext()); instance.update(); expect(instance.find(QueryStringInput).props()).toEqual( expect.objectContaining({ diff --git a/x-pack/solutions/observability/plugins/uptime/public/legacy_uptime/lib/helper/rtl_helpers.tsx b/x-pack/solutions/observability/plugins/uptime/public/legacy_uptime/lib/helper/rtl_helpers.tsx index e678fbfa3502a..06ba94b29d812 100644 --- a/x-pack/solutions/observability/plugins/uptime/public/legacy_uptime/lib/helper/rtl_helpers.tsx +++ b/x-pack/solutions/observability/plugins/uptime/public/legacy_uptime/lib/helper/rtl_helpers.tsx @@ -29,6 +29,7 @@ import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { triggersActionsUiMock } from '@kbn/triggers-actions-ui-plugin/public/mocks'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; +import { kqlPluginMock } from '@kbn/kql/public/mocks'; import type { Store } from 'redux'; import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; @@ -170,6 +171,7 @@ export const mockCore: () => Partial = () => { ), }, unifiedSearch: unifiedSearchPluginMock.createStartContract(), + kql: kqlPluginMock.createStartContract(), charts: chartPluginMock.createStartContract(), }; diff --git a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/.storybook/decorators/kibana_react_decorator.tsx b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/.storybook/decorators/kibana_react_decorator.tsx index 636011ac81ad6..00c4271aaeae1 100644 --- a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/.storybook/decorators/kibana_react_decorator.tsx +++ b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/.storybook/decorators/kibana_react_decorator.tsx @@ -10,6 +10,7 @@ import { action } from '@storybook/addon-actions'; import { createKibanaReactContext, type KibanaServices } from '@kbn/kibana-react-plugin/public'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; import { applicationServiceMock } from '@kbn/core-application-browser-mocks'; +import { kqlPluginMock } from '@kbn/kql/public/mocks'; import { of } from 'rxjs'; import { WEB_STORAGE_CLEAR_ACTION, @@ -179,6 +180,7 @@ const services: Partial = { editDataView: action(EDIT_DATA_VIEW_ACTION), }, }, + kql: kqlPluginMock.createStartContract(), }; export const KibanaReactStorybookDecorator = (Story: ComponentType) => { diff --git a/x-pack/solutions/security/plugins/cloud_security_posture/public/test/fixtures/get_mock_dependencies.ts b/x-pack/solutions/security/plugins/cloud_security_posture/public/test/fixtures/get_mock_dependencies.ts index 002f12d12087e..61baef5a04c37 100644 --- a/x-pack/solutions/security/plugins/cloud_security_posture/public/test/fixtures/get_mock_dependencies.ts +++ b/x-pack/solutions/security/plugins/cloud_security_posture/public/test/fixtures/get_mock_dependencies.ts @@ -14,10 +14,12 @@ import { licensingMock } from '@kbn/licensing-plugin/public/mocks'; import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks'; import { sessionStorageMock } from '@kbn/core-http-server-mocks'; import { sharePluginMock } from '@kbn/share-plugin/public/mocks'; +import { kqlPluginMock } from '@kbn/kql/public/mocks'; export const getMockDependencies = () => ({ data: dataPluginMock.createStartContract(), unifiedSearch: unifiedSearchPluginMock.createStartContract(), + kql: kqlPluginMock.createStartContract(), charts: chartPluginMock.createStartContract(), discover: discoverPluginMock.createStartContract(), fleet: fleetMock.createStartMock(), diff --git a/x-pack/solutions/security/plugins/cloud_security_posture/public/test/mock_server/mock_server.ts b/x-pack/solutions/security/plugins/cloud_security_posture/public/test/mock_server/mock_server.ts index f7e1b39266931..cc918b089d61e 100644 --- a/x-pack/solutions/security/plugins/cloud_security_posture/public/test/mock_server/mock_server.ts +++ b/x-pack/solutions/security/plugins/cloud_security_posture/public/test/mock_server/mock_server.ts @@ -163,8 +163,10 @@ export const getMockServerDependencies = () => { }, }, dataViewFieldEditor: dataViewFieldEditorMock.createStartContract(), + kql: { + ...getMockDependencies().kql, + }, unifiedSearch: { - ...getMockDependencies().unifiedSearch, ui: { ...getMockDependencies().unifiedSearch.ui, SearchBar, diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts b/x-pack/solutions/security/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts index 35d46a40ce197..e7eea12c24819 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts @@ -8,6 +8,7 @@ import React from 'react'; import type { RecursivePartial } from '@elastic/eui/src/components/common'; import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; +import { kqlPluginMock } from '@kbn/kql/public/mocks'; import { navigationPluginMock } from '@kbn/navigation-plugin/public/mocks'; import { discoverPluginMock } from '@kbn/discover-plugin/public/mocks'; import { coreMock, themeServiceMock } from '@kbn/core/public/mocks'; @@ -118,6 +119,7 @@ export const createStartServicesMock = ( const urlService = new MockUrlService(); const locator = urlService.locators.create(new MlLocatorDefinition()); const fleet = fleetMock.createStartMock(); + const kql = kqlPluginMock.createStartContract(); const unifiedSearch = unifiedSearchPluginMock.createStartContract(); const navigation = navigationPluginMock.createStartContract(); const discover = discoverPluginMock.createStartContract(); @@ -155,6 +157,7 @@ export const createStartServicesMock = ( configSettings: getDefaultConfigSettings(), apm, cases, + kql, unifiedSearch, navigation, discover, From 69bbf32456fd443061e4bcb886bc290db71846a3 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 22 Dec 2025 13:34:26 +0000 Subject: [PATCH 33/60] Changes from node scripts/lint_ts_projects --fix --- .../packages/kbn-cloud-security-posture/graph/tsconfig.json | 3 ++- .../security/plugins/cloud_security_posture/tsconfig.json | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/tsconfig.json b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/tsconfig.json index aa34763d18e89..cf0f71e517d4a 100644 --- a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/tsconfig.json +++ b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/tsconfig.json @@ -26,6 +26,7 @@ "@kbn/expandable-flyout", "@kbn/react-query", "@kbn/fleet-plugin", - "@kbn/core-http-browser" + "@kbn/core-http-browser", + "@kbn/kql" ] } diff --git a/x-pack/solutions/security/plugins/cloud_security_posture/tsconfig.json b/x-pack/solutions/security/plugins/cloud_security_posture/tsconfig.json index cd7588b18ce31..041ae4b9968a8 100755 --- a/x-pack/solutions/security/plugins/cloud_security_posture/tsconfig.json +++ b/x-pack/solutions/security/plugins/cloud_security_posture/tsconfig.json @@ -73,6 +73,7 @@ "@kbn/shared-ux-card-no-data", "@kbn/react-kibana-context-theme", "@kbn/react-query", + "@kbn/kql", ], "exclude": ["target/**/*"] } From fe96fcb4d7ef547c7f42a1b0025f3558f3078d32 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 22 Dec 2025 13:45:06 +0000 Subject: [PATCH 34/60] Changes from node scripts/regenerate_moon_projects.js --update --- .../security/packages/kbn-cloud-security-posture/graph/moon.yml | 1 + .../solutions/security/plugins/cloud_security_posture/moon.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/moon.yml b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/moon.yml index 942966727af17..bec2e2070b5c1 100644 --- a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/moon.yml +++ b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/moon.yml @@ -39,6 +39,7 @@ dependsOn: - '@kbn/react-query' - '@kbn/fleet-plugin' - '@kbn/core-http-browser' + - '@kbn/kql' tags: - shared-browser - package diff --git a/x-pack/solutions/security/plugins/cloud_security_posture/moon.yml b/x-pack/solutions/security/plugins/cloud_security_posture/moon.yml index 6a88ad5c38288..4bd3697977bb0 100644 --- a/x-pack/solutions/security/plugins/cloud_security_posture/moon.yml +++ b/x-pack/solutions/security/plugins/cloud_security_posture/moon.yml @@ -77,6 +77,7 @@ dependsOn: - '@kbn/shared-ux-card-no-data' - '@kbn/react-kibana-context-theme' - '@kbn/react-query' + - '@kbn/kql' tags: - plugin - prod From 79495385bb98de8a078859bf62f5d331cf91b4f5 Mon Sep 17 00:00:00 2001 From: Stratoula Date: Tue, 23 Dec 2025 09:13:06 +0100 Subject: [PATCH 35/60] More fixes --- packages/kbn-optimizer/limits.yml | 16 ++++++------ .../event_annotation_listing/kibana.jsonc | 1 - .../plugins/shared/controls/kibana.jsonc | 1 - src/platform/plugins/shared/kql/README.md | 2 ++ .../get_query_string_input.tsx | 4 +-- .../components/query_string_input/index.tsx | 25 +++++++++++++++++++ .../plugins/shared/kql/public/index.ts | 4 +-- .../shared/vis_types/timeseries/kibana.jsonc | 1 - .../shared/vis_types/timeseries/moon.yml | 1 - .../application/components/vis_editor.tsx | 4 +-- .../contexts/query_input_bar_context.ts | 4 +-- .../vis_types/timeseries/public/plugin.ts | 6 ----- .../vis_types/timeseries/public/services.ts | 3 --- .../shared/vis_types/timeseries/tsconfig.json | 1 - .../plugins/private/graph/kibana.jsonc | 2 +- .../platform/plugins/private/graph/moon.yml | 1 - .../private/graph/public/application.tsx | 4 +-- .../graph/public/apps/workspace_route.tsx | 6 ++--- .../graph/public/components/search_bar.tsx | 6 +---- .../plugins/private/graph/public/plugin.ts | 6 ++--- .../plugins/private/graph/tsconfig.json | 1 - .../plugins/private/monitoring/kibana.jsonc | 1 - .../plugins/private/monitoring/moon.yml | 1 - .../public/alerts/configuration.tsx | 4 +-- .../plugins/private/monitoring/tsconfig.json | 1 - .../plugins/private/transform/kibana.jsonc | 1 + .../step_define/step_define_form.test.tsx | 4 +-- .../plugins/shared/alerting/kibana.jsonc | 1 - .../platform/plugins/shared/alerting/moon.yml | 1 - .../plugins/shared/alerting/public/plugin.ts | 4 +-- .../plugins/shared/alerting/tsconfig.json | 2 -- .../shared/fleet/.storybook/context/stubs.tsx | 1 + .../platform/plugins/shared/maps/kibana.jsonc | 1 + .../platform/plugins/shared/ml/kibana.jsonc | 1 + .../ml/public/application/management/index.ts | 1 + .../plugins/shared/stack_alerts/kibana.jsonc | 1 + .../observability/plugins/apm/kibana.jsonc | 1 + .../observability/plugins/uptime/kibana.jsonc | 1 + .../decorators/kibana_react_decorator.tsx | 4 +-- .../public/src/types.ts | 2 ++ 40 files changed, 70 insertions(+), 62 deletions(-) create mode 100644 src/platform/plugins/shared/kql/public/components/query_string_input/index.tsx diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index d75cbf8d422ab..72defd5e9a386 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -4,7 +4,7 @@ pageLoadAssetSize: agentBuilderPlatform: 64202 aiAssistantManagementSelection: 13590 aiops: 15359 - alerting: 26932 + alerting: 22371 apm: 38573 apmSourcesAccess: 2278 automaticImport: 12162 @@ -97,7 +97,7 @@ pageLoadAssetSize: kibanaReact: 22503 kibanaUsageCollection: 1736 kibanaUtils: 54848 - kql: 50000 + kql: 36676 kubernetesSecurity: 6807 lens: 71718 licenseManagement: 8265 @@ -107,7 +107,7 @@ pageLoadAssetSize: logsDataAccess: 7900 logsShared: 101273 logstash: 15882 - maintenanceWindows: 8803 + maintenanceWindows: 8783 management: 12951 maps: 45917 mapsEms: 6734 @@ -119,7 +119,7 @@ pageLoadAssetSize: newsfeed: 12371 noDataPage: 1749 observability: 107797 - observabilityAgentBuilder: 7079 + observabilityAgentBuilder: 7077 observabilityAIAssistant: 54556 observabilityAIAssistantApp: 18012 observabilityAiAssistantManagement: 7126 @@ -165,8 +165,8 @@ pageLoadAssetSize: securitySolutionServerless: 52082 serverless: 7451 serverlessObservability: 19472 - serverlessSearch: 26340 - serverlessWorkplaceAI: 6671 + serverlessSearch: 26314 + serverlessWorkplaceAI: 6645 sessionView: 47912 share: 58677 slo: 36645 @@ -185,7 +185,7 @@ pageLoadAssetSize: uiActions: 24670 uiActionsEnhanced: 20189 unifiedDocViewer: 14513 - unifiedSearch: 24000 + unifiedSearch: 19103 upgradeAssistant: 6898 uptime: 48171 urlDrilldown: 18939 @@ -207,6 +207,6 @@ pageLoadAssetSize: visTypeXy: 32342 visualizations: 38375 watcher: 10485 - workflowsExtensions: 73502 + workflowsExtensions: 68415 workflowsManagement: 8497 workplaceAIApp: 6135 diff --git a/src/platform/plugins/private/event_annotation_listing/kibana.jsonc b/src/platform/plugins/private/event_annotation_listing/kibana.jsonc index 0ec911379699c..04e6672742b97 100644 --- a/src/platform/plugins/private/event_annotation_listing/kibana.jsonc +++ b/src/platform/plugins/private/event_annotation_listing/kibana.jsonc @@ -18,7 +18,6 @@ "presentationUtil", "visualizations", "dataViews", - "unifiedSearch", "kql", "kibanaUtils", "contentManagement" diff --git a/src/platform/plugins/shared/controls/kibana.jsonc b/src/platform/plugins/shared/controls/kibana.jsonc index 449032c654294..bab2782634d20 100644 --- a/src/platform/plugins/shared/controls/kibana.jsonc +++ b/src/platform/plugins/shared/controls/kibana.jsonc @@ -16,7 +16,6 @@ "embeddable", "dataViews", "data", - "unifiedSearch", "kql", "uiActions", ], diff --git a/src/platform/plugins/shared/kql/README.md b/src/platform/plugins/shared/kql/README.md index d72e1f6c9be14..07be32d974740 100644 --- a/src/platform/plugins/shared/kql/README.md +++ b/src/platform/plugins/shared/kql/README.md @@ -1 +1,3 @@ # @kbn/kql + +This plugin contains the kql autocomplete service and the UI KQL searchbar. \ No newline at end of file diff --git a/src/platform/plugins/shared/kql/public/components/query_string_input/get_query_string_input.tsx b/src/platform/plugins/shared/kql/public/components/query_string_input/get_query_string_input.tsx index 14e2d596c9d9a..de46f8b6441ae 100644 --- a/src/platform/plugins/shared/kql/public/components/query_string_input/get_query_string_input.tsx +++ b/src/platform/plugins/shared/kql/public/components/query_string_input/get_query_string_input.tsx @@ -8,8 +8,8 @@ */ import React from 'react'; -import type { QueryStringInputProps } from './query_string_input'; -import { QueryStringInput } from './query_string_input'; +import type { QueryStringInputProps } from '.'; +import { QueryStringInput } from '.'; export function createQueryStringInput(deps: QueryStringInputProps['deps']) { return (props: Omit) => { diff --git a/src/platform/plugins/shared/kql/public/components/query_string_input/index.tsx b/src/platform/plugins/shared/kql/public/components/query_string_input/index.tsx new file mode 100644 index 0000000000000..550250a601663 --- /dev/null +++ b/src/platform/plugins/shared/kql/public/components/query_string_input/index.tsx @@ -0,0 +1,25 @@ +/* + * 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 React from 'react'; +import type { QueryStringInputProps } from './query_string_input'; + +const Fallback = () =>
; + +const LazyQueryStringInputUI = React.lazy(async () => { + const { QueryStringInput } = await import('./query_string_input'); + return { default: QueryStringInput }; +}); + +export const QueryStringInput = (props: QueryStringInputProps) => ( + }> + + +); + +export type { QueryStringInputProps }; diff --git a/src/platform/plugins/shared/kql/public/index.ts b/src/platform/plugins/shared/kql/public/index.ts index 57cca559df970..a6642067a68f7 100644 --- a/src/platform/plugins/shared/kql/public/index.ts +++ b/src/platform/plugins/shared/kql/public/index.ts @@ -10,8 +10,8 @@ import type { PluginInitializerContext } from '@kbn/core/public'; import { KqlPlugin } from './plugin'; export type { KqlPluginStart, KqlPluginSetup } from './plugin'; -export type { QueryStringInputProps } from './components/query_string_input/query_string_input'; -export { QueryStringInput } from './components/query_string_input/query_string_input'; +export type { QueryStringInputProps } from './components/query_string_input'; +export { QueryStringInput } from './components/query_string_input'; export { QueryLanguageSwitcher } from './components/query_string_input/language_switcher'; export { FilterButtonGroup } from './components/query_string_input/filter_button_group'; export { fromUser } from './components/query_string_input/from_user'; diff --git a/src/platform/plugins/shared/vis_types/timeseries/kibana.jsonc b/src/platform/plugins/shared/vis_types/timeseries/kibana.jsonc index d14c110dc4a55..6b4ee9f83c950 100644 --- a/src/platform/plugins/shared/vis_types/timeseries/kibana.jsonc +++ b/src/platform/plugins/shared/vis_types/timeseries/kibana.jsonc @@ -21,7 +21,6 @@ "dataViews", "fieldFormats", "usageCollection", - "unifiedSearch", "kql", "expressionXY", "uiActions", diff --git a/src/platform/plugins/shared/vis_types/timeseries/moon.yml b/src/platform/plugins/shared/vis_types/timeseries/moon.yml index 6c73b42e33c7e..cec2f3f4db704 100644 --- a/src/platform/plugins/shared/vis_types/timeseries/moon.yml +++ b/src/platform/plugins/shared/vis_types/timeseries/moon.yml @@ -26,7 +26,6 @@ dependsOn: - '@kbn/visualizations-plugin' - '@kbn/kibana-utils-plugin' - '@kbn/kibana-react-plugin' - - '@kbn/unified-search-plugin' - '@kbn/i18n' - '@kbn/field-formats-plugin' - '@kbn/datemath' diff --git a/src/platform/plugins/shared/vis_types/timeseries/public/application/components/vis_editor.tsx b/src/platform/plugins/shared/vis_types/timeseries/public/application/components/vis_editor.tsx index 2546133c15f27..cc2ea17c29543 100644 --- a/src/platform/plugins/shared/vis_types/timeseries/public/application/components/vis_editor.tsx +++ b/src/platform/plugins/shared/vis_types/timeseries/public/application/components/vis_editor.tsx @@ -30,7 +30,7 @@ import { TIME_RANGE_DATA_MODES, TIME_RANGE_MODE_KEY } from '../../../common/enum import { VisPicker } from './vis_picker'; import type { VisFields } from '../lib/fetch_fields'; import { fetchFields } from '../lib/fetch_fields'; -import { getDataStart, getCoreStart, getUnifiedSearchStart } from '../../services'; +import { getDataStart, getCoreStart, getKqlStart } from '../../services'; import type { TimeseriesVisParams } from '../../types'; import { UseIndexPatternModeCallout } from './use_index_patter_mode_callout'; @@ -190,7 +190,7 @@ export class VisEditor extends Component { http: HttpSetup; timefilter: TimefilterContract; appName: string; - unifiedSearch: UnifiedSearchPublicPluginStart; kql: KqlPluginStart; core: CoreStart; notifications: CoreStart['notifications']; @@ -114,7 +110,6 @@ export class MetricsPlugin implements Plugin { dataViews, usageCollection, fieldFormats, - unifiedSearch, kql, uiActions, embeddable, @@ -124,7 +119,6 @@ export class MetricsPlugin implements Plugin { setI18n(core.i18n); setFieldFormats(fieldFormats); setDataStart(data); - setUnifiedSearchStart(unifiedSearch); setKqlStart(kql); setDataViewsStart(dataViews); setCoreStart(core); diff --git a/src/platform/plugins/shared/vis_types/timeseries/public/services.ts b/src/platform/plugins/shared/vis_types/timeseries/public/services.ts index 9fb004f342244..07d13133c40b0 100644 --- a/src/platform/plugins/shared/vis_types/timeseries/public/services.ts +++ b/src/platform/plugins/shared/vis_types/timeseries/public/services.ts @@ -14,7 +14,6 @@ import type { ChartsPluginStart } from '@kbn/charts-plugin/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; -import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import type { KqlPluginStart } from '@kbn/kql/public'; export const [getUISettings, setUISettings] = createGetterSetter('UISettings'); @@ -25,8 +24,6 @@ export const [getFieldFormats, setFieldFormats] = export const [getCoreStart, setCoreStart] = createGetterSetter('CoreStart'); export const [getDataStart, setDataStart] = createGetterSetter('DataStart'); -export const [getUnifiedSearchStart, setUnifiedSearchStart] = - createGetterSetter('unifiedSearchStart'); export const [getKqlStart, setKqlStart] = createGetterSetter('kqlStart'); diff --git a/src/platform/plugins/shared/vis_types/timeseries/tsconfig.json b/src/platform/plugins/shared/vis_types/timeseries/tsconfig.json index 7f861d0958c57..cb242c4e30811 100644 --- a/src/platform/plugins/shared/vis_types/timeseries/tsconfig.json +++ b/src/platform/plugins/shared/vis_types/timeseries/tsconfig.json @@ -20,7 +20,6 @@ "@kbn/visualizations-plugin", "@kbn/kibana-utils-plugin", "@kbn/kibana-react-plugin", - "@kbn/unified-search-plugin", "@kbn/i18n", "@kbn/field-formats-plugin", "@kbn/datemath", diff --git a/x-pack/platform/plugins/private/graph/kibana.jsonc b/x-pack/platform/plugins/private/graph/kibana.jsonc index 52546c18c68ea..0b880cbe0040c 100644 --- a/x-pack/platform/plugins/private/graph/kibana.jsonc +++ b/x-pack/platform/plugins/private/graph/kibana.jsonc @@ -19,7 +19,7 @@ "data", "dataViews", "navigation", - "unifiedSearch", + "kql", "inspector", "savedObjectsManagement", "savedObjectsFinder", diff --git a/x-pack/platform/plugins/private/graph/moon.yml b/x-pack/platform/plugins/private/graph/moon.yml index e7f9c82c47297..8949265e84c17 100644 --- a/x-pack/platform/plugins/private/graph/moon.yml +++ b/x-pack/platform/plugins/private/graph/moon.yml @@ -28,7 +28,6 @@ dependsOn: - '@kbn/kibana-utils-plugin' - '@kbn/kibana-react-plugin' - '@kbn/spaces-plugin' - - '@kbn/unified-search-plugin' - '@kbn/i18n' - '@kbn/config-schema' - '@kbn/i18n-react' diff --git a/x-pack/platform/plugins/private/graph/public/application.tsx b/x-pack/platform/plugins/private/graph/public/application.tsx index 170cc9f5787a4..3f71809d3721b 100644 --- a/x-pack/platform/plugins/private/graph/public/application.tsx +++ b/x-pack/platform/plugins/private/graph/public/application.tsx @@ -22,7 +22,6 @@ import ReactDOM from 'react-dom'; import React from 'react'; import type { DataPlugin } from '@kbn/data-plugin/public'; import type { DataViewsContract } from '@kbn/data-views-plugin/public'; -import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import type { LicensingPluginStart } from '@kbn/licensing-plugin/public'; import type { NavigationPublicPluginStart as NavigationStart } from '@kbn/navigation-plugin/public'; import type { Storage } from '@kbn/kibana-utils-plugin/public'; @@ -37,6 +36,7 @@ import type { ContentManagementPublicStart, } from '@kbn/content-management-plugin/public'; +import type { KqlPluginStart } from '@kbn/kql/public'; import type { GraphSavePolicy } from './types'; import { graphRouter } from './router'; import { checkLicense } from '../common/check_license'; @@ -60,7 +60,7 @@ export interface GraphDependencies { toastNotifications: ToastsStart; dataViews: DataViewsContract; data: ReturnType; - unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; contentClient: ContentClient; addBasePath: (url: string) => string; getBasePath: () => string; diff --git a/x-pack/platform/plugins/private/graph/public/apps/workspace_route.tsx b/x-pack/platform/plugins/private/graph/public/apps/workspace_route.tsx index 3559161a2f281..2de032dedd1fd 100644 --- a/x-pack/platform/plugins/private/graph/public/apps/workspace_route.tsx +++ b/x-pack/platform/plugins/private/graph/public/apps/workspace_route.tsx @@ -34,7 +34,7 @@ export const WorkspaceRoute = ({ capabilities, storage, data, - unifiedSearch, + kql, getBasePath, addBasePath, setHeaderActionMenu, @@ -69,12 +69,12 @@ export const WorkspaceRoute = ({ appName: 'graph', storage, data, - unifiedSearch, + kql, savedObjectsManagement, contentManagement, ...coreStart, }), - [coreStart, data, storage, unifiedSearch, savedObjectsManagement, contentManagement] + [coreStart, data, storage, kql, savedObjectsManagement, contentManagement] ); const { loading, requestAdapter, callNodeProxy, callSearchNodeProxy, handleSearchQueryError } = diff --git a/x-pack/platform/plugins/private/graph/public/components/search_bar.tsx b/x-pack/platform/plugins/private/graph/public/components/search_bar.tsx index ca85c8be58772..bde365aa39d6b 100644 --- a/x-pack/platform/plugins/private/graph/public/components/search_bar.tsx +++ b/x-pack/platform/plugins/private/graph/public/components/search_bar.tsx @@ -21,10 +21,7 @@ import { toElasticsearchQuery, fromKueryExpression } from '@kbn/es-query'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { TooltipWrapper } from '@kbn/visualization-utils'; import type { DataView } from '@kbn/data-views-plugin/public'; -import type { - IUnifiedSearchPluginServices, - UnifiedSearchPublicPluginStart, -} from '@kbn/unified-search-plugin/public/types'; +import type { IUnifiedSearchPluginServices } from '@kbn/unified-search-plugin/public/types'; import type { ContentManagementPublicStart } from '@kbn/content-management-plugin/public'; import { css } from '@emotion/react'; @@ -111,7 +108,6 @@ export function SearchBarComponent(props: SearchBarStateProps & SearchBarProps) const { services, overlays } = useKibana< IUnifiedSearchPluginServices & { contentManagement: ContentManagementPublicStart; - unifiedSearch: UnifiedSearchPublicPluginStart; kql: KqlPluginStart; } >(); diff --git a/x-pack/platform/plugins/private/graph/public/plugin.ts b/x-pack/platform/plugins/private/graph/public/plugin.ts index fe22925919888..f9bb4771bef8c 100644 --- a/x-pack/platform/plugins/private/graph/public/plugin.ts +++ b/x-pack/platform/plugins/private/graph/public/plugin.ts @@ -28,8 +28,8 @@ import type { } from '@kbn/content-management-plugin/public'; import type { LicensingPluginStart } from '@kbn/licensing-plugin/public'; import type { HomePublicPluginSetup, HomePublicPluginStart } from '@kbn/home-plugin/public'; -import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import type { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; +import type { KqlPluginStart } from '@kbn/kql/public'; import { checkLicense } from '../common/check_license'; import type { ConfigSchema } from '../server/config'; import { CONTENT_ID, LATEST_VERSION } from '../common/content_management'; @@ -43,7 +43,7 @@ export interface GraphPluginStartDependencies { navigation: NavigationStart; licensing: LicensingPluginStart; data: DataPublicPluginStart; - unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; inspector: InspectorPublicPluginStart; home?: HomePublicPluginStart; spaces?: SpacesApi; @@ -115,7 +115,7 @@ export class GraphPlugin coreStart, navigation: pluginsStart.navigation, data: pluginsStart.data, - unifiedSearch: pluginsStart.unifiedSearch, + kql: pluginsStart.kql, contentClient: pluginsStart.contentManagement.client, addBasePath: core.http.basePath.prepend, getBasePath: core.http.basePath.get, diff --git a/x-pack/platform/plugins/private/graph/tsconfig.json b/x-pack/platform/plugins/private/graph/tsconfig.json index 6119a3e8c5e9e..b2b2d55099048 100644 --- a/x-pack/platform/plugins/private/graph/tsconfig.json +++ b/x-pack/platform/plugins/private/graph/tsconfig.json @@ -23,7 +23,6 @@ "@kbn/kibana-utils-plugin", "@kbn/kibana-react-plugin", "@kbn/spaces-plugin", - "@kbn/unified-search-plugin", "@kbn/i18n", "@kbn/config-schema", "@kbn/i18n-react", diff --git a/x-pack/platform/plugins/private/monitoring/kibana.jsonc b/x-pack/platform/plugins/private/monitoring/kibana.jsonc index 9547693cdd7ac..9cec1e072ef61 100644 --- a/x-pack/platform/plugins/private/monitoring/kibana.jsonc +++ b/x-pack/platform/plugins/private/monitoring/kibana.jsonc @@ -15,7 +15,6 @@ "data", "navigation", "dataViews", - "unifiedSearch", "kql", "share", "fieldsMetadata" diff --git a/x-pack/platform/plugins/private/monitoring/moon.yml b/x-pack/platform/plugins/private/monitoring/moon.yml index 22aad0b175701..cb488548d5ef7 100644 --- a/x-pack/platform/plugins/private/monitoring/moon.yml +++ b/x-pack/platform/plugins/private/monitoring/moon.yml @@ -40,7 +40,6 @@ dependsOn: - '@kbn/test-jest-helpers' - '@kbn/safer-lodash-set' - '@kbn/core-http-browser' - - '@kbn/unified-search-plugin' - '@kbn/config-schema' - '@kbn/rule-registry-plugin' - '@kbn/telemetry-collection-manager-plugin' diff --git a/x-pack/platform/plugins/private/monitoring/public/alerts/configuration.tsx b/x-pack/platform/plugins/private/monitoring/public/alerts/configuration.tsx index 9e4b83e906ab8..ce4d2b09ed2a5 100644 --- a/x-pack/platform/plugins/private/monitoring/public/alerts/configuration.tsx +++ b/x-pack/platform/plugins/private/monitoring/public/alerts/configuration.tsx @@ -16,7 +16,7 @@ import React, { Fragment, useCallback, useMemo } from 'react'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { ChartsPluginStart } from '@kbn/charts-plugin/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; -import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import type { KqlPluginStart } from '@kbn/kql/public'; import type { CoreStart } from '@kbn/core/public'; import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public'; import { hideBottomBar, showBottomBar } from '../lib/setup_mode'; @@ -32,7 +32,7 @@ type KibanaDeps = { dataViews: DataViewsPublicPluginStart; charts?: ChartsPluginStart; data: DataPublicPluginStart; - unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; fieldsMetadata: FieldsMetadataPublicStart; } & CoreStart; diff --git a/x-pack/platform/plugins/private/monitoring/tsconfig.json b/x-pack/platform/plugins/private/monitoring/tsconfig.json index b09dfa6474df5..7675217830257 100644 --- a/x-pack/platform/plugins/private/monitoring/tsconfig.json +++ b/x-pack/platform/plugins/private/monitoring/tsconfig.json @@ -33,7 +33,6 @@ "@kbn/test-jest-helpers", "@kbn/safer-lodash-set", "@kbn/core-http-browser", - "@kbn/unified-search-plugin", "@kbn/config-schema", "@kbn/rule-registry-plugin", "@kbn/telemetry-collection-manager-plugin", diff --git a/x-pack/platform/plugins/private/transform/kibana.jsonc b/x-pack/platform/plugins/private/transform/kibana.jsonc index 9112e80f043e6..2218d6a15d200 100644 --- a/x-pack/platform/plugins/private/transform/kibana.jsonc +++ b/x-pack/platform/plugins/private/transform/kibana.jsonc @@ -22,6 +22,7 @@ "triggersActionsUi", "fieldFormats", "unifiedSearch", + "kql", "charts", "savedObjectsFinder", "savedObjectsManagement", diff --git a/x-pack/platform/plugins/private/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx b/x-pack/platform/plugins/private/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx index 5a43625af7008..2d36482df2ba2 100644 --- a/x-pack/platform/plugins/private/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx +++ b/x-pack/platform/plugins/private/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx @@ -25,7 +25,7 @@ import type { SearchItems } from '../../../../hooks/use_search_items'; import { getAggNameConflictToastMessages } from './common'; import { StepDefineForm } from './step_define_form'; -import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; +import { kqlPluginMock } from '@kbn/kql/public/mocks'; jest.mock('../../../../app_dependencies'); @@ -75,7 +75,7 @@ describe('Transform: ', () => { const services = { ...startMock, data: dataPluginMock.createStartContract(), - unifiedSearch: unifiedSearchPluginMock.createStartContract(), + kql: kqlPluginMock.createStartContract(), appName: 'the-test-app', storage: createMockStorage(), }; diff --git a/x-pack/platform/plugins/shared/alerting/kibana.jsonc b/x-pack/platform/plugins/shared/alerting/kibana.jsonc index fbed1e699b685..18f933e0dd22d 100644 --- a/x-pack/platform/plugins/shared/alerting/kibana.jsonc +++ b/x-pack/platform/plugins/shared/alerting/kibana.jsonc @@ -21,7 +21,6 @@ "licensing", "taskManager", "management", - "unifiedSearch", "kql", "embeddable" ], diff --git a/x-pack/platform/plugins/shared/alerting/moon.yml b/x-pack/platform/plugins/shared/alerting/moon.yml index a0e42ff785027..761014d5567e9 100644 --- a/x-pack/platform/plugins/shared/alerting/moon.yml +++ b/x-pack/platform/plugins/shared/alerting/moon.yml @@ -52,7 +52,6 @@ dependsOn: - '@kbn/rrule' - '@kbn/management-plugin' - '@kbn/core-saved-objects-utils-server' - - '@kbn/unified-search-plugin' - '@kbn/kql' - '@kbn/core-http-server-mocks' - '@kbn/serverless' diff --git a/x-pack/platform/plugins/shared/alerting/public/plugin.ts b/x-pack/platform/plugins/shared/alerting/public/plugin.ts index 22207d9941fe5..b4bb2f20dfdb2 100644 --- a/x-pack/platform/plugins/shared/alerting/public/plugin.ts +++ b/x-pack/platform/plugins/shared/alerting/public/plugin.ts @@ -10,7 +10,7 @@ import type { ManagementSetup } from '@kbn/management-plugin/public'; import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import type { LicensingPluginStart } from '@kbn/licensing-plugin/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; -import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import type { KqlPluginStart } from '@kbn/kql/public'; import type { ServerlessPluginStart } from '@kbn/serverless/public'; import type { MaintenanceWindowsServerStart } from '@kbn/maintenance-windows-plugin/server'; @@ -70,7 +70,7 @@ export interface AlertingPluginSetup { export interface AlertingPluginStart { licensing: LicensingPluginStart; spaces: SpacesPluginStart; - unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; data: DataPublicPluginStart; serverless?: ServerlessPluginStart; } diff --git a/x-pack/platform/plugins/shared/alerting/tsconfig.json b/x-pack/platform/plugins/shared/alerting/tsconfig.json index 0611073d7fc58..7e562fa96c3fc 100644 --- a/x-pack/platform/plugins/shared/alerting/tsconfig.json +++ b/x-pack/platform/plugins/shared/alerting/tsconfig.json @@ -45,7 +45,6 @@ "@kbn/rrule", "@kbn/management-plugin", "@kbn/core-saved-objects-utils-server", - "@kbn/unified-search-plugin", "@kbn/kql", "@kbn/core-http-server-mocks", "@kbn/serverless", @@ -75,7 +74,6 @@ "@kbn/maintenance-windows-plugin", "@kbn/config", "@kbn/expect", - "@kbn/kql" ], "exclude": ["target/**/*"] } diff --git a/x-pack/platform/plugins/shared/fleet/.storybook/context/stubs.tsx b/x-pack/platform/plugins/shared/fleet/.storybook/context/stubs.tsx index f71ea4a0e75c3..609635c1ce8c0 100644 --- a/x-pack/platform/plugins/shared/fleet/.storybook/context/stubs.tsx +++ b/x-pack/platform/plugins/shared/fleet/.storybook/context/stubs.tsx @@ -13,6 +13,7 @@ export const stubbedStartServices = { data: {} as FleetStartServices['data'], dataViews: {} as FleetStartServices['dataViews'], unifiedSearch: {} as FleetStartServices['unifiedSearch'], + kql: {} as FleetStartServices['kql'], deprecations: {} as FleetStartServices['deprecations'], fatalErrors: {} as FleetStartServices['fatalErrors'], navigation: {} as FleetStartServices['navigation'], diff --git a/x-pack/platform/plugins/shared/maps/kibana.jsonc b/x-pack/platform/plugins/shared/maps/kibana.jsonc index 660bf4b810708..0e6a74ccb56eb 100644 --- a/x-pack/platform/plugins/shared/maps/kibana.jsonc +++ b/x-pack/platform/plugins/shared/maps/kibana.jsonc @@ -17,6 +17,7 @@ "requiredPlugins": [ "controls", "unifiedSearch", + "kql", "lens", "licensing", "features", diff --git a/x-pack/platform/plugins/shared/ml/kibana.jsonc b/x-pack/platform/plugins/shared/ml/kibana.jsonc index 1a4daf6880ce7..ec635469e23e4 100644 --- a/x-pack/platform/plugins/shared/ml/kibana.jsonc +++ b/x-pack/platform/plugins/shared/ml/kibana.jsonc @@ -29,6 +29,7 @@ "triggersActionsUi", "uiActions", "unifiedSearch", + "kql", "savedObjectsManagement", "savedSearch", "contentManagement", diff --git a/x-pack/platform/plugins/shared/ml/public/application/management/index.ts b/x-pack/platform/plugins/shared/ml/public/application/management/index.ts index 1cf4d7bf1fdab..a73a687c3eddf 100644 --- a/x-pack/platform/plugins/shared/ml/public/application/management/index.ts +++ b/x-pack/platform/plugins/shared/ml/public/application/management/index.ts @@ -163,6 +163,7 @@ export function registerManagementSections( uiActions: pluginsStart.uiActions, unifiedSearch: pluginsStart.unifiedSearch, spaces: pluginsStart.spaces, + kql: pluginsStart.kql, ...deps, }; diff --git a/x-pack/platform/plugins/shared/stack_alerts/kibana.jsonc b/x-pack/platform/plugins/shared/stack_alerts/kibana.jsonc index c3536e923ec26..13f55d01af296 100644 --- a/x-pack/platform/plugins/shared/stack_alerts/kibana.jsonc +++ b/x-pack/platform/plugins/shared/stack_alerts/kibana.jsonc @@ -16,6 +16,7 @@ ], "requiredPlugins": [ "unifiedSearch", + "kql", "alerting", "features", "triggersActionsUi", diff --git a/x-pack/solutions/observability/plugins/apm/kibana.jsonc b/x-pack/solutions/observability/plugins/apm/kibana.jsonc index d2419ca75db64..8604f38acf7fc 100644 --- a/x-pack/solutions/observability/plugins/apm/kibana.jsonc +++ b/x-pack/solutions/observability/plugins/apm/kibana.jsonc @@ -30,6 +30,7 @@ "triggersActionsUi", "share", "unifiedSearch", + "kql", "dataViews", "lens", "maps", diff --git a/x-pack/solutions/observability/plugins/uptime/kibana.jsonc b/x-pack/solutions/observability/plugins/uptime/kibana.jsonc index 1007475b0e642..bc28e4c6a4598 100644 --- a/x-pack/solutions/observability/plugins/uptime/kibana.jsonc +++ b/x-pack/solutions/observability/plugins/uptime/kibana.jsonc @@ -37,6 +37,7 @@ "triggersActionsUi", "usageCollection", "unifiedSearch", + "kql", "charts", "fieldsMetadata" ], diff --git a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/.storybook/decorators/kibana_react_decorator.tsx b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/.storybook/decorators/kibana_react_decorator.tsx index 00c4271aaeae1..d7b0f114b6f36 100644 --- a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/.storybook/decorators/kibana_react_decorator.tsx +++ b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/.storybook/decorators/kibana_react_decorator.tsx @@ -10,7 +10,6 @@ import { action } from '@storybook/addon-actions'; import { createKibanaReactContext, type KibanaServices } from '@kbn/kibana-react-plugin/public'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; import { applicationServiceMock } from '@kbn/core-application-browser-mocks'; -import { kqlPluginMock } from '@kbn/kql/public/mocks'; import { of } from 'rxjs'; import { WEB_STORAGE_CLEAR_ACTION, @@ -121,7 +120,7 @@ const uiSettings: Record = { const services: Partial = { appName: 'test', application: applicationServiceMock.createStartContract(), - unifiedSearch: { + kql: { autocomplete: { getQuerySuggestions: () => [], getAutocompleteSettings: () => {}, @@ -180,7 +179,6 @@ const services: Partial = { editDataView: action(EDIT_DATA_VIEW_ACTION), }, }, - kql: kqlPluginMock.createStartContract(), }; export const KibanaReactStorybookDecorator = (Story: ComponentType) => { diff --git a/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/types.ts b/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/types.ts index 6a59195d961aa..5d226b92d28ef 100644 --- a/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/types.ts +++ b/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/types.ts @@ -31,6 +31,7 @@ import type { estypes } from '@elastic/elasticsearch'; import type { IKibanaSearchResponse, IKibanaSearchRequest } from '@kbn/search-types'; import type { BoolQuery } from '@kbn/es-query'; import type { GenericBuckets } from '@kbn/grouping/src'; +import type { KqlPluginStart } from '@kbn/kql/public'; export interface BaseEsQuery { query?: { bool: BoolQuery; @@ -43,6 +44,7 @@ export interface CspClientPluginStartDeps { dataViews: DataViewsServicePublic; dataViewFieldEditor: IndexPatternFieldEditorStart; unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; uiActions: UiActionsStart; fieldFormats: FieldFormatsStart; toastNotifications: ToastsStart; From 975525f549cd79024a40b4727b8b2e690999af60 Mon Sep 17 00:00:00 2001 From: Stratoula Date: Tue, 23 Dec 2025 12:41:42 +0100 Subject: [PATCH 36/60] Fix vis editor --- src/platform/plugins/shared/visualizations/kibana.jsonc | 1 + src/platform/plugins/shared/visualizations/public/plugin.ts | 3 +++ .../shared/visualizations/public/visualize_app/types.ts | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/platform/plugins/shared/visualizations/kibana.jsonc b/src/platform/plugins/shared/visualizations/kibana.jsonc index 3f8a420f305c6..edc5b90a31755 100644 --- a/src/platform/plugins/shared/visualizations/kibana.jsonc +++ b/src/platform/plugins/shared/visualizations/kibana.jsonc @@ -26,6 +26,7 @@ "dataViews", "dataViewEditor", "unifiedSearch", + "kql", "usageCollection", "savedObjectsFinder", "savedObjectsManagement", diff --git a/src/platform/plugins/shared/visualizations/public/plugin.ts b/src/platform/plugins/shared/visualizations/public/plugin.ts index 9049b14470bb9..a75a5552ac76c 100644 --- a/src/platform/plugins/shared/visualizations/public/plugin.ts +++ b/src/platform/plugins/shared/visualizations/public/plugin.ts @@ -67,6 +67,7 @@ import type { EmbeddableEnhancedPluginStart } from '@kbn/embeddable-enhanced-plu import { css, injectGlobal } from '@emotion/css'; import { VisualizeConstants, VISUALIZE_EMBEDDABLE_TYPE } from '@kbn/visualizations-common'; +import type { KqlPluginStart } from '@kbn/kql/public'; import type { TypesSetup, TypesStart } from './vis_types'; import type { VisualizeServices } from './visualize_app/types'; import { @@ -166,6 +167,7 @@ export interface VisualizationsStartDeps { screenshotMode: ScreenshotModePluginStart; fieldFormats: FieldFormatsStart; unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; usageCollection: UsageCollectionStart; savedObjectsManagement: SavedObjectsManagementPluginStart; contentManagement: ContentManagementPublicStart; @@ -406,6 +408,7 @@ export class VisualizationsPlugin visEditorsRegistry, listingViewRegistry, unifiedSearch: pluginsStart.unifiedSearch, + kql: pluginsStart.kql, serverless: pluginsStart.serverless, noDataPage: pluginsStart.noDataPage, contentManagement: pluginsStart.contentManagement, diff --git a/src/platform/plugins/shared/visualizations/public/visualize_app/types.ts b/src/platform/plugins/shared/visualizations/public/visualize_app/types.ts index c9d812efbae5c..31e3cec922df7 100644 --- a/src/platform/plugins/shared/visualizations/public/visualize_app/types.ts +++ b/src/platform/plugins/shared/visualizations/public/visualize_app/types.ts @@ -46,6 +46,7 @@ import type { SavedSearch, SavedSearchPublicPluginStart } from '@kbn/saved-searc import type { ServerlessPluginStart } from '@kbn/serverless/public'; import type { NoDataPagePluginStart } from '@kbn/no-data-page-plugin/public'; import type { ContentManagementPublicStart } from '@kbn/content-management-plugin/public'; +import type { KqlPluginStart } from '@kbn/kql/public'; import type { Vis, VisualizeEmbeddableContract, VisSavedObject, PersistedState } from '..'; import type { ListingViewRegistry, SavedVisState } from '../types'; @@ -119,6 +120,7 @@ export interface VisualizeServices extends CoreStart { visEditorsRegistry: VisEditorsRegistry; listingViewRegistry: ListingViewRegistry; unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; serverless?: ServerlessPluginStart; noDataPage?: NoDataPagePluginStart; contentManagement: ContentManagementPublicStart; From 7d419b2fbb05a541116ce41093a1b0e94a833116 Mon Sep 17 00:00:00 2001 From: Stratoula Date: Tue, 23 Dec 2025 12:46:55 +0100 Subject: [PATCH 37/60] More FTs fixes --- .../test/scout/ui/tests/discover_cdp_perf.spec.ts | 1 + x-pack/solutions/observability/plugins/profiling/kibana.jsonc | 1 + .../solutions/observability/plugins/profiling/public/types.ts | 3 +++ 3 files changed, 5 insertions(+) diff --git a/x-pack/platform/plugins/private/discover_enhanced/test/scout/ui/tests/discover_cdp_perf.spec.ts b/x-pack/platform/plugins/private/discover_enhanced/test/scout/ui/tests/discover_cdp_perf.spec.ts index 25344079d8f5a..f59c4331c0545 100644 --- a/x-pack/platform/plugins/private/discover_enhanced/test/scout/ui/tests/discover_cdp_perf.spec.ts +++ b/x-pack/platform/plugins/private/discover_enhanced/test/scout/ui/tests/discover_cdp_perf.spec.ts @@ -73,6 +73,7 @@ test.describe( 'eventAnnotation', 'expressionXY', 'kbn-ui-shared-deps-npm', + 'kql', 'lens', 'maps', ...(config.projectType === 'security' ? ['securitySolution'] : []), diff --git a/x-pack/solutions/observability/plugins/profiling/kibana.jsonc b/x-pack/solutions/observability/plugins/profiling/kibana.jsonc index 2be8c5af7cf69..eb7c3feddd665 100644 --- a/x-pack/solutions/observability/plugins/profiling/kibana.jsonc +++ b/x-pack/solutions/observability/plugins/profiling/kibana.jsonc @@ -23,6 +23,7 @@ "observability", "observabilityShared", "unifiedSearch", + "kql", "share", "profilingDataAccess" ], diff --git a/x-pack/solutions/observability/plugins/profiling/public/types.ts b/x-pack/solutions/observability/plugins/profiling/public/types.ts index 9ad6036a44439..89146c32f72e7 100644 --- a/x-pack/solutions/observability/plugins/profiling/public/types.ts +++ b/x-pack/solutions/observability/plugins/profiling/public/types.ts @@ -28,6 +28,7 @@ import type { UnifiedSearchPublicPluginStart, UnifiedSearchPluginSetup, } from '@kbn/unified-search-plugin/public'; +import type { KqlPluginSetup, KqlPluginStart } from '@kbn/kql/public'; export interface ProfilingPluginPublicSetupDeps { observability: ObservabilityPublicSetup; @@ -38,6 +39,7 @@ export interface ProfilingPluginPublicSetupDeps { charts: ChartsPluginSetup; share: SharePluginSetup; unifiedSearch: UnifiedSearchPluginSetup; + kql: KqlPluginSetup; } export interface ProfilingPluginPublicStartDeps { @@ -49,5 +51,6 @@ export interface ProfilingPluginPublicStartDeps { charts: ChartsPluginStart; share: SharePluginStart; unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; licensing: LicensingPluginStart; } From 2800abc3e3d3470000beed78516bef72bf604b73 Mon Sep 17 00:00:00 2001 From: Stratoula Date: Tue, 23 Dec 2025 15:41:27 +0100 Subject: [PATCH 38/60] Fix unit test --- src/platform/plugins/shared/visualizations/public/mocks.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/platform/plugins/shared/visualizations/public/mocks.ts b/src/platform/plugins/shared/visualizations/public/mocks.ts index f781362350d08..84167d103ac85 100644 --- a/src/platform/plugins/shared/visualizations/public/mocks.ts +++ b/src/platform/plugins/shared/visualizations/public/mocks.ts @@ -24,6 +24,7 @@ import { savedObjectTaggingOssPluginMock } from '@kbn/saved-objects-tagging-oss- import { screenshotModePluginMock } from '@kbn/screenshot-mode-plugin/public/mocks'; import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks'; import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; +import { kqlPluginMock } from '@kbn/kql/public/mocks'; import { savedObjectsManagementPluginMock } from '@kbn/saved-objects-management-plugin/public/mocks'; import { savedSearchPluginMock } from '@kbn/saved-search-plugin/public/mocks'; import type { ContentManagementPublicStart } from '@kbn/content-management-plugin/public'; @@ -83,6 +84,7 @@ const createInstance = async () => { screenshotMode: screenshotModePluginMock.createStartContract(), fieldFormats: fieldFormatsServiceMock.createStartContract(), unifiedSearch: unifiedSearchPluginMock.createStartContract(), + kql: kqlPluginMock.createStartContract(), usageCollection: { reportUiCounter: jest.fn(), }, From f88bbf8b5c94d66c6ea07e51d47e5f53f31c264b Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 23 Dec 2025 14:54:36 +0000 Subject: [PATCH 39/60] Changes from node scripts/lint_ts_projects --fix --- src/platform/plugins/shared/visualizations/tsconfig.json | 1 + x-pack/platform/plugins/private/graph/tsconfig.json | 1 + x-pack/solutions/observability/plugins/profiling/tsconfig.json | 3 ++- .../packages/kbn-cloud-security-posture/graph/tsconfig.json | 1 - .../packages/kbn-cloud-security-posture/public/tsconfig.json | 3 ++- 5 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/platform/plugins/shared/visualizations/tsconfig.json b/src/platform/plugins/shared/visualizations/tsconfig.json index 1845133bb2b59..09e162acdf448 100644 --- a/src/platform/plugins/shared/visualizations/tsconfig.json +++ b/src/platform/plugins/shared/visualizations/tsconfig.json @@ -82,6 +82,7 @@ "@kbn/visualizations-common", "@kbn/lens-common", "@kbn/cps", + "@kbn/kql", ], "exclude": ["target/**/*"] } diff --git a/x-pack/platform/plugins/private/graph/tsconfig.json b/x-pack/platform/plugins/private/graph/tsconfig.json index b2b2d55099048..f9c670526e311 100644 --- a/x-pack/platform/plugins/private/graph/tsconfig.json +++ b/x-pack/platform/plugins/private/graph/tsconfig.json @@ -53,6 +53,7 @@ "@kbn/core-saved-objects-api-server", "@kbn/licensing-types", "@kbn/kql", + "@kbn/unified-search-plugin", ], "exclude": [ "target/**/*", diff --git a/x-pack/solutions/observability/plugins/profiling/tsconfig.json b/x-pack/solutions/observability/plugins/profiling/tsconfig.json index 2e4024d718994..65e88b8ea6305 100644 --- a/x-pack/solutions/observability/plugins/profiling/tsconfig.json +++ b/x-pack/solutions/observability/plugins/profiling/tsconfig.json @@ -59,7 +59,8 @@ "@kbn/core-security-server", "@kbn/charts-theme", "@kbn/shared-ux-error-boundary", - "@kbn/licensing-types" + "@kbn/licensing-types", + "@kbn/kql" // add references to other TypeScript projects the plugin depends on // requiredPlugins from ./kibana.json diff --git a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/tsconfig.json b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/tsconfig.json index cf0f71e517d4a..dc38f73ebf761 100644 --- a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/tsconfig.json +++ b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/tsconfig.json @@ -27,6 +27,5 @@ "@kbn/react-query", "@kbn/fleet-plugin", "@kbn/core-http-browser", - "@kbn/kql" ] } diff --git a/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/tsconfig.json b/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/tsconfig.json index 5153a3a11c1b5..f89a0c4e477ee 100644 --- a/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/tsconfig.json +++ b/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/tsconfig.json @@ -38,6 +38,7 @@ "@kbn/expandable-flyout", "@kbn/grouping", "@kbn/std", - "@kbn/react-query" + "@kbn/react-query", + "@kbn/kql" ] } From db0b4e6d9d429304a19b35f601424ac90cf5e99e Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 23 Dec 2025 14:55:26 +0000 Subject: [PATCH 40/60] Changes from node scripts/build_plugin_list_docs --- docs/extend/plugin-list.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/extend/plugin-list.md b/docs/extend/plugin-list.md index 4d6d686205ec1..c9d6812aeabf0 100644 --- a/docs/extend/plugin-list.md +++ b/docs/extend/plugin-list.md @@ -64,7 +64,7 @@ mapped_pages: | [kibanaReact](https://github.com/elastic/kibana/blob/main/src/platform/plugins/shared/kibana_react/README.md) | Tools for building React applications in Kibana. | | [kibanaUsageCollection](https://github.com/elastic/kibana/blob/main/src/platform/plugins/private/kibana_usage_collection/README.md) | This plugin registers the Platform Usage Collectors in Kibana. | | [kibanaUtils](https://github.com/elastic/kibana/blob/main/src/platform/plugins/shared/kibana_utils/README.md) | Utilities for building Kibana plugins. | -| [kql](https://github.com/elastic/kibana/blob/main/src/platform/plugins/shared/kql/README.md) | WARNING: Missing or empty README. | +| [kql](https://github.com/elastic/kibana/blob/main/src/platform/plugins/shared/kql/README.md) | This plugin contains the kql autocomplete service and the UI KQL searchbar. | | [links](https://github.com/elastic/kibana/blob/main/src/platform/plugins/private/links/README.md) | This plugin adds the Links panel which allows authors to create hard links to navigate on click and bring all context from the source dashboard to the destination dashboard. | | [management](https://github.com/elastic/kibana/blob/main/src/platform/plugins/shared/management/README.md) | This plugins contains the "Stack Management" page framework. It offers navigation and an API to link individual management section into it. This plugin does not contain any individual management section itself. | | [mapsEms](https://github.com/elastic/kibana/blob/main/src/platform/plugins/private/maps_ems/README.md) | Utility plugin: | From e67ba0889ed9f3604f2d2cf964040e7008e4b3eb Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 23 Dec 2025 15:08:45 +0000 Subject: [PATCH 41/60] Changes from node scripts/regenerate_moon_projects.js --update --- src/platform/plugins/shared/visualizations/moon.yml | 1 + x-pack/platform/plugins/private/graph/moon.yml | 1 + x-pack/platform/plugins/shared/alerting/moon.yml | 1 - x-pack/solutions/observability/plugins/profiling/moon.yml | 1 + .../security/packages/kbn-cloud-security-posture/graph/moon.yml | 1 - .../security/packages/kbn-cloud-security-posture/public/moon.yml | 1 + 6 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/platform/plugins/shared/visualizations/moon.yml b/src/platform/plugins/shared/visualizations/moon.yml index 5f11c0ad7c8a5..7884459fbe210 100644 --- a/src/platform/plugins/shared/visualizations/moon.yml +++ b/src/platform/plugins/shared/visualizations/moon.yml @@ -95,6 +95,7 @@ dependsOn: - '@kbn/visualizations-common' - '@kbn/lens-common' - '@kbn/cps' + - '@kbn/kql' tags: - plugin - prod diff --git a/x-pack/platform/plugins/private/graph/moon.yml b/x-pack/platform/plugins/private/graph/moon.yml index 8949265e84c17..d3f466e85497d 100644 --- a/x-pack/platform/plugins/private/graph/moon.yml +++ b/x-pack/platform/plugins/private/graph/moon.yml @@ -58,6 +58,7 @@ dependsOn: - '@kbn/core-saved-objects-api-server' - '@kbn/licensing-types' - '@kbn/kql' + - '@kbn/unified-search-plugin' tags: - plugin - prod diff --git a/x-pack/platform/plugins/shared/alerting/moon.yml b/x-pack/platform/plugins/shared/alerting/moon.yml index 761014d5567e9..d970296c35cb4 100644 --- a/x-pack/platform/plugins/shared/alerting/moon.yml +++ b/x-pack/platform/plugins/shared/alerting/moon.yml @@ -81,7 +81,6 @@ dependsOn: - '@kbn/maintenance-windows-plugin' - '@kbn/config' - '@kbn/expect' - - '@kbn/kql' tags: - plugin - prod diff --git a/x-pack/solutions/observability/plugins/profiling/moon.yml b/x-pack/solutions/observability/plugins/profiling/moon.yml index 66ec02223b3c6..cc3a967c6553d 100644 --- a/x-pack/solutions/observability/plugins/profiling/moon.yml +++ b/x-pack/solutions/observability/plugins/profiling/moon.yml @@ -65,6 +65,7 @@ dependsOn: - '@kbn/charts-theme' - '@kbn/shared-ux-error-boundary' - '@kbn/licensing-types' + - '@kbn/kql' tags: - plugin - prod diff --git a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/moon.yml b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/moon.yml index bec2e2070b5c1..942966727af17 100644 --- a/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/moon.yml +++ b/x-pack/solutions/security/packages/kbn-cloud-security-posture/graph/moon.yml @@ -39,7 +39,6 @@ dependsOn: - '@kbn/react-query' - '@kbn/fleet-plugin' - '@kbn/core-http-browser' - - '@kbn/kql' tags: - shared-browser - package diff --git a/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/moon.yml b/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/moon.yml index 27a2f12c08c4f..d9c7c1d56b539 100644 --- a/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/moon.yml +++ b/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/moon.yml @@ -46,6 +46,7 @@ dependsOn: - '@kbn/grouping' - '@kbn/std' - '@kbn/react-query' + - '@kbn/kql' tags: - shared-browser - package From dd2cb51deaa8d73b202ff76558e570711efccd8b Mon Sep 17 00:00:00 2001 From: Stratoula Date: Wed, 24 Dec 2025 09:36:42 +0100 Subject: [PATCH 42/60] Fix jest test --- .../definitions/filters/filters.test.tsx | 90 ++++++++++++------- 1 file changed, 57 insertions(+), 33 deletions(-) diff --git a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/filters/filters.test.tsx b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/filters/filters.test.tsx index 3b1c3cb9c33c6..2aa60a5967b73 100644 --- a/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/filters/filters.test.tsx +++ b/x-pack/platform/plugins/shared/lens/public/datasources/form_based/operations/definitions/filters/filters.test.tsx @@ -8,29 +8,47 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks'; -import type { IUiSettingsClient, HttpSetup } from '@kbn/core/public'; +import type { IUiSettingsClient } from '@kbn/core/public'; import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; -import { fireEvent, screen } from '@testing-library/react'; +import { fireEvent, screen, render } from '@testing-library/react'; import { kqlPluginMock } from '@kbn/kql/public/mocks'; import type { FiltersIndexPatternColumn, FormBasedLayer } from '@kbn/lens-common'; import { filtersOperation } from '..'; import { createMockedIndexPattern } from '../../../mocks'; import userEvent from '@testing-library/user-event'; -import { renderWithProviders } from '../../../../../test_utils/test_utils'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { coreMock } from '@kbn/core/public/mocks'; +import { I18nProvider } from '@kbn/i18n-react'; +import { EuiThemeProvider } from '@elastic/eui'; const uiSettingsMock = {} as IUiSettingsClient; +const coreMockStart = coreMock.createStart(); -const defaultProps = { +const mockServices = { + http: coreMockStart.http, storage: {} as IStorageWrapper, + dataViews: dataViewPluginMocks.createStartContract(), + data: dataPluginMock.createStartContract(), uiSettings: uiSettingsMock, + notifications: coreMockStart.notifications, + kql: kqlPluginMock.createStartContract(), + docLinks: coreMockStart.docLinks, +}; + +const wrapInProviders = (component: React.ReactElement) => ( + + + {component} + + +); + +const defaultProps = { + ...mockServices, dateRange: { fromDate: 'now-1d', toDate: 'now' }, - data: dataPluginMock.createStartContract(), fieldFormats: fieldFormatsServiceMock.createStartContract(), - kql: kqlPluginMock.createStartContract(), - dataViews: dataViewPluginMocks.createStartContract(), - http: {} as HttpSetup, indexPattern: createMockedIndexPattern(), operationDefinitionMap: {}, isFullscreen: false, @@ -41,7 +59,7 @@ const defaultProps = { // @ts-expect-error window['__@hello-pangea/dnd-disable-dev-warnings'] = true; // issue with enzyme & @hello-pangea/dnd throwing errors: https://github.com/hello-pangea/dnd/issues/644 -jest.mock('@kbn/unified-search-plugin/public', () => ({ +jest.mock('@kbn/kql/public', () => ({ QueryStringInput: () => { return 'QueryStringInput'; }, @@ -300,14 +318,16 @@ describe('filters', () => { // Workaround for timeout via https://github.com/testing-library/user-event/issues/833#issuecomment-1171452841 const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); const updateLayerSpy = jest.fn(); - renderWithProviders( - + render( + wrapInProviders( + + ) ); await user.click(screen.getAllByTestId('indexPattern-filters-existingFilterTrigger')[1]); @@ -342,14 +362,16 @@ describe('filters', () => { describe('Modify filters', () => { it('should correctly show existing filters ', () => { const updateLayerSpy = jest.fn(); - renderWithProviders( - + render( + wrapInProviders( + + ) ); const filtersLabels = screen .getAllByTestId('indexPattern-filters-existingFilterTrigger') @@ -362,14 +384,16 @@ describe('filters', () => { const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); jest.useFakeTimers(); const updateLayerSpy = jest.fn(); - renderWithProviders( - + render( + wrapInProviders( + + ) ); await user.click(screen.getByTestId('lns-customBucketContainer-remove-1')); From 265e3e19c4f50d7c49d592a8264727d1642f3dff Mon Sep 17 00:00:00 2001 From: Stratoula Date: Wed, 24 Dec 2025 09:41:17 +0100 Subject: [PATCH 43/60] Fix input control --- src/platform/plugins/private/input_control_vis/kibana.jsonc | 1 + .../plugins/private/input_control_vis/public/plugin.ts | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/platform/plugins/private/input_control_vis/kibana.jsonc b/src/platform/plugins/private/input_control_vis/kibana.jsonc index 0dce906132726..aa49fb93ea3d8 100644 --- a/src/platform/plugins/private/input_control_vis/kibana.jsonc +++ b/src/platform/plugins/private/input_control_vis/kibana.jsonc @@ -17,6 +17,7 @@ "visDefaultEditor", "visualizations", "unifiedSearch", + "kql", "uiActions" ], "requiredBundles": [ diff --git a/src/platform/plugins/private/input_control_vis/public/plugin.ts b/src/platform/plugins/private/input_control_vis/public/plugin.ts index a5ec8db5e9a10..fb56e23ffb482 100644 --- a/src/platform/plugins/private/input_control_vis/public/plugin.ts +++ b/src/platform/plugins/private/input_control_vis/public/plugin.ts @@ -14,7 +14,7 @@ import type { UnifiedSearchPublicPluginStart, UnifiedSearchPluginSetup, } from '@kbn/unified-search-plugin/public'; -import type { KqlPluginSetup } from '@kbn/kql/public'; +import type { KqlPluginSetup, KqlPluginStart } from '@kbn/kql/public'; import type { Plugin as ExpressionsPublicPlugin } from '@kbn/expressions-plugin/public'; import type { VisualizationsSetup, VisualizationsStart } from '@kbn/visualizations-plugin/public'; import type { UiActionsStart } from '@kbn/ui-actions-plugin/public'; @@ -54,6 +54,7 @@ export interface InputControlVisPluginStartDependencies { visualizations: VisualizationsStart; data: DataPublicPluginStart; unifiedSearch: UnifiedSearchPublicPluginStart; + kql: KqlPluginStart; uiActions: UiActionsStart; } From f65b5ce065c715f2e0c4b4f87a222485dc7c53aa Mon Sep 17 00:00:00 2001 From: Stratoula Date: Wed, 24 Dec 2025 10:10:47 +0100 Subject: [PATCH 44/60] Fix observability alerts --- .../solutions/observability/plugins/observability/kibana.jsonc | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/solutions/observability/plugins/observability/kibana.jsonc b/x-pack/solutions/observability/plugins/observability/kibana.jsonc index 7a291d959f517..bf6903982f69d 100644 --- a/x-pack/solutions/observability/plugins/observability/kibana.jsonc +++ b/x-pack/solutions/observability/plugins/observability/kibana.jsonc @@ -30,6 +30,7 @@ "triggersActionsUi", "security", "share", + "unifiedSearch", "visualizations", "dashboard", "expressions", From 013a0cc28abe6ca62ab694247cb0440eb6bea13d Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 2 Jan 2026 08:22:44 +0000 Subject: [PATCH 45/60] Changes from node scripts/eslint_all_files --no-cache --fix --- .../solutions/security/plugins/security_solution/public/types.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/types.ts b/x-pack/solutions/security/plugins/security_solution/public/types.ts index 6600389ae89a5..672b62fa43c5a 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/types.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/types.ts @@ -66,7 +66,6 @@ import type { ProductFeatureKeyType, ProductFeatureKeys } from '@kbn/security-so import type { ElasticAssistantSharedStatePublicPluginStart } from '@kbn/elastic-assistant-shared-state-plugin/public'; import type { InferencePublicStart } from '@kbn/inference-plugin/public'; import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public'; -import type { OnechatPluginStart } from '@kbn/onechat-plugin/public'; import type { KqlPluginStart } from '@kbn/kql/public'; import type { AgentBuilderPluginStart } from '@kbn/agent-builder-plugin/public'; import type { ResolverPluginSetup } from './resolver/types'; From 488bf594195afc0c17d259d566d82b56170a7413 Mon Sep 17 00:00:00 2001 From: Stratoula Date: Fri, 2 Jan 2026 09:37:46 +0100 Subject: [PATCH 46/60] Update limits --- packages/kbn-optimizer/limits.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index be74b4929184f..8e77e1ed9b5bd 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -55,7 +55,7 @@ pageLoadAssetSize: embeddableAlertsTable: 6524 embeddableEnhanced: 8448 enterpriseSearch: 40244 - entityStore: 4075 + entityStore: 1755 esql: 18224 esqlDataGrid: 10209 esUiShared: 101220 @@ -81,7 +81,7 @@ pageLoadAssetSize: globalSearch: 6890 globalSearchBar: 26986 globalSearchProviders: 4646 - graph: 9946 + graph: 9924 grokdebugger: 5484 home: 13560 imageEmbeddable: 6000 @@ -99,7 +99,7 @@ pageLoadAssetSize: kibanaReact: 22503 kibanaUsageCollection: 1736 kibanaUtils: 54848 - kql: 36676 + kql: 15485 kubernetesSecurity: 6807 lens: 71718 licenseManagement: 8265 @@ -128,11 +128,10 @@ pageLoadAssetSize: observabilityLogsExplorer: 4918 observabilityOnboarding: 12872 observabilityShared: 75115 - onechat: 78482 osquery: 47422 painlessLab: 6299 presentationPanel: 11418 - presentationUtil: 9948 + presentationUtil: 9285 productDocBase: 5678 productIntercept: 9860 profiling: 20716 From 2d5af9e4026fa169027b9de2807ddbc966e995cb Mon Sep 17 00:00:00 2001 From: Stratoula Date: Mon, 5 Jan 2026 08:59:43 +0100 Subject: [PATCH 47/60] Fix wrong merge conflict --- x-pack/platform/plugins/private/transform/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/platform/plugins/private/transform/tsconfig.json b/x-pack/platform/plugins/private/transform/tsconfig.json index 753e3047b394d..e0f55dc062690 100644 --- a/x-pack/platform/plugins/private/transform/tsconfig.json +++ b/x-pack/platform/plugins/private/transform/tsconfig.json @@ -86,7 +86,7 @@ "@kbn/fields-metadata-plugin", "@kbn/licensing-types", "@kbn/react-query", - "@kbn/kql" + "@kbn/kql", "@kbn/scout", "@kbn/core-http-common" ], From 7c9eddf08005501f2a08aefe8e4382dc2a64b891 Mon Sep 17 00:00:00 2001 From: Stratoula Date: Mon, 5 Jan 2026 16:01:18 +0100 Subject: [PATCH 48/60] Cleanup --- .../plugins/shared/vis_types/timeseries/public/plugin.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/platform/plugins/shared/vis_types/timeseries/public/plugin.ts b/src/platform/plugins/shared/vis_types/timeseries/public/plugin.ts index 16b0392dc5aa4..ef90cff26c741 100644 --- a/src/platform/plugins/shared/vis_types/timeseries/public/plugin.ts +++ b/src/platform/plugins/shared/vis_types/timeseries/public/plugin.ts @@ -67,7 +67,6 @@ export interface TimeseriesVisDependencies extends Partial { timefilter: TimefilterContract; appName: string; kql: KqlPluginStart; - core: CoreStart; notifications: CoreStart['notifications']; storage: IStorageWrapper; data: DataPublicPluginStart; From 1e711997d22dbc722f7be03fda84c3812a62b072 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 6 Jan 2026 06:48:06 +0000 Subject: [PATCH 49/60] Changes from node scripts/lint_ts_projects --fix --- src/platform/packages/shared/kbn-lens-common/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/src/platform/packages/shared/kbn-lens-common/tsconfig.json b/src/platform/packages/shared/kbn-lens-common/tsconfig.json index f11735cdf8228..22aa574eeb38a 100644 --- a/src/platform/packages/shared/kbn-lens-common/tsconfig.json +++ b/src/platform/packages/shared/kbn-lens-common/tsconfig.json @@ -63,7 +63,6 @@ "@kbn/i18n", "@kbn/chart-icons", "@kbn/alerts-ui-shared", - "@kbn/controls-plugin", "@kbn/cps", "@kbn/kql", "@kbn/control-group-renderer" From 23953f0743ec35a2ff47ab3c0443b345ca78b98e Mon Sep 17 00:00:00 2001 From: Stratoula Date: Tue, 6 Jan 2026 09:00:15 +0100 Subject: [PATCH 50/60] Fix --- src/platform/packages/shared/kbn-lens-common/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/src/platform/packages/shared/kbn-lens-common/tsconfig.json b/src/platform/packages/shared/kbn-lens-common/tsconfig.json index 22aa574eeb38a..c97ad6dcf8ae1 100644 --- a/src/platform/packages/shared/kbn-lens-common/tsconfig.json +++ b/src/platform/packages/shared/kbn-lens-common/tsconfig.json @@ -64,7 +64,6 @@ "@kbn/chart-icons", "@kbn/alerts-ui-shared", "@kbn/cps", - "@kbn/kql", "@kbn/control-group-renderer" ] } From b919a387a3e7b428015b28f136b0378872566853 Mon Sep 17 00:00:00 2001 From: Stratoula Date: Tue, 6 Jan 2026 09:21:01 +0100 Subject: [PATCH 51/60] Revert --- src/platform/packages/shared/kbn-lens-common/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/platform/packages/shared/kbn-lens-common/tsconfig.json b/src/platform/packages/shared/kbn-lens-common/tsconfig.json index c97ad6dcf8ae1..22aa574eeb38a 100644 --- a/src/platform/packages/shared/kbn-lens-common/tsconfig.json +++ b/src/platform/packages/shared/kbn-lens-common/tsconfig.json @@ -64,6 +64,7 @@ "@kbn/chart-icons", "@kbn/alerts-ui-shared", "@kbn/cps", + "@kbn/kql", "@kbn/control-group-renderer" ] } From b10067f1bb8475d9899a0d635f29e0ddfb89027f Mon Sep 17 00:00:00 2001 From: Stratoula Date: Wed, 7 Jan 2026 11:09:12 +0100 Subject: [PATCH 52/60] Fixes in the yml settings changes --- .../resources/base/bin/kibana-docker | 5 +++ .../kql/server/config_deprecations.test.ts | 26 +++++------ .../shared/kql/server/config_deprecations.ts | 45 +++++++++++++++++++ 3 files changed, 63 insertions(+), 13 deletions(-) diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker index 798db80fb825e..df39b75048e98 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker @@ -53,6 +53,11 @@ kibana_vars=( data.search.sessions.notTouchedTimeout data.search.sessions.pageSize data.search.sessions.trackingInterval + unifiedSearch.autocomplete.valueSuggestions.terminateAfter + unifiedSearch.autocomplete.valueSuggestions.timeout + unifiedSearch.autocomplete.querySuggestions.enabled + unifiedSearch.autocomplete.valueSuggestions.enabled + unifiedSearch.autocomplete.valueSuggestions.tiers kql.autocomplete.valueSuggestions.terminateAfter kql.autocomplete.valueSuggestions.timeout kql.autocomplete.querySuggestions.enabled diff --git a/src/platform/plugins/shared/kql/server/config_deprecations.test.ts b/src/platform/plugins/shared/kql/server/config_deprecations.test.ts index e109647fc1900..3c92da2baf130 100644 --- a/src/platform/plugins/shared/kql/server/config_deprecations.test.ts +++ b/src/platform/plugins/shared/kql/server/config_deprecations.test.ts @@ -38,7 +38,7 @@ const applyConfigDeprecations = (settings: Record = {}) => { describe('Config Deprecations', () => { it('does not report deprecations for default configurationc', () => { - const configFirstStep = { unifiedSearch: { autocomplete: { valueSuggestions: {} } } }; + const configFirstStep = { data: { autocomplete: { valueSuggestions: {} } } }; const { messages, migrated } = applyConfigDeprecations(cloneDeep(configFirstStep)); expect(migrated).toEqual(configFirstStep); expect(messages).toHaveLength(0); @@ -76,9 +76,9 @@ describe('Config Deprecations', () => { `); }); - it('renames unifiedSearch.autocomplete.querySuggestions.enabled to kql.autocomplete.querySuggestions.enabled', () => { + it('renames data.autocomplete.querySuggestions.enabled to kql.autocomplete.querySuggestions.enabled', () => { const config = { - unifiedSearch: { + data: { autocomplete: { querySuggestions: { enabled: false, @@ -87,18 +87,18 @@ describe('Config Deprecations', () => { }, }; const { messages, migrated } = applyConfigDeprecations(cloneDeep(config)); - expect(migrated.unifiedSearch?.autocomplete.querySuggestions.enabled).not.toBeDefined(); + expect(migrated.data?.autocomplete.querySuggestions.enabled).not.toBeDefined(); expect(migrated.kql.autocomplete.querySuggestions.enabled).toEqual(false); expect(messages).toMatchInlineSnapshot(` Array [ - "Setting \\"unifiedSearch.autocomplete.querySuggestions.enabled\\" has been replaced by \\"kql.autocomplete.querySuggestions.enabled\\"", + "Setting \\"data.autocomplete.querySuggestions.enabled\\" has been replaced by \\"kql.autocomplete.querySuggestions.enabled\\"", ] `); }); - it('renames unifiedSearch.autocomplete.valueSuggestions.enabled to kql.autocomplete.valueSuggestions.enabled', () => { + it('renames data.autocomplete.valueSuggestions.enabled to kql.autocomplete.valueSuggestions.enabled', () => { const config = { - unifiedSearch: { + data: { autocomplete: { valueSuggestions: { enabled: false, @@ -107,18 +107,18 @@ describe('Config Deprecations', () => { }, }; const { messages, migrated } = applyConfigDeprecations(cloneDeep(config)); - expect(migrated.unifiedSearch?.autocomplete.valueSuggestions.enabled).not.toBeDefined(); + expect(migrated.data?.autocomplete.valueSuggestions.enabled).not.toBeDefined(); expect(migrated.kql.autocomplete.valueSuggestions.enabled).toEqual(false); expect(messages).toMatchInlineSnapshot(` Array [ - "Setting \\"unifiedSearch.autocomplete.valueSuggestions.enabled\\" has been replaced by \\"kql.autocomplete.valueSuggestions.enabled\\"", + "Setting \\"data.autocomplete.valueSuggestions.enabled\\" has been replaced by \\"kql.autocomplete.valueSuggestions.enabled\\"", ] `); }); - it('renames unifiedSearch.autocomplete.valueSuggestions.tiers to kql.autocomplete.valueSuggestions.tiers', () => { + it('renames data.autocomplete.valueSuggestions.tiers to kql.autocomplete.valueSuggestions.tiers', () => { const config = { - unifiedSearch: { + data: { autocomplete: { valueSuggestions: { tiers: [], @@ -127,11 +127,11 @@ describe('Config Deprecations', () => { }, }; const { messages, migrated } = applyConfigDeprecations(cloneDeep(config)); - expect(migrated.unifiedSearch?.autocomplete.valueSuggestions.tiers).not.toBeDefined(); + expect(migrated.data?.autocomplete.valueSuggestions.tiers).not.toBeDefined(); expect(migrated.kql.autocomplete.valueSuggestions.tiers).toEqual([]); expect(messages).toMatchInlineSnapshot(` Array [ - "Setting \\"unifiedSearch.autocomplete.valueSuggestions.tiers\\" has been replaced by \\"kql.autocomplete.valueSuggestions.tiers\\"", + "Setting \\"data.autocomplete.valueSuggestions.tiers\\" has been replaced by \\"kql.autocomplete.valueSuggestions.tiers\\"", ] `); }); diff --git a/src/platform/plugins/shared/kql/server/config_deprecations.ts b/src/platform/plugins/shared/kql/server/config_deprecations.ts index cb126b966a3f6..1029f6ea05f63 100644 --- a/src/platform/plugins/shared/kql/server/config_deprecations.ts +++ b/src/platform/plugins/shared/kql/server/config_deprecations.ts @@ -12,6 +12,11 @@ import type { ConfigDeprecationProvider } from '@kbn/core/server'; export const autocompleteConfigDeprecationProvider: ConfigDeprecationProvider = ({ renameFromRoot, }) => [ + renameFromRoot( + 'data.autocomplete.valueSuggestions.terminateAfter', + 'kql.autocomplete.valueSuggestions.terminateAfter', + { level: 'warning' } + ), renameFromRoot( 'unifiedSearch.autocomplete.valueSuggestions.terminateAfter', 'kql.autocomplete.valueSuggestions.terminateAfter', @@ -22,6 +27,18 @@ export const autocompleteConfigDeprecationProvider: ConfigDeprecationProvider = 'kql.autocomplete.valueSuggestions.terminateAfter', { level: 'warning' } ), + renameFromRoot( + 'unifiedSearch.autocomplete.valueSuggestions.terminateAfter', + 'kql.autocomplete.valueSuggestions.terminateAfter', + { level: 'warning' } + ), + renameFromRoot( + 'data.autocomplete.valueSuggestions.timeout', + 'kql.autocomplete.valueSuggestions.timeout', + { + level: 'warning', + } + ), renameFromRoot( 'unifiedSearch.autocomplete.valueSuggestions.timeout', 'kql.autocomplete.valueSuggestions.timeout', @@ -32,6 +49,20 @@ export const autocompleteConfigDeprecationProvider: ConfigDeprecationProvider = renameFromRoot('kibana.autocompleteTimeout', 'kql.autocomplete.valueSuggestions.timeout', { level: 'warning', }), + renameFromRoot( + 'unifiedSearch.autocomplete.valueSuggestions.timeout', + 'kql.autocomplete.valueSuggestions.timeout', + { + level: 'warning', + } + ), + renameFromRoot( + 'data.autocomplete.querySuggestions.enabled', + 'kql.autocomplete.querySuggestions.enabled', + { + level: 'warning', + } + ), renameFromRoot( 'unifiedSearch.autocomplete.querySuggestions.enabled', 'kql.autocomplete.querySuggestions.enabled', @@ -39,6 +70,13 @@ export const autocompleteConfigDeprecationProvider: ConfigDeprecationProvider = level: 'warning', } ), + renameFromRoot( + 'data.autocomplete.valueSuggestions.enabled', + 'kql.autocomplete.valueSuggestions.enabled', + { + level: 'warning', + } + ), renameFromRoot( 'unifiedSearch.autocomplete.valueSuggestions.enabled', 'kql.autocomplete.valueSuggestions.enabled', @@ -46,6 +84,13 @@ export const autocompleteConfigDeprecationProvider: ConfigDeprecationProvider = level: 'warning', } ), + renameFromRoot( + 'data.autocomplete.valueSuggestions.tiers', + 'kql.autocomplete.valueSuggestions.tiers', + { + level: 'warning', + } + ), renameFromRoot( 'unifiedSearch.autocomplete.valueSuggestions.tiers', 'kql.autocomplete.valueSuggestions.tiers', From e23c55ae06f258ce65c31f384f9d688bc78aae0d Mon Sep 17 00:00:00 2001 From: Rudolf Meijering Date: Wed, 7 Jan 2026 15:50:50 +0100 Subject: [PATCH 53/60] POC: read kql plugin config from unifiedSearch configPath --- config/kibana.yml | 4 +- src/platform/plugins/shared/kql/kibana.jsonc | 1 + .../kql/server/config_deprecations.test.ts | 138 ------------------ .../shared/kql/server/config_deprecations.ts | 101 ------------- .../plugins/shared/kql/server/index.ts | 3 - .../shared/unified_search/kibana.jsonc | 2 +- 6 files changed, 4 insertions(+), 245 deletions(-) delete mode 100644 src/platform/plugins/shared/kql/server/config_deprecations.test.ts delete mode 100644 src/platform/plugins/shared/kql/server/config_deprecations.ts diff --git a/config/kibana.yml b/config/kibana.yml index 5c0ec35aa8ff5..ea8c763487aab 100644 --- a/config/kibana.yml +++ b/config/kibana.yml @@ -172,8 +172,8 @@ # =================== Search Autocomplete =================== # Time in milliseconds to wait for autocomplete suggestions from Elasticsearch. # This value must be a whole number greater than zero. Defaults to 1000ms -#kql.autocomplete.valueSuggestions.timeout: 1000 +#unifiedSearch.autocomplete.valueSuggestions.timeout: 1000 # Maximum number of documents loaded by each shard to generate autocomplete suggestions. # This value must be a whole number greater than zero. Defaults to 100_000 -#kql.autocomplete.valueSuggestions.terminateAfter: 100000 +#unifiedSearch.autocomplete.valueSuggestions.terminateAfter: 100000 diff --git a/src/platform/plugins/shared/kql/kibana.jsonc b/src/platform/plugins/shared/kql/kibana.jsonc index 6ed9af0807351..e8329ec11c342 100644 --- a/src/platform/plugins/shared/kql/kibana.jsonc +++ b/src/platform/plugins/shared/kql/kibana.jsonc @@ -6,6 +6,7 @@ "visibility": "shared", "plugin": { "id": "kql", + "configPath": "unifiedSearch", "server": true, "browser": true, "optionalPlugins": [ diff --git a/src/platform/plugins/shared/kql/server/config_deprecations.test.ts b/src/platform/plugins/shared/kql/server/config_deprecations.test.ts deleted file mode 100644 index 3c92da2baf130..0000000000000 --- a/src/platform/plugins/shared/kql/server/config_deprecations.test.ts +++ /dev/null @@ -1,138 +0,0 @@ -/* - * 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 { cloneDeep } from 'lodash'; - -import { applyDeprecations, configDeprecationFactory } from '@kbn/config'; -import { configDeprecationsMock } from '@kbn/core/server/mocks'; - -import { autocompleteConfigDeprecationProvider } from './config_deprecations'; - -const deprecationContext = configDeprecationsMock.createContext(); - -const applyConfigDeprecations = (settings: Record = {}) => { - const deprecations = autocompleteConfigDeprecationProvider(configDeprecationFactory); - const deprecationMessages: string[] = []; - const migrated = applyDeprecations( - settings, - deprecations.map((deprecation) => ({ - deprecation, - path: '', - context: deprecationContext, - })), - () => - ({ message }) => - deprecationMessages.push(message) - ); - return { - messages: deprecationMessages, - migrated: migrated.config, - }; -}; - -describe('Config Deprecations', () => { - it('does not report deprecations for default configurationc', () => { - const configFirstStep = { data: { autocomplete: { valueSuggestions: {} } } }; - const { messages, migrated } = applyConfigDeprecations(cloneDeep(configFirstStep)); - expect(migrated).toEqual(configFirstStep); - expect(messages).toHaveLength(0); - }); - - it('renames kibana.autocompleteTerminateAfter to kql.autocomplete.valueSuggestions.terminateAfter', () => { - const config = { - kibana: { - autocompleteTerminateAfter: 123, - }, - }; - const { messages, migrated } = applyConfigDeprecations(cloneDeep(config)); - expect(migrated.kibana?.autocompleteTerminateAfter).not.toBeDefined(); - expect(migrated.kql.autocomplete.valueSuggestions.terminateAfter).toEqual(123); - expect(messages).toMatchInlineSnapshot(` - Array [ - "Setting \\"kibana.autocompleteTerminateAfter\\" has been replaced by \\"kql.autocomplete.valueSuggestions.terminateAfter\\"", - ] - `); - }); - - it('renames kibana.autocompleteTimeout to kql.autocomplete.valueSuggestions.timeout', () => { - const config = { - kibana: { - autocompleteTimeout: 123, - }, - }; - const { messages, migrated } = applyConfigDeprecations(cloneDeep(config)); - expect(migrated.kibana?.autocompleteTimeout).not.toBeDefined(); - expect(migrated.kql.autocomplete.valueSuggestions.timeout).toEqual(123); - expect(messages).toMatchInlineSnapshot(` - Array [ - "Setting \\"kibana.autocompleteTimeout\\" has been replaced by \\"kql.autocomplete.valueSuggestions.timeout\\"", - ] - `); - }); - - it('renames data.autocomplete.querySuggestions.enabled to kql.autocomplete.querySuggestions.enabled', () => { - const config = { - data: { - autocomplete: { - querySuggestions: { - enabled: false, - }, - }, - }, - }; - const { messages, migrated } = applyConfigDeprecations(cloneDeep(config)); - expect(migrated.data?.autocomplete.querySuggestions.enabled).not.toBeDefined(); - expect(migrated.kql.autocomplete.querySuggestions.enabled).toEqual(false); - expect(messages).toMatchInlineSnapshot(` - Array [ - "Setting \\"data.autocomplete.querySuggestions.enabled\\" has been replaced by \\"kql.autocomplete.querySuggestions.enabled\\"", - ] - `); - }); - - it('renames data.autocomplete.valueSuggestions.enabled to kql.autocomplete.valueSuggestions.enabled', () => { - const config = { - data: { - autocomplete: { - valueSuggestions: { - enabled: false, - }, - }, - }, - }; - const { messages, migrated } = applyConfigDeprecations(cloneDeep(config)); - expect(migrated.data?.autocomplete.valueSuggestions.enabled).not.toBeDefined(); - expect(migrated.kql.autocomplete.valueSuggestions.enabled).toEqual(false); - expect(messages).toMatchInlineSnapshot(` - Array [ - "Setting \\"data.autocomplete.valueSuggestions.enabled\\" has been replaced by \\"kql.autocomplete.valueSuggestions.enabled\\"", - ] - `); - }); - - it('renames data.autocomplete.valueSuggestions.tiers to kql.autocomplete.valueSuggestions.tiers', () => { - const config = { - data: { - autocomplete: { - valueSuggestions: { - tiers: [], - }, - }, - }, - }; - const { messages, migrated } = applyConfigDeprecations(cloneDeep(config)); - expect(migrated.data?.autocomplete.valueSuggestions.tiers).not.toBeDefined(); - expect(migrated.kql.autocomplete.valueSuggestions.tiers).toEqual([]); - expect(messages).toMatchInlineSnapshot(` - Array [ - "Setting \\"data.autocomplete.valueSuggestions.tiers\\" has been replaced by \\"kql.autocomplete.valueSuggestions.tiers\\"", - ] - `); - }); -}); diff --git a/src/platform/plugins/shared/kql/server/config_deprecations.ts b/src/platform/plugins/shared/kql/server/config_deprecations.ts deleted file mode 100644 index 1029f6ea05f63..0000000000000 --- a/src/platform/plugins/shared/kql/server/config_deprecations.ts +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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 type { ConfigDeprecationProvider } from '@kbn/core/server'; - -export const autocompleteConfigDeprecationProvider: ConfigDeprecationProvider = ({ - renameFromRoot, -}) => [ - renameFromRoot( - 'data.autocomplete.valueSuggestions.terminateAfter', - 'kql.autocomplete.valueSuggestions.terminateAfter', - { level: 'warning' } - ), - renameFromRoot( - 'unifiedSearch.autocomplete.valueSuggestions.terminateAfter', - 'kql.autocomplete.valueSuggestions.terminateAfter', - { level: 'warning' } - ), - renameFromRoot( - 'kibana.autocompleteTerminateAfter', - 'kql.autocomplete.valueSuggestions.terminateAfter', - { level: 'warning' } - ), - renameFromRoot( - 'unifiedSearch.autocomplete.valueSuggestions.terminateAfter', - 'kql.autocomplete.valueSuggestions.terminateAfter', - { level: 'warning' } - ), - renameFromRoot( - 'data.autocomplete.valueSuggestions.timeout', - 'kql.autocomplete.valueSuggestions.timeout', - { - level: 'warning', - } - ), - renameFromRoot( - 'unifiedSearch.autocomplete.valueSuggestions.timeout', - 'kql.autocomplete.valueSuggestions.timeout', - { - level: 'warning', - } - ), - renameFromRoot('kibana.autocompleteTimeout', 'kql.autocomplete.valueSuggestions.timeout', { - level: 'warning', - }), - renameFromRoot( - 'unifiedSearch.autocomplete.valueSuggestions.timeout', - 'kql.autocomplete.valueSuggestions.timeout', - { - level: 'warning', - } - ), - renameFromRoot( - 'data.autocomplete.querySuggestions.enabled', - 'kql.autocomplete.querySuggestions.enabled', - { - level: 'warning', - } - ), - renameFromRoot( - 'unifiedSearch.autocomplete.querySuggestions.enabled', - 'kql.autocomplete.querySuggestions.enabled', - { - level: 'warning', - } - ), - renameFromRoot( - 'data.autocomplete.valueSuggestions.enabled', - 'kql.autocomplete.valueSuggestions.enabled', - { - level: 'warning', - } - ), - renameFromRoot( - 'unifiedSearch.autocomplete.valueSuggestions.enabled', - 'kql.autocomplete.valueSuggestions.enabled', - { - level: 'warning', - } - ), - renameFromRoot( - 'data.autocomplete.valueSuggestions.tiers', - 'kql.autocomplete.valueSuggestions.tiers', - { - level: 'warning', - } - ), - renameFromRoot( - 'unifiedSearch.autocomplete.valueSuggestions.tiers', - 'kql.autocomplete.valueSuggestions.tiers', - { - level: 'warning', - } - ), -]; diff --git a/src/platform/plugins/shared/kql/server/index.ts b/src/platform/plugins/shared/kql/server/index.ts index cec2da4c62123..9f42b31e4205c 100644 --- a/src/platform/plugins/shared/kql/server/index.ts +++ b/src/platform/plugins/shared/kql/server/index.ts @@ -12,8 +12,6 @@ import type { ConfigSchema } from './config'; import { configSchema } from './config'; import type { KQLServerPlugin, KQLServerPluginSetup, KQLServerPluginStart } from './plugin'; -import { autocompleteConfigDeprecationProvider } from './config_deprecations'; - /** * Static code to be shared externally * @public @@ -28,7 +26,6 @@ export type { KQLServerPluginSetup as PluginSetup, KQLServerPluginStart as Plugi export type { KQLServerPlugin as Plugin }; export const config: PluginConfigDescriptor = { - deprecations: autocompleteConfigDeprecationProvider, exposeToBrowser: { autocomplete: true, }, diff --git a/src/platform/plugins/shared/unified_search/kibana.jsonc b/src/platform/plugins/shared/unified_search/kibana.jsonc index abfb7b038b8c1..b787ecdc80e1c 100644 --- a/src/platform/plugins/shared/unified_search/kibana.jsonc +++ b/src/platform/plugins/shared/unified_search/kibana.jsonc @@ -15,7 +15,7 @@ "browser": true, "server": false, "configPath": [ - "unifiedSearch" + "unifiedSearchDeprecatedSettings" ], "requiredPlugins": [ "dataViews", From 7b9ca3ede206fa8648435029b0c2bb378b85789b Mon Sep 17 00:00:00 2001 From: Rudolf Meijering Date: Wed, 7 Jan 2026 15:51:22 +0100 Subject: [PATCH 54/60] TEMP: enable unifiedSearch config in kibana.yml to test --- config/kibana.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/kibana.yml b/config/kibana.yml index ea8c763487aab..f0864e87b6f49 100644 --- a/config/kibana.yml +++ b/config/kibana.yml @@ -172,7 +172,7 @@ # =================== Search Autocomplete =================== # Time in milliseconds to wait for autocomplete suggestions from Elasticsearch. # This value must be a whole number greater than zero. Defaults to 1000ms -#unifiedSearch.autocomplete.valueSuggestions.timeout: 1000 +unifiedSearch.autocomplete.valueSuggestions.timeout: 1000 # Maximum number of documents loaded by each shard to generate autocomplete suggestions. # This value must be a whole number greater than zero. Defaults to 100_000 From 9ea17f7021d9fd4dedb1bc8c91f39af40b5081d6 Mon Sep 17 00:00:00 2001 From: Stratoula Date: Wed, 7 Jan 2026 16:09:15 +0100 Subject: [PATCH 55/60] Cleanup --- .../configuration-reference/general-settings.md | 4 ++-- .../docker_generator/resources/base/bin/kibana-docker | 5 ----- src/platform/plugins/shared/kql/kibana.jsonc | 2 ++ .../test_suites/core_plugins/rendering.ts | 10 +++++----- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/docs/reference/configuration-reference/general-settings.md b/docs/reference/configuration-reference/general-settings.md index 4f2aed779c77c..a0f67e9248772 100644 --- a/docs/reference/configuration-reference/general-settings.md +++ b/docs/reference/configuration-reference/general-settings.md @@ -517,10 +517,10 @@ $$$settings-telemetry-optIn$$$ `telemetry.optIn` This setting can be changed at any time in [Advanced Settings](/reference/advanced-settings.md). To prevent users from changing it, set [`telemetry.allowChangingOptInStatus`](#telemetry-allowChangingOptInStatus) to `false`. **Default: `true`** -`kql.autocomplete.valueSuggestions.timeout` ![logo cloud](https://doc-icons.s3.us-east-2.amazonaws.com/logo_cloud.svg "Supported on {{ech}}") +`unifiedSearch.autocomplete.valueSuggestions.timeout` ![logo cloud](https://doc-icons.s3.us-east-2.amazonaws.com/logo_cloud.svg "Supported on {{ech}}") : Time in milliseconds to wait for autocomplete suggestions from {{es}}. This value must be a whole number greater than zero. **Default: `"1000"`** -`kql.autocomplete.valueSuggestions.terminateAfter` ![logo cloud](https://doc-icons.s3.us-east-2.amazonaws.com/logo_cloud.svg "Supported on {{ech}}") +`unifiedSearch.autocomplete.valueSuggestions.terminateAfter` ![logo cloud](https://doc-icons.s3.us-east-2.amazonaws.com/logo_cloud.svg "Supported on {{ech}}") : Maximum number of documents loaded by each shard to generate autocomplete suggestions. This value must be a whole number greater than zero. **Default: `"100000"`** ::::{note} diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker index df39b75048e98..8e6192d910c34 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker @@ -58,11 +58,6 @@ kibana_vars=( unifiedSearch.autocomplete.querySuggestions.enabled unifiedSearch.autocomplete.valueSuggestions.enabled unifiedSearch.autocomplete.valueSuggestions.tiers - kql.autocomplete.valueSuggestions.terminateAfter - kql.autocomplete.valueSuggestions.timeout - kql.autocomplete.querySuggestions.enabled - kql.autocomplete.valueSuggestions.enabled - kql.autocomplete.valueSuggestions.tiers elasticsearch.customHeaders elasticsearch.hosts elasticsearch.logQueries diff --git a/src/platform/plugins/shared/kql/kibana.jsonc b/src/platform/plugins/shared/kql/kibana.jsonc index e8329ec11c342..9533fbbc55c06 100644 --- a/src/platform/plugins/shared/kql/kibana.jsonc +++ b/src/platform/plugins/shared/kql/kibana.jsonc @@ -6,6 +6,8 @@ "visibility": "shared", "plugin": { "id": "kql", + // We are getting config from unifiedSearch plugin + // that way we don't need to change the configs for users "configPath": "unifiedSearch", "server": true, "browser": true, diff --git a/src/platform/test/plugin_functional/test_suites/core_plugins/rendering.ts b/src/platform/test/plugin_functional/test_suites/core_plugins/rendering.ts index 3383bd74fe466..3e683bf985d98 100644 --- a/src/platform/test/plugin_functional/test_suites/core_plugins/rendering.ts +++ b/src/platform/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -101,11 +101,11 @@ export default function ({ getService }: PluginFunctionalProviderContext) { 'console.autocompleteDefinitions.endpointsAvailability (stack?|serverless?)', 'console.ui.enabled (boolean?)', 'console.ui.embeddedEnabled (boolean?)', - 'kql.autocomplete.querySuggestions.enabled (boolean?)', - 'kql.autocomplete.valueSuggestions.enabled (boolean?)', - 'kql.autocomplete.valueSuggestions.terminateAfter (duration?)', - 'kql.autocomplete.valueSuggestions.tiers (array?)', - 'kql.autocomplete.valueSuggestions.timeout (duration?)', + 'unifiedSearch.autocomplete.querySuggestions.enabled (boolean?)', + 'unifiedSearch.autocomplete.valueSuggestions.enabled (boolean?)', + 'unifiedSearch.autocomplete.valueSuggestions.terminateAfter (duration?)', + 'unifiedSearch.autocomplete.valueSuggestions.tiers (array?)', + 'unifiedSearch.autocomplete.valueSuggestions.timeout (duration?)', 'data.search.aggs.shardDelay.enabled (boolean?)', 'data.search.asyncSearch.batchedReduceSize (number?)', 'data.search.asyncSearch.keepAlive (duration?)', From 7ad467d6c8846219fa50c70eff323bf1a13c3025 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 7 Jan 2026 15:23:14 +0000 Subject: [PATCH 56/60] Changes from node scripts/lint_ts_projects --fix --- src/platform/plugins/shared/kql/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/src/platform/plugins/shared/kql/tsconfig.json b/src/platform/plugins/shared/kql/tsconfig.json index 460f492bb2eaf..b5ea0c3e0237a 100644 --- a/src/platform/plugins/shared/kql/tsconfig.json +++ b/src/platform/plugins/shared/kql/tsconfig.json @@ -25,7 +25,6 @@ "@kbn/kibana-utils-plugin", "@kbn/utility-types-jest", "@kbn/config-schema", - "@kbn/config", ], "exclude": [ "target/**/*", From 33354497209e5fb2f55091cdc6612f33ae7f1246 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 7 Jan 2026 15:34:49 +0000 Subject: [PATCH 57/60] Changes from node scripts/regenerate_moon_projects.js --update --- src/platform/plugins/shared/kql/moon.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/src/platform/plugins/shared/kql/moon.yml b/src/platform/plugins/shared/kql/moon.yml index 7d8a0d71e9437..b4cd6af95dd4a 100644 --- a/src/platform/plugins/shared/kql/moon.yml +++ b/src/platform/plugins/shared/kql/moon.yml @@ -33,7 +33,6 @@ dependsOn: - '@kbn/kibana-utils-plugin' - '@kbn/utility-types-jest' - '@kbn/config-schema' - - '@kbn/config' tags: - plugin - prod From 25530b0d04d61ee572b13655fd0c4fff4011cdbe Mon Sep 17 00:00:00 2001 From: mbondyra Date: Tue, 13 Jan 2026 14:07:31 +0100 Subject: [PATCH 58/60] use reusable useMemoCss --- .../filter_button_group.tsx | 2 +- .../query_string_input.styles.tsx | 2 +- .../typeahead/suggestion_component.tsx | 3 +- .../typeahead/suggestions_component.tsx | 3 +- .../public/components/utils/use_memo_css.ts | 52 ------------------- .../filter_bar/filter_item/filter_item.tsx | 2 +- .../saved_query_management_list.tsx | 2 +- .../unified_search/public/use_memo_css.ts | 52 ------------------- 8 files changed, 6 insertions(+), 112 deletions(-) delete mode 100644 src/platform/plugins/shared/kql/public/components/utils/use_memo_css.ts delete mode 100644 src/platform/plugins/shared/unified_search/public/use_memo_css.ts diff --git a/src/platform/plugins/shared/kql/public/components/query_string_input/filter_button_group.tsx b/src/platform/plugins/shared/kql/public/components/query_string_input/filter_button_group.tsx index 36b11c04d65b0..3f28921a8a43d 100644 --- a/src/platform/plugins/shared/kql/public/components/query_string_input/filter_button_group.tsx +++ b/src/platform/plugins/shared/kql/public/components/query_string_input/filter_button_group.tsx @@ -13,7 +13,7 @@ import classNames from 'classnames'; import type { UseEuiTheme } from '@elastic/eui'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { css } from '@emotion/react'; -import { useMemoCss } from '../utils/use_memo_css'; +import { useMemoCss } from '@kbn/css-utils/public/use_memo_css'; interface Props { items: ReactNode[]; diff --git a/src/platform/plugins/shared/kql/public/components/query_string_input/query_string_input.styles.tsx b/src/platform/plugins/shared/kql/public/components/query_string_input/query_string_input.styles.tsx index 5b007ff25c8e0..fdeb769f4d2e3 100644 --- a/src/platform/plugins/shared/kql/public/components/query_string_input/query_string_input.styles.tsx +++ b/src/platform/plugins/shared/kql/public/components/query_string_input/query_string_input.styles.tsx @@ -11,7 +11,7 @@ import React from 'react'; import type { UseEuiTheme } from '@elastic/eui'; import { euiThemeVars } from '@kbn/ui-theme'; import { css } from '@emotion/react'; -import { useMemoCss } from '../utils/use_memo_css'; +import { useMemoCss } from '@kbn/css-utils/public/use_memo_css'; const queryStringInputStyles = { container: ({ euiTheme }: UseEuiTheme) => diff --git a/src/platform/plugins/shared/kql/public/components/typeahead/suggestion_component.tsx b/src/platform/plugins/shared/kql/public/components/typeahead/suggestion_component.tsx index 56f258fdd3cfc..1c95fdb1af443 100644 --- a/src/platform/plugins/shared/kql/public/components/typeahead/suggestion_component.tsx +++ b/src/platform/plugins/shared/kql/public/components/typeahead/suggestion_component.tsx @@ -12,8 +12,7 @@ import { EuiIcon, euiFontSize } from '@elastic/eui'; import classNames from 'classnames'; import React, { useCallback } from 'react'; import { css } from '@emotion/react'; -import type { EmotionStyles } from '../utils/use_memo_css'; -import { useMemoCss } from '../utils/use_memo_css'; +import { type EmotionStyles, useMemoCss } from '@kbn/css-utils/public/use_memo_css'; import type { QuerySuggestion } from '../../autocomplete'; import type { SuggestionOnClick, SuggestionOnMouseEnter } from './types'; diff --git a/src/platform/plugins/shared/kql/public/components/typeahead/suggestions_component.tsx b/src/platform/plugins/shared/kql/public/components/typeahead/suggestions_component.tsx index 86a0b514a45a0..c777a021ed371 100644 --- a/src/platform/plugins/shared/kql/public/components/typeahead/suggestions_component.tsx +++ b/src/platform/plugins/shared/kql/public/components/typeahead/suggestions_component.tsx @@ -16,8 +16,7 @@ import useRafState from 'react-use/lib/useRafState'; import type { UseEuiTheme } from '@elastic/eui'; import { euiShadow, euiShadowFlat } from '@elastic/eui'; import { css } from '@emotion/react'; -import type { EmotionStyles } from '../utils/use_memo_css'; -import { useMemoCss } from '../utils/use_memo_css'; +import { type EmotionStyles, useMemoCss } from '@kbn/css-utils/public/use_memo_css'; import type { QuerySuggestion } from '../../autocomplete'; import { SuggestionComponent } from './suggestion_component'; import { diff --git a/src/platform/plugins/shared/kql/public/components/utils/use_memo_css.ts b/src/platform/plugins/shared/kql/public/components/utils/use_memo_css.ts deleted file mode 100644 index b8da755f9de53..0000000000000 --- a/src/platform/plugins/shared/kql/public/components/utils/use_memo_css.ts +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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 { useMemo } from 'react'; -import type { CSSInterpolation } from '@emotion/css'; -import type { UseEuiTheme } from '@elastic/eui'; -import { useEuiTheme } from '@elastic/eui'; - -// TODO: Move to use @kbn/css-utils when available https://github.com/elastic/kibana/pull/223933 - -export type EmotionStyles = Record< - string, - CSSInterpolation | ((theme: UseEuiTheme) => CSSInterpolation) ->; - -type StaticEmotionStyles = Record; - -/** - * Custom hook to reduce boilerplate when working with Emotion styles that may depend on - * the EUI theme. - * - * Accepts a map of styles where each entry is either a static Emotion style (via `css`) - * or a function that returns styles based on the current `euiTheme`. - * - * It returns a memoized version of the style map with all values resolved to static - * Emotion styles, allowing components to use a clean and unified object for styling. - * - * This helps simplify component code by centralizing theme-aware style logic. - * - * Example usage: - * const componentStyles = { - * container: css({ overflow: hidden }), - * leftPane: ({ euiTheme }) => css({ paddingTop: euiTheme.size.m }), - * } - * const styles = useMemoCss(componentStyles); - */ -export const useMemoCss = (styleMap: EmotionStyles) => { - const euiThemeContext = useEuiTheme(); - const outputStyles = useMemo(() => { - return Object.entries(styleMap).reduce((acc, [key, value]) => { - acc[key] = typeof value === 'function' ? value(euiThemeContext) : value; - return acc; - }, {}); - }, [euiThemeContext, styleMap]); - return outputStyles; -}; diff --git a/src/platform/plugins/shared/unified_search/public/filter_bar/filter_item/filter_item.tsx b/src/platform/plugins/shared/unified_search/public/filter_bar/filter_item/filter_item.tsx index 55022a51f76aa..1395966a95621 100644 --- a/src/platform/plugins/shared/unified_search/public/filter_bar/filter_item/filter_item.tsx +++ b/src/platform/plugins/shared/unified_search/public/filter_bar/filter_item/filter_item.tsx @@ -24,8 +24,8 @@ import React, { useState, useEffect, useCallback } from 'react'; import { type DocLinksStart, type IUiSettingsClient } from '@kbn/core/public'; import type { DataView, DataViewsContract } from '@kbn/data-views-plugin/public'; import { css } from '@emotion/react'; +import { useMemoCss } from '@kbn/css-utils/public/use_memo_css'; import { getIndexPatternFromFilter, getDisplayValueFromFilter } from '@kbn/data-plugin/public'; -import { useMemoCss } from '../../use_memo_css'; import { FilterEditor } from '../filter_editor/filter_editor'; import { FilterView } from '../filter_view'; import type { FilterPanelOption } from '../../types'; diff --git a/src/platform/plugins/shared/unified_search/public/saved_query_management/saved_query_management_list.tsx b/src/platform/plugins/shared/unified_search/public/saved_query_management/saved_query_management_list.tsx index 128a693e26e81..2708630dce23e 100644 --- a/src/platform/plugins/shared/unified_search/public/saved_query_management/saved_query_management_list.tsx +++ b/src/platform/plugins/shared/unified_search/public/saved_query_management/saved_query_management_list.tsx @@ -32,6 +32,7 @@ import { i18n } from '@kbn/i18n'; import type { RefObject } from 'react'; import React, { useCallback, useState, useRef, useEffect, useMemo } from 'react'; import { renderToStaticMarkup } from 'react-dom/server'; +import { useMemoCss } from '@kbn/css-utils/public/use_memo_css'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import type { SavedQuery, SavedQueryService } from '@kbn/data-plugin/public'; import type { SavedQueryAttributes } from '@kbn/data-plugin/common'; @@ -39,7 +40,6 @@ import { debounce } from 'lodash'; import useLatest from 'react-use/lib/useLatest'; import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import { css } from '@emotion/react'; -import { useMemoCss } from '../use_memo_css'; import type { IUnifiedSearchPluginServices } from '../types'; import { strings as queryBarMenuPanelsStrings } from '../query_string_input/query_bar_menu_panels'; import { PanelTitle } from '../query_string_input/panel_title'; diff --git a/src/platform/plugins/shared/unified_search/public/use_memo_css.ts b/src/platform/plugins/shared/unified_search/public/use_memo_css.ts deleted file mode 100644 index b8da755f9de53..0000000000000 --- a/src/platform/plugins/shared/unified_search/public/use_memo_css.ts +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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 { useMemo } from 'react'; -import type { CSSInterpolation } from '@emotion/css'; -import type { UseEuiTheme } from '@elastic/eui'; -import { useEuiTheme } from '@elastic/eui'; - -// TODO: Move to use @kbn/css-utils when available https://github.com/elastic/kibana/pull/223933 - -export type EmotionStyles = Record< - string, - CSSInterpolation | ((theme: UseEuiTheme) => CSSInterpolation) ->; - -type StaticEmotionStyles = Record; - -/** - * Custom hook to reduce boilerplate when working with Emotion styles that may depend on - * the EUI theme. - * - * Accepts a map of styles where each entry is either a static Emotion style (via `css`) - * or a function that returns styles based on the current `euiTheme`. - * - * It returns a memoized version of the style map with all values resolved to static - * Emotion styles, allowing components to use a clean and unified object for styling. - * - * This helps simplify component code by centralizing theme-aware style logic. - * - * Example usage: - * const componentStyles = { - * container: css({ overflow: hidden }), - * leftPane: ({ euiTheme }) => css({ paddingTop: euiTheme.size.m }), - * } - * const styles = useMemoCss(componentStyles); - */ -export const useMemoCss = (styleMap: EmotionStyles) => { - const euiThemeContext = useEuiTheme(); - const outputStyles = useMemo(() => { - return Object.entries(styleMap).reduce((acc, [key, value]) => { - acc[key] = typeof value === 'function' ? value(euiThemeContext) : value; - return acc; - }, {}); - }, [euiThemeContext, styleMap]); - return outputStyles; -}; From 525bc9537440ce629bfff358bb4543ce553a1654 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 13 Jan 2026 13:37:40 +0000 Subject: [PATCH 59/60] Changes from node scripts/lint_ts_projects --fix --- src/platform/plugins/shared/kql/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/platform/plugins/shared/kql/tsconfig.json b/src/platform/plugins/shared/kql/tsconfig.json index b5ea0c3e0237a..89627ea7ccb59 100644 --- a/src/platform/plugins/shared/kql/tsconfig.json +++ b/src/platform/plugins/shared/kql/tsconfig.json @@ -25,6 +25,7 @@ "@kbn/kibana-utils-plugin", "@kbn/utility-types-jest", "@kbn/config-schema", + "@kbn/css-utils", ], "exclude": [ "target/**/*", From 66a1bf9e01b2da846829395fa485bb53fdfdb625 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 13 Jan 2026 13:49:17 +0000 Subject: [PATCH 60/60] Changes from node scripts/regenerate_moon_projects.js --update --- src/platform/plugins/shared/kql/moon.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/platform/plugins/shared/kql/moon.yml b/src/platform/plugins/shared/kql/moon.yml index b4cd6af95dd4a..e13c7d5e074a9 100644 --- a/src/platform/plugins/shared/kql/moon.yml +++ b/src/platform/plugins/shared/kql/moon.yml @@ -33,6 +33,7 @@ dependsOn: - '@kbn/kibana-utils-plugin' - '@kbn/utility-types-jest' - '@kbn/config-schema' + - '@kbn/css-utils' tags: - plugin - prod