diff --git a/docs/api/dashboard-api.asciidoc b/docs/api/dashboard-api.asciidoc index 60df46b899f14..60360e406e372 100644 --- a/docs/api/dashboard-api.asciidoc +++ b/docs/api/dashboard-api.asciidoc @@ -1,7 +1,7 @@ [[dashboard-api]] == Import and export dashboard APIs -deprecated::[7.15.0,Both of these APIs have been deprecated in favor of the {api-kibana}/group/endpoint-saved-objects[saved objects API].] +deprecated::[7.15.0,Both of these APIs have been deprecated and will be removed in 9.0.0] Import and export dashboards with the corresponding saved objects, such as visualizations, saved searches, and data views. @@ -16,4 +16,4 @@ The following import and export dashboard APIs are available: * <> to export dashboards and corresponding saved objects include::dashboard/import-dashboard.asciidoc[] -include::dashboard/export-dashboard.asciidoc[] \ No newline at end of file +include::dashboard/export-dashboard.asciidoc[] diff --git a/docs/api/data-views.asciidoc b/docs/api/data-views.asciidoc index 62d1546bdfd45..f15d7d40f49b3 100644 --- a/docs/api/data-views.asciidoc +++ b/docs/api/data-views.asciidoc @@ -8,5 +8,3 @@ For the latest details, refer to {api-kibana}/group/endpoint-data-views[data vie WARNING: Do not write documents directly to the `.kibana` index. When you write directly to the `.kibana` index, the data becomes corrupted and permanently breaks future {kib} versions. -WARNING: Use the data views APIs for managing data views instead of lower-level <>. - diff --git a/docs/api/osquery-manager.asciidoc b/docs/api/osquery-manager.asciidoc index f1c6f5cc664ee..2607bdad1f54f 100644 --- a/docs/api/osquery-manager.asciidoc +++ b/docs/api/osquery-manager.asciidoc @@ -3,7 +3,7 @@ experimental[] Run live queries, manage packs and saved queries -WARNING: Use the osquery manager APIs for managing packs and saved queries instead of lower-level <>. +Use the osquery manager APIs for managing packs and saved queries. The following osquery manager APIs are available: diff --git a/docs/api/saved-objects.asciidoc b/docs/api/saved-objects.asciidoc index b8e7f00f54872..3e0e206b468e7 100644 --- a/docs/api/saved-objects.asciidoc +++ b/docs/api/saved-objects.asciidoc @@ -1,9 +1,4 @@ [[saved-objects-api]] == Saved objects APIs -Manage {kib} saved objects, including dashboards, visualizations, and more. - -For the latest details, refer to the {api-kibana}/group/endpoint-saved-objects[saved object API]. - -WARNING: Do not write documents directly to the `.kibana` index. When you write directly -to the `.kibana` index, the data becomes corrupted and permanently breaks future {kib} versions. +For the latest details, refer to the {api-kibana}/group/endpoint-saved-objects[saved objects API]. diff --git a/docs/api/short-urls.asciidoc b/docs/api/short-urls.asciidoc index ded639c897f3f..0b47efa83e02f 100644 --- a/docs/api/short-urls.asciidoc +++ b/docs/api/short-urls.asciidoc @@ -1,7 +1,14 @@ [[short-urls-api]] == Short URLs APIs -Manage {kib} short URLs. +experimental[] Manage {kib} short URLs. + +The following short urls APIs are available: + +* <> +* <> +* <> +* <> include::short-urls/create-short-url.asciidoc[] include::short-urls/get-short-url.asciidoc[] diff --git a/docs/developer/contributing/development-functional-tests.asciidoc b/docs/developer/contributing/development-functional-tests.asciidoc index 88163ebb6804b..23d43480eb090 100644 --- a/docs/developer/contributing/development-functional-tests.asciidoc +++ b/docs/developer/contributing/development-functional-tests.asciidoc @@ -203,7 +203,7 @@ Tests should run at the positive security boundary condition, meaning that they The functional UI tests now default to logging in with a user named `test_user` and the roles of this user can be changed dynamically without logging in and out. -In order to achieve this a new service was introduced called `createTestUserService` (see `packages/kbn-ftr-common-functional-ui-services/services/security/test_user.ts`). The purpose of this test user service is to create roles defined in the test config files and setRoles() or restoreDefaults(). +In order to achieve this a new service was introduced called `createTestUserService` (see `test/common/services/security/test_user.ts`). The purpose of this test user service is to create roles defined in the test config files and setRoles() or restoreDefaults(). An example of how to set the role like how its defined below: @@ -366,14 +366,14 @@ await testSubjects.click(‘containerButton’); ** `find.allByCssSelector()` **retry:**::: -// * Source: {kibana-blob}packages/kbn-ftr-common-functional-services/services/retry/retry.ts[packages/kbn-ftr-common-functional-services/services/retry/retry.ts] +// * Source: {kibana-blob}test/common/services/retry/retry.ts[test/common/services/retry/retry.ts] * Helpers for retrying operations * Popular methods: ** `retry.try(fn, onFailureBlock)` - Execute `fn` in a loop until it succeeds or the default timeout elapses. The optional `onFailureBlock` is executed before each retry attempt. ** `retry.tryForTime(ms, fn, onFailureBlock)` - Execute `fn` in a loop until it succeeds or `ms` milliseconds elapses. The optional `onFailureBlock` is executed before each retry attempt. **kibanaServer:**::: -// * Source: {kibana-blob}packages/kbn-ftr-common-functional-services/services/kibana_server/kibana_server.ts[packages/kbn-ftr-common-functional-services/services/kibana_server/kibana_server.ts] +// * Source: {kibana-blob}test/common/services/kibana_server/kibana_server.js[test/common/services/kibana_server/kibana_server.js] * Helpers for interacting with {kib}'s server * Commonly used methods: ** `kibanaServer.uiSettings.update()` @@ -381,7 +381,7 @@ await testSubjects.click(‘containerButton’); ** `kibanaServer.status.getOverallState()` **esArchiver:**::: -// * Source: {kibana-blob}packages/kbn-ftr-common-functional-services/services/es_archiver.ts[packages/kbn-ftr-common-functional-services/services/es_archiver.ts] +// * Source: {kibana-blob}test/common/services/es_archiver.ts[test/common/services/es_archiver.ts] * Load/unload archives created with the `esArchiver` * Popular methods: ** `esArchiver.load(path)` @@ -393,7 +393,7 @@ Full list of services that are used in functional tests can be found here: {kiba **Low-level utilities:**::: * es -// ** Source: {kibana-blob}packages/kbn-ftr-common-functional-services/services/es.ts[packages/kbn-ftr-common-functional-services/services/es.ts] +// ** Source: {kibana-blob}test/common/services/es.ts[test/common/services/es.ts] ** {es} client ** Higher level options: `kibanaServer.uiSettings` or `esArchiver` * remote diff --git a/oas_docs/output/kibana.serverless.yaml b/oas_docs/output/kibana.serverless.yaml index 8d81681f3789e..363fc33d89a3e 100644 --- a/oas_docs/output/kibana.serverless.yaml +++ b/oas_docs/output/kibana.serverless.yaml @@ -19031,10 +19031,25 @@ tags: - name: Fleet uninstall tokens - description: Machine learning name: ml - - description: >- - Manage Kibana saved objects, including dashboards, visualizations, and - more. + - description: > + Export sets of saved objects that you want to import into {kib}, resolve + import errors, and rotate an encryption key for encrypted saved objects + with the saved objects APIs. + + + To manage a specific type of saved object, use the corresponding APIs. + + For example, use: + + + [Data views](../group/endpoint-data-views) + + + Warning: Do not write documents directly to the `.kibana` index. When you + write directly to the `.kibana` index, the data becomes corrupted and + permanently breaks future Kibana versions. name: saved objects + x-displayName: Saved objects - description: 'SLO APIs enable you to define, manage and track service-level objectives' name: slo - name: system diff --git a/oas_docs/output/kibana.yaml b/oas_docs/output/kibana.yaml index dfe69b9be3400..d5ad2469780bc 100644 --- a/oas_docs/output/kibana.yaml +++ b/oas_docs/output/kibana.yaml @@ -27592,10 +27592,30 @@ tags: - name: Fleet uninstall tokens - description: Machine learning name: ml - - description: >- - Manage Kibana saved objects, including dashboards, visualizations, and - more. + - description: > + Export sets of saved objects that you want to import into {kib}, resolve + import errors, and rotate an encryption key for encrypted saved objects + with the saved objects APIs. + + + To manage a specific type of saved object, use the corresponding APIs. + + For example, use: + + + * [Data views](../group/endpoint-data-views) + + * [Spaces](https://www.elastic.co/guide/en/kibana/current/spaces-api.html) + + * [Short + URLs](https://www.elastic.co/guide/en/kibana/current/short-urls-api.html) + + + Warning: Do not write documents directly to the `.kibana` index. When you + write directly to the `.kibana` index, the data becomes corrupted and + permanently breaks future Kibana versions. name: saved objects + x-displayName: Saved objects - description: 'SLO APIs enable you to define, manage and track service-level objectives' name: slo - name: system diff --git a/oas_docs/overlays/kibana.overlays.serverless.yaml b/oas_docs/overlays/kibana.overlays.serverless.yaml index dc21647813180..5d1064eecb37e 100644 --- a/oas_docs/overlays/kibana.overlays.serverless.yaml +++ b/oas_docs/overlays/kibana.overlays.serverless.yaml @@ -50,10 +50,6 @@ actions: description: Change displayName update: x-displayName: "Machine learning" - - target: '$.tags[?(@.name=="saved objects")]' - description: Change displayName - update: - x-displayName: "Saved objects" - target: '$.tags[?(@.name=="slo")]' description: Change displayName update: diff --git a/oas_docs/overlays/kibana.overlays.yaml b/oas_docs/overlays/kibana.overlays.yaml index 926550ca085b7..cd3b40eeda819 100644 --- a/oas_docs/overlays/kibana.overlays.yaml +++ b/oas_docs/overlays/kibana.overlays.yaml @@ -65,10 +65,6 @@ actions: description: Change displayName update: x-displayName: "Machine learning" - - target: '$.tags[?(@.name=="saved objects")]' - description: Change displayName - update: - x-displayName: "Saved objects" - target: '$.tags[?(@.name=="slo")]' description: Change displayName update: diff --git a/package.json b/package.json index 3bcbda6c44346..66469d798997d 100644 --- a/package.json +++ b/package.json @@ -1651,7 +1651,7 @@ "buildkite-test-collector": "^1.7.0", "callsites": "^3.1.0", "chance": "1.0.18", - "chromedriver": "^127.0.3", + "chromedriver": "^128.0.0", "clean-webpack-plugin": "^3.0.0", "cli-progress": "^3.12.0", "cli-table3": "^0.6.1", diff --git a/packages/core/saved-objects/docs/openapi/bundled.json b/packages/core/saved-objects/docs/openapi/bundled.json index 06010e44e1a19..36439a6b46749 100644 --- a/packages/core/saved-objects/docs/openapi/bundled.json +++ b/packages/core/saved-objects/docs/openapi/bundled.json @@ -20,7 +20,8 @@ "tags": [ { "name": "saved objects", - "description": "Manage Kibana saved objects, including dashboards, visualizations, and more." + "x-displayName": "Saved objects", + "description": "Export sets of saved objects that you want to import into {kib}, resolve import errors, and rotate an encryption key for encrypted saved objects with the saved objects APIs.\n\nTo manage a specific type of saved object, use the corresponding APIs.\nFor example, use:\n\n* [Data views](../group/endpoint-data-views)\n* [Spaces](https://www.elastic.co/guide/en/kibana/current/spaces-api.html)\n* [Short URLs](https://www.elastic.co/guide/en/kibana/current/short-urls-api.html)\n\nWarning: Do not write documents directly to the `.kibana` index. When you write directly to the `.kibana` index, the data becomes corrupted and permanently breaks future Kibana versions.\n" } ], "paths": { diff --git a/packages/core/saved-objects/docs/openapi/bundled.yaml b/packages/core/saved-objects/docs/openapi/bundled.yaml index d616131ac494e..130c1c8d0a252 100644 --- a/packages/core/saved-objects/docs/openapi/bundled.yaml +++ b/packages/core/saved-objects/docs/openapi/bundled.yaml @@ -12,7 +12,18 @@ servers: - url: / tags: - name: saved objects - description: Manage Kibana saved objects, including dashboards, visualizations, and more. + x-displayName: Saved objects + description: | + Export sets of saved objects that you want to import into {kib}, resolve import errors, and rotate an encryption key for encrypted saved objects with the saved objects APIs. + + To manage a specific type of saved object, use the corresponding APIs. + For example, use: + + * [Data views](../group/endpoint-data-views) + * [Spaces](https://www.elastic.co/guide/en/kibana/current/spaces-api.html) + * [Short URLs](https://www.elastic.co/guide/en/kibana/current/short-urls-api.html) + + Warning: Do not write documents directly to the `.kibana` index. When you write directly to the `.kibana` index, the data becomes corrupted and permanently breaks future Kibana versions. paths: /api/encrypted_saved_objects/_rotate_key: post: diff --git a/packages/core/saved-objects/docs/openapi/bundled_serverless.json b/packages/core/saved-objects/docs/openapi/bundled_serverless.json index 6bc767088bbec..61a2671c878cf 100644 --- a/packages/core/saved-objects/docs/openapi/bundled_serverless.json +++ b/packages/core/saved-objects/docs/openapi/bundled_serverless.json @@ -25,7 +25,8 @@ "tags": [ { "name": "saved objects", - "description": "Manage Kibana saved objects, including dashboards, visualizations, and more." + "x-displayName": "Saved objects", + "description": "Export sets of saved objects that you want to import into {kib}, resolve import errors, and rotate an encryption key for encrypted saved objects with the saved objects APIs.\n\nTo manage a specific type of saved object, use the corresponding APIs.\nFor example, use:\n\n[Data views](../group/endpoint-data-views)\n\nWarning: Do not write documents directly to the `.kibana` index. When you write directly to the `.kibana` index, the data becomes corrupted and permanently breaks future Kibana versions.\n" } ], "paths": { diff --git a/packages/core/saved-objects/docs/openapi/bundled_serverless.yaml b/packages/core/saved-objects/docs/openapi/bundled_serverless.yaml index a8dc8b30fffe2..00331902faa17 100644 --- a/packages/core/saved-objects/docs/openapi/bundled_serverless.yaml +++ b/packages/core/saved-objects/docs/openapi/bundled_serverless.yaml @@ -15,7 +15,16 @@ servers: default: localhost:5601 tags: - name: saved objects - description: Manage Kibana saved objects, including dashboards, visualizations, and more. + x-displayName: Saved objects + description: | + Export sets of saved objects that you want to import into {kib}, resolve import errors, and rotate an encryption key for encrypted saved objects with the saved objects APIs. + + To manage a specific type of saved object, use the corresponding APIs. + For example, use: + + [Data views](../group/endpoint-data-views) + + Warning: Do not write documents directly to the `.kibana` index. When you write directly to the `.kibana` index, the data becomes corrupted and permanently breaks future Kibana versions. paths: /api/saved_objects/_export: post: diff --git a/packages/core/saved-objects/docs/openapi/entrypoint.yaml b/packages/core/saved-objects/docs/openapi/entrypoint.yaml index ee20a72801647..5cd9039988186 100644 --- a/packages/core/saved-objects/docs/openapi/entrypoint.yaml +++ b/packages/core/saved-objects/docs/openapi/entrypoint.yaml @@ -10,7 +10,18 @@ info: url: https://www.elastic.co/licensing/elastic-license tags: - name: saved objects - description: Manage Kibana saved objects, including dashboards, visualizations, and more. + x-displayName: Saved objects + description: | + Export sets of saved objects that you want to import into {kib}, resolve import errors, and rotate an encryption key for encrypted saved objects with the saved objects APIs. + + To manage a specific type of saved object, use the corresponding APIs. + For example, use: + + * [Data views](../group/endpoint-data-views) + * [Spaces](https://www.elastic.co/guide/en/kibana/current/spaces-api.html) + * [Short URLs](https://www.elastic.co/guide/en/kibana/current/short-urls-api.html) + + Warning: Do not write documents directly to the `.kibana` index. When you write directly to the `.kibana` index, the data becomes corrupted and permanently breaks future Kibana versions. servers: - url: / paths: diff --git a/packages/core/saved-objects/docs/openapi/entrypoint_serverless.yaml b/packages/core/saved-objects/docs/openapi/entrypoint_serverless.yaml index ab3dcacf9a39d..69c742a8d7acd 100644 --- a/packages/core/saved-objects/docs/openapi/entrypoint_serverless.yaml +++ b/packages/core/saved-objects/docs/openapi/entrypoint_serverless.yaml @@ -10,7 +10,16 @@ info: url: https://www.elastic.co/licensing/elastic-license tags: - name: saved objects - description: Manage Kibana saved objects, including dashboards, visualizations, and more. + x-displayName: Saved objects + description: | + Export sets of saved objects that you want to import into {kib}, resolve import errors, and rotate an encryption key for encrypted saved objects with the saved objects APIs. + + To manage a specific type of saved object, use the corresponding APIs. + For example, use: + + [Data views](../group/endpoint-data-views) + + Warning: Do not write documents directly to the `.kibana` index. When you write directly to the `.kibana` index, the data becomes corrupted and permanently breaks future Kibana versions. servers: - url: 'https://{kibana_url}' variables: diff --git a/packages/kbn-ftr-common-functional-services/index.ts b/packages/kbn-ftr-common-functional-services/index.ts index 36d76c7e2ffd7..3cc6df44adf01 100644 --- a/packages/kbn-ftr-common-functional-services/index.ts +++ b/packages/kbn-ftr-common-functional-services/index.ts @@ -10,7 +10,6 @@ import { ProvidedType } from '@kbn/test'; export { services as commonFunctionalServices } from './services/all'; import { KibanaServerProvider } from './services/kibana_server'; -export { KibanaServerProvider } from './services/kibana_server'; export type KibanaServer = ProvidedType; export { RetryService } from './services/retry'; @@ -19,7 +18,6 @@ import { EsArchiverProvider } from './services/es_archiver'; export type EsArchiver = ProvidedType; import { EsProvider } from './services/es'; -export { EsProvider } from './services/es'; export type Es = ProvidedType; import { SupertestWithoutAuthProvider } from './services/supertest_without_auth'; @@ -31,10 +29,3 @@ import { SamlAuthProvider } from './services/saml_auth/saml_auth_provider'; export type SamlAuthProviderType = ProvidedType; export type { FtrProviderContext } from './services/ftr_provider_context'; -export { runSavedObjInfoSvc } from './services/saved_object_info'; - -export type { BsearchService, SendOptions } from './services/bsearch'; -export { SavedObjectInfoService } from './services/saved_object_info'; -export { DeploymentService } from './services/deployment'; -export { IndexPatternsService } from './services/index_patterns'; -export { RandomnessService } from './services/randomness'; diff --git a/packages/kbn-ftr-common-functional-services/services/all.ts b/packages/kbn-ftr-common-functional-services/services/all.ts index c6c0fb792bb81..49308faeb3dd0 100644 --- a/packages/kbn-ftr-common-functional-services/services/all.ts +++ b/packages/kbn-ftr-common-functional-services/services/all.ts @@ -10,13 +10,6 @@ import { EsArchiverProvider } from './es_archiver'; import { EsProvider } from './es'; import { KibanaServerProvider } from './kibana_server'; import { RetryService } from './retry'; -import { BsearchService } from './bsearch'; -import { ConsoleProvider } from './console'; -import { DeploymentService } from './deployment'; -import { EsDeleteAllIndicesProvider } from './es_delete_all_indices'; -import { IndexPatternsService } from './index_patterns'; -import { SavedObjectInfoService } from './saved_object_info'; -import { RandomnessService } from './randomness'; import { SupertestWithoutAuthProvider } from './supertest_without_auth'; import { SamlAuthProvider } from './saml_auth'; @@ -25,13 +18,6 @@ export const services = { kibanaServer: KibanaServerProvider, esArchiver: EsArchiverProvider, retry: RetryService, - bsearch: BsearchService, - console: ConsoleProvider, - deployment: DeploymentService, - esDeleteAllIndices: EsDeleteAllIndicesProvider, - indexPatterns: IndexPatternsService, - savedObjectInfo: SavedObjectInfoService, - randomness: RandomnessService, supertestWithoutAuth: SupertestWithoutAuthProvider, samlAuth: SamlAuthProvider, }; diff --git a/packages/kbn-ftr-common-functional-services/tsconfig.json b/packages/kbn-ftr-common-functional-services/tsconfig.json index 490eed5f7ac0e..56a442ad2f5a9 100644 --- a/packages/kbn-ftr-common-functional-services/tsconfig.json +++ b/packages/kbn-ftr-common-functional-services/tsconfig.json @@ -16,15 +16,8 @@ "@kbn/es-archiver", "@kbn/test", "@kbn/expect", - "@kbn/search-types", - "@kbn/core-http-common", - "@kbn/bfetch-plugin", - "@kbn/data-plugin", - "@kbn/dev-cli-runner", - "@kbn/dev-cli-errors", "@kbn/repo-info", "@kbn/es", - "@kbn/data-views-plugin" ], "exclude": [ "target/**/*", diff --git a/packages/kbn-ftr-common-functional-ui-services/index.ts b/packages/kbn-ftr-common-functional-ui-services/index.ts index f5f5b9df5b8ae..cd383a03e5f5d 100644 --- a/packages/kbn-ftr-common-functional-ui-services/index.ts +++ b/packages/kbn-ftr-common-functional-ui-services/index.ts @@ -22,4 +22,3 @@ export { } from './services/remote/network_profiles'; export type { TimeoutOpt } from './types'; export { TestSubjects } from './services/test_subjects'; -export { SecurityService } from './services/security'; diff --git a/packages/kbn-ftr-common-functional-ui-services/services/all.ts b/packages/kbn-ftr-common-functional-ui-services/services/all.ts index 610697c69dfbf..bffa7468e14ea 100644 --- a/packages/kbn-ftr-common-functional-ui-services/services/all.ts +++ b/packages/kbn-ftr-common-functional-ui-services/services/all.ts @@ -12,7 +12,6 @@ import { FindProvider } from './find'; import { TestSubjects } from './test_subjects'; import { BrowserProvider } from './browser'; import { ToastsService } from './toasts'; -import { SecurityServiceProvider } from './security'; export const services = { retryOnStale: RetryOnStaleProvider, @@ -21,5 +20,4 @@ export const services = { testSubjects: TestSubjects, browser: BrowserProvider, toasts: ToastsService, - security: SecurityServiceProvider, }; diff --git a/packages/kbn-ftr-common-functional-ui-services/services/ftr_provider_context.ts b/packages/kbn-ftr-common-functional-ui-services/services/ftr_provider_context.ts index 6f589ac6d1bb4..992fe27059e45 100644 --- a/packages/kbn-ftr-common-functional-ui-services/services/ftr_provider_context.ts +++ b/packages/kbn-ftr-common-functional-ui-services/services/ftr_provider_context.ts @@ -7,18 +7,12 @@ */ import { GenericFtrProviderContext, GenericFtrService } from '@kbn/test'; -import { - RetryService, - EsProvider, - KibanaServerProvider, -} from '@kbn/ftr-common-functional-services'; +import { RetryService } from '@kbn/ftr-common-functional-services'; import { services as commonFunctionalUiServices } from './all'; const services = { ...commonFunctionalUiServices, retry: RetryService, - es: EsProvider, - kibanaServer: KibanaServerProvider, }; export type FtrProviderContext = GenericFtrProviderContext; diff --git a/packages/kbn-ui-shared-deps-npm/BUILD.bazel b/packages/kbn-ui-shared-deps-npm/BUILD.bazel index 937cbe0c2a8ef..48f234b0bfe10 100644 --- a/packages/kbn-ui-shared-deps-npm/BUILD.bazel +++ b/packages/kbn-ui-shared-deps-npm/BUILD.bazel @@ -37,6 +37,7 @@ RUNTIME_DEPS = [ "@npm//whatwg-fetch", "@npm//symbol-observable", "@npm//@babel/runtime", + "@npm//@elastic/apm-rum-core", "@npm//@elastic/charts", "@npm//@elastic/eui", "@npm//@elastic/numeral", diff --git a/packages/kbn-ui-shared-deps-npm/webpack.config.js b/packages/kbn-ui-shared-deps-npm/webpack.config.js index 294bffdaaa833..34b70f3ea6dfb 100644 --- a/packages/kbn-ui-shared-deps-npm/webpack.config.js +++ b/packages/kbn-ui-shared-deps-npm/webpack.config.js @@ -58,6 +58,7 @@ module.exports = (_, argv) => { '@babel/runtime/helpers/wrapNativeSuper', // modules from npm + '@elastic/apm-rum-core', '@elastic/charts', '@elastic/eui', '@elastic/eui/optimize/es/components/provider/nested', diff --git a/packages/kbn-ui-shared-deps-src/src/definitions.js b/packages/kbn-ui-shared-deps-src/src/definitions.js index 6f7bff397d320..8dbe5bfbc2828 100644 --- a/packages/kbn-ui-shared-deps-src/src/definitions.js +++ b/packages/kbn-ui-shared-deps-src/src/definitions.js @@ -102,6 +102,7 @@ const externals = { '@tanstack/react-query-devtools': '__kbnSharedDeps__.ReactQueryDevtools', '@kbn/code-editor': '__kbnSharedDeps__.KbnCodeEditor', '@kbn/esql-ast': '__kbnSharedDeps__.KbnEsqlAst', + '@elastic/apm-rum-core': '__kbnSharedDeps__.ElasticApmRumCore', }; module.exports = { distDir, jsFilename, cssDistFilename, externals }; diff --git a/packages/kbn-ui-shared-deps-src/src/entry.js b/packages/kbn-ui-shared-deps-src/src/entry.js index 5347952978780..5b56e10108153 100644 --- a/packages/kbn-ui-shared-deps-src/src/entry.js +++ b/packages/kbn-ui-shared-deps-src/src/entry.js @@ -75,3 +75,4 @@ export const ReactQuery = require('@tanstack/react-query'); export const ReactQueryDevtools = require('@tanstack/react-query-devtools'); export const KbnCodeEditor = require('@kbn/code-editor'); export const KbnEsqlAst = require('@kbn/esql-ast'); +export const ElasticApmRumCore = require('@elastic/apm-rum-core'); diff --git a/scripts/saved_objs_info.js b/scripts/saved_objs_info.js index 229565921ba90..f17a2897b83da 100644 --- a/scripts/saved_objs_info.js +++ b/scripts/saved_objs_info.js @@ -7,4 +7,4 @@ */ require('../src/setup_node_env'); -require('@kbn/ftr-common-functional-services').runSavedObjInfoSvc(); +require('@kbn/test-suites-src/common/services/saved_object_info').runSavedObjInfoSvc(); diff --git a/src/plugins/embeddable/public/add_panel_flyout/add_panel_flyout.tsx b/src/plugins/embeddable/public/add_panel_flyout/add_panel_flyout.tsx index 1c9e9c00c8b12..d767e8f7e7c8e 100644 --- a/src/plugins/embeddable/public/add_panel_flyout/add_panel_flyout.tsx +++ b/src/plugins/embeddable/public/add_panel_flyout/add_panel_flyout.tsx @@ -82,9 +82,11 @@ const runAddTelemetry = ( export const AddPanelFlyout = ({ container, onAddPanel, + modalTitleId, }: { container: PresentationContainer; onAddPanel?: (id: string) => void; + modalTitleId?: string; }) => { const legacyFactoriesBySavedObjectType: LegacyFactoryMap = useMemo(() => { return [...embeddableStart.getEmbeddableFactories()] @@ -187,7 +189,7 @@ export const AddPanelFlyout = ({ <> -

+

{i18n.translate('embeddableApi.addPanel.Title', { defaultMessage: 'Add from library' })}

diff --git a/src/plugins/embeddable/public/add_panel_flyout/open_add_panel_flyout.tsx b/src/plugins/embeddable/public/add_panel_flyout/open_add_panel_flyout.tsx index 5bda8fd235c77..d8dec543780f5 100644 --- a/src/plugins/embeddable/public/add_panel_flyout/open_add_panel_flyout.tsx +++ b/src/plugins/embeddable/public/add_panel_flyout/open_add_panel_flyout.tsx @@ -9,7 +9,7 @@ import React, { Suspense } from 'react'; import { OverlayRef } from '@kbn/core/public'; -import { EuiLoadingSpinner } from '@elastic/eui'; +import { EuiLoadingSpinner, htmlIdGenerator } from '@elastic/eui'; import { toMountPoint } from '@kbn/react-kibana-mount'; import { PresentationContainer } from '@kbn/presentation-containers'; @@ -20,6 +20,8 @@ const LazyAddPanelFlyout = React.lazy(async () => { return { default: module.AddPanelFlyout }; }); +const htmlId = htmlIdGenerator('modalTitleId'); + export const openAddPanelFlyout = ({ container, onAddPanel, @@ -29,21 +31,28 @@ export const openAddPanelFlyout = ({ onAddPanel?: (id: string) => void; onClose?: () => void; }): OverlayRef => { + const modalTitleId = htmlId(); + // send the overlay ref to the root embeddable if it is capable of tracking overlays const flyoutSession = core.overlays.openFlyout( toMountPoint( }> - + , core ), { - 'data-test-subj': 'dashboardAddPanel', ownFocus: true, onClose: (overlayRef) => { if (onClose) onClose(); overlayRef.close(); }, + 'data-test-subj': 'dashboardAddPanel', + 'aria-labelledby': modalTitleId, } ); diff --git a/test/analytics/services/index.ts b/test/analytics/services/index.ts index 0d3465986249a..0c75df9bc2050 100644 --- a/test/analytics/services/index.ts +++ b/test/analytics/services/index.ts @@ -7,17 +7,14 @@ */ import { GenericFtrProviderContext } from '@kbn/test'; -import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; -import { commonFunctionalUIServices } from '@kbn/ftr-common-functional-ui-services'; - +import { services as commonServices } from '../../common/services'; import { services as functionalServices } from '../../functional/services'; import { pageObjects } from '../../functional/page_objects'; import { KibanaEBTServerProvider, KibanaEBTUIProvider } from './kibana_ebt'; export const services = { - ...commonFunctionalServices, - ...commonFunctionalUIServices, + ...commonServices, ...functionalServices, kibana_ebt_server: KibanaEBTServerProvider, kibana_ebt_ui: KibanaEBTUIProvider, diff --git a/test/api_integration/apis/console/autocomplete_entities.ts b/test/api_integration/apis/console/autocomplete_entities.ts index 1993ef1f7fe19..a410e50950cd1 100644 --- a/test/api_integration/apis/console/autocomplete_entities.ts +++ b/test/api_integration/apis/console/autocomplete_entities.ts @@ -8,11 +8,25 @@ import expect from '@kbn/expect'; import type { FtrProviderContext } from '../../ftr_provider_context'; +import { helpers } from './helpers'; export default ({ getService }: FtrProviderContext) => { - const console = getService('console'); - const supertest = getService('supertest'); + const { + createIndex, + createAlias, + createLegacyTemplate, + createIndexTemplate, + createComponentTemplate, + createDataStream, + deleteIndex, + deleteAlias, + deleteLegacyTemplate, + deleteIndexTemplate, + deleteComponentTemplate, + deleteDataStream, + } = helpers(getService); + const supertest = getService('supertest'); const sendRequest = (query: object) => supertest.get('/api/console/autocomplete_entities').query(query); @@ -26,26 +40,22 @@ export default ({ getService }: FtrProviderContext) => { before(async () => { // Setup indices, aliases, templates, and data streams - await console.createIndex(indexName); - await console.createAlias(indexName, aliasName); - await console.createComponentTemplate(componentTemplateName); - await console.createIndexTemplate( - indexTemplateName, - [dataStreamName], - [componentTemplateName] - ); - await console.createDataStream(dataStreamName); - await console.createLegacyTemplate(legacyTemplateName); + await createIndex(indexName); + await createAlias(indexName, aliasName); + await createComponentTemplate(componentTemplateName); + await createIndexTemplate(indexTemplateName, [dataStreamName], [componentTemplateName]); + await createDataStream(dataStreamName); + await createLegacyTemplate(legacyTemplateName); }); after(async () => { // Cleanup indices, aliases, templates, and data streams - await console.deleteAlias(indexName, aliasName); - await console.deleteIndex(indexName); - await console.deleteDataStream(dataStreamName); - await console.deleteIndexTemplate(indexTemplateName); - await console.deleteComponentTemplate(componentTemplateName); - await console.deleteLegacyTemplate(legacyTemplateName); + await deleteAlias(indexName, aliasName); + await deleteIndex(indexName); + await deleteDataStream(dataStreamName); + await deleteIndexTemplate(indexTemplateName); + await deleteComponentTemplate(componentTemplateName); + await deleteLegacyTemplate(legacyTemplateName); }); it('should not succeed if no settings are provided in query params', async () => { diff --git a/packages/kbn-ftr-common-functional-services/services/console.ts b/test/api_integration/apis/console/helpers.ts similarity index 95% rename from packages/kbn-ftr-common-functional-services/services/console.ts rename to test/api_integration/apis/console/helpers.ts index 1ca5128d83f7c..cfd1365f212fa 100644 --- a/packages/kbn-ftr-common-functional-services/services/console.ts +++ b/test/api_integration/apis/console/helpers.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import type { FtrProviderContext } from './ftr_provider_context'; +import type { FtrProviderContext } from '../../ftr_provider_context'; -export function ConsoleProvider({ getService }: FtrProviderContext) { +export function helpers(getService: FtrProviderContext['getService']) { const client = getService('es'); const createIndex = async (indexName: string) => { diff --git a/test/api_integration/services/index.ts b/test/api_integration/services/index.ts index f72460b7ffc5f..42a2616b781ac 100644 --- a/test/api_integration/services/index.ts +++ b/test/api_integration/services/index.ts @@ -6,13 +6,12 @@ * Side Public License, v 1. */ -import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; -import { commonFunctionalUIServices } from '@kbn/ftr-common-functional-ui-services'; +import { services as commonServices } from '../../common/services'; + import { KibanaSupertestProvider, ElasticsearchSupertestProvider } from './supertest'; export const services = { - ...commonFunctionalServices, - ...commonFunctionalUIServices, + ...commonServices, supertest: KibanaSupertestProvider, esSupertest: ElasticsearchSupertestProvider, }; diff --git a/test/common/config.js b/test/common/config.js index 8f2d6b7e14018..163703a693356 100644 --- a/test/common/config.js +++ b/test/common/config.js @@ -9,6 +9,7 @@ import path from 'path'; import { format as formatUrl } from 'url'; import { esTestConfig, kbnTestConfig, kibanaServerTestUser } from '@kbn/test'; +import { services } from './services'; export default function () { const servers = { @@ -84,5 +85,6 @@ export default function () { })}`, ], }, + services, }; } diff --git a/test/common/ftr_provider_context.ts b/test/common/ftr_provider_context.ts new file mode 100644 index 0000000000000..6d21aedfe1d5e --- /dev/null +++ b/test/common/ftr_provider_context.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 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 or the Server + * Side Public License, v 1. + */ + +import { GenericFtrProviderContext, GenericFtrService } from '@kbn/test'; + +import { services } from './services'; + +export type FtrProviderContext = GenericFtrProviderContext; +export class FtrService extends GenericFtrService {} diff --git a/packages/kbn-ftr-common-functional-services/services/bsearch.ts b/test/common/services/bsearch.ts similarity index 98% rename from packages/kbn-ftr-common-functional-services/services/bsearch.ts rename to test/common/services/bsearch.ts index b36834dedbca5..81063813cec5f 100644 --- a/packages/kbn-ftr-common-functional-services/services/bsearch.ts +++ b/test/common/services/bsearch.ts @@ -12,7 +12,7 @@ import type SuperTest from 'supertest'; import type { IEsSearchResponse } from '@kbn/search-types'; import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; import { BFETCH_ROUTE_VERSION_LATEST } from '@kbn/bfetch-plugin/common'; -import { FtrService } from './ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; /** * Function copied from here: @@ -62,7 +62,6 @@ export interface SendOptions { * }); * expect(response).eql({ ... your value ... }); */ - export class BsearchService extends FtrService { private readonly retry = this.ctx.getService('retry'); diff --git a/test/common/services/console.ts b/test/common/services/console.ts new file mode 100644 index 0000000000000..a864952fa081b --- /dev/null +++ b/test/common/services/console.ts @@ -0,0 +1,15 @@ +/* + * 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 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 or the Server + * Side Public License, v 1. + */ + +import type { FtrProviderContext } from '../ftr_provider_context'; +import { helpers } from '../../api_integration/apis/console/helpers'; +export function ConsoleProvider({ getService }: FtrProviderContext) { + return { + helpers: helpers(getService), + }; +} diff --git a/packages/kbn-ftr-common-functional-services/services/deployment.ts b/test/common/services/deployment.ts similarity index 96% rename from packages/kbn-ftr-common-functional-services/services/deployment.ts rename to test/common/services/deployment.ts index 28474a6bc60e2..e61d6b360da19 100644 --- a/packages/kbn-ftr-common-functional-services/services/deployment.ts +++ b/test/common/services/deployment.ts @@ -11,7 +11,7 @@ import { Agent } from 'https'; import fetch from 'node-fetch'; import { getUrl } from '@kbn/test'; -import { FtrService } from './ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; export class DeploymentService extends FtrService { private readonly config = this.ctx.getService('config'); diff --git a/packages/kbn-ftr-common-functional-services/services/es_delete_all_indices.ts b/test/common/services/es_delete_all_indices.ts similarity index 97% rename from packages/kbn-ftr-common-functional-services/services/es_delete_all_indices.ts rename to test/common/services/es_delete_all_indices.ts index cf3f93d9ef6aa..5f0ecba2cbde8 100644 --- a/packages/kbn-ftr-common-functional-services/services/es_delete_all_indices.ts +++ b/test/common/services/es_delete_all_indices.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { FtrProviderContext } from './ftr_provider_context'; +import { FtrProviderContext } from '../ftr_provider_context'; export function EsDeleteAllIndicesProvider({ getService }: FtrProviderContext) { const log = getService('log'); diff --git a/test/common/services/index.ts b/test/common/services/index.ts index 18855d9dee114..ccc786d4ccc6e 100644 --- a/test/common/services/index.ts +++ b/test/common/services/index.ts @@ -7,26 +7,17 @@ */ import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; -import { commonFunctionalUIServices } from '@kbn/ftr-common-functional-ui-services'; +import { DeploymentService } from './deployment'; +import { RandomnessService } from './randomness'; +import { SecurityServiceProvider } from './security'; +import { EsDeleteAllIndicesProvider } from './es_delete_all_indices'; +import { SavedObjectInfoService } from './saved_object_info'; +import { IndexPatternsService } from './index_patterns'; +import { BsearchService } from './bsearch'; +import { ConsoleProvider } from './console'; // pick only services that work for any FTR config, e.g. 'samlAuth' requires SAML setup in config file -const { - es, - esArchiver, - kibanaServer, - retry, - supertestWithoutAuth, - deployment, - randomness, - esDeleteAllIndices, - savedObjectInfo, - indexPatterns, - bsearch, - console, -} = commonFunctionalServices; - -// pick what was there previously -const { security } = commonFunctionalUIServices; +const { es, esArchiver, kibanaServer, retry, supertestWithoutAuth } = commonFunctionalServices; export const services = { es, @@ -34,12 +25,12 @@ export const services = { kibanaServer, retry, supertestWithoutAuth, - deployment, - randomness, - security, - esDeleteAllIndices, - savedObjectInfo, - indexPatterns, - bsearch, - console, + deployment: DeploymentService, + randomness: RandomnessService, + security: SecurityServiceProvider, + esDeleteAllIndices: EsDeleteAllIndicesProvider, + savedObjectInfo: SavedObjectInfoService, + indexPatterns: IndexPatternsService, + bsearch: BsearchService, + console: ConsoleProvider, }; diff --git a/packages/kbn-ftr-common-functional-services/services/index_patterns.ts b/test/common/services/index_patterns.ts similarity index 91% rename from packages/kbn-ftr-common-functional-services/services/index_patterns.ts rename to test/common/services/index_patterns.ts index 6113040b927b3..3fe02863b8568 100644 --- a/packages/kbn-ftr-common-functional-services/services/index_patterns.ts +++ b/test/common/services/index_patterns.ts @@ -6,11 +6,10 @@ * Side Public License, v 1. */ -import type { DataViewSpec } from '@kbn/data-plugin/common'; - -import { INITIAL_REST_VERSION } from '@kbn/data-views-plugin/server/constants'; import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; -import { FtrService } from './ftr_provider_context'; +import { INITIAL_REST_VERSION } from '@kbn/data-views-plugin/server/constants'; +import { DataViewSpec } from '@kbn/data-plugin/common'; +import { FtrService } from '../ftr_provider_context'; export class IndexPatternsService extends FtrService { private readonly kibanaServer = this.ctx.getService('kibanaServer'); diff --git a/packages/kbn-ftr-common-functional-services/services/randomness.ts b/test/common/services/randomness.ts similarity index 97% rename from packages/kbn-ftr-common-functional-services/services/randomness.ts rename to test/common/services/randomness.ts index d3ea0c74ff5de..c799c34539f15 100644 --- a/packages/kbn-ftr-common-functional-services/services/randomness.ts +++ b/test/common/services/randomness.ts @@ -9,7 +9,7 @@ import Chance from 'chance'; import { ToolingLog } from '@kbn/tooling-log'; -import { FtrService } from './ftr_provider_context'; +import { FtrService } from '../ftr_provider_context'; let __CACHED_SEED__: number | undefined; function getSeed(log: ToolingLog) { diff --git a/packages/kbn-ftr-common-functional-services/services/saved_object_info/index.ts b/test/common/services/saved_object_info/index.ts similarity index 100% rename from packages/kbn-ftr-common-functional-services/services/saved_object_info/index.ts rename to test/common/services/saved_object_info/index.ts diff --git a/packages/kbn-ftr-common-functional-services/services/saved_object_info/saved_object_info.ts b/test/common/services/saved_object_info/saved_object_info.ts similarity index 97% rename from packages/kbn-ftr-common-functional-services/services/saved_object_info/saved_object_info.ts rename to test/common/services/saved_object_info/saved_object_info.ts index 289c730cac17a..a1b98cbd408bf 100644 --- a/packages/kbn-ftr-common-functional-services/services/saved_object_info/saved_object_info.ts +++ b/test/common/services/saved_object_info/saved_object_info.ts @@ -13,7 +13,7 @@ import { flow, pipe } from 'fp-ts/function'; import * as TE from 'fp-ts/lib/TaskEither'; import * as T from 'fp-ts/lib/Task'; import { ToolingLog } from '@kbn/tooling-log'; -import { FtrService } from '../ftr_provider_context'; +import { FtrService } from '../../ftr_provider_context'; import { print } from './utils'; const pluck = diff --git a/packages/kbn-ftr-common-functional-services/services/saved_object_info/saved_objects_info_svc.md b/test/common/services/saved_object_info/saved_objects_info_svc.md similarity index 100% rename from packages/kbn-ftr-common-functional-services/services/saved_object_info/saved_objects_info_svc.md rename to test/common/services/saved_object_info/saved_objects_info_svc.md diff --git a/packages/kbn-ftr-common-functional-services/services/saved_object_info/use_with_jq.md b/test/common/services/saved_object_info/use_with_jq.md similarity index 100% rename from packages/kbn-ftr-common-functional-services/services/saved_object_info/use_with_jq.md rename to test/common/services/saved_object_info/use_with_jq.md diff --git a/packages/kbn-ftr-common-functional-services/services/saved_object_info/utils.ts b/test/common/services/saved_object_info/utils.ts similarity index 100% rename from packages/kbn-ftr-common-functional-services/services/saved_object_info/utils.ts rename to test/common/services/saved_object_info/utils.ts diff --git a/packages/kbn-ftr-common-functional-ui-services/services/security/index.ts b/test/common/services/security/index.ts similarity index 83% rename from packages/kbn-ftr-common-functional-ui-services/services/security/index.ts rename to test/common/services/security/index.ts index 308b2f8b075a0..d34246c0c4411 100644 --- a/packages/kbn-ftr-common-functional-ui-services/services/security/index.ts +++ b/test/common/services/security/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { SecurityService, SecurityServiceProvider } from './security'; +export { SecurityServiceProvider } from './security'; diff --git a/packages/kbn-ftr-common-functional-ui-services/services/security/role.ts b/test/common/services/security/role.ts similarity index 100% rename from packages/kbn-ftr-common-functional-ui-services/services/security/role.ts rename to test/common/services/security/role.ts diff --git a/packages/kbn-ftr-common-functional-ui-services/services/security/role_mappings.ts b/test/common/services/security/role_mappings.ts similarity index 100% rename from packages/kbn-ftr-common-functional-ui-services/services/security/role_mappings.ts rename to test/common/services/security/role_mappings.ts diff --git a/packages/kbn-ftr-common-functional-ui-services/services/security/security.ts b/test/common/services/security/security.ts similarity index 95% rename from packages/kbn-ftr-common-functional-ui-services/services/security/security.ts rename to test/common/services/security/security.ts index 5a5ff531acd5e..a182f225f2388 100644 --- a/packages/kbn-ftr-common-functional-ui-services/services/security/security.ts +++ b/test/common/services/security/security.ts @@ -9,7 +9,7 @@ import { Role } from './role'; import { User } from './user'; import { RoleMappings } from './role_mappings'; -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrProviderContext } from '../../ftr_provider_context'; import { createTestUserService, TestUserSupertestProvider, TestUser } from './test_user'; import { createSystemIndicesUser } from './system_indices_user'; diff --git a/packages/kbn-ftr-common-functional-ui-services/services/security/system_indices_user.ts b/test/common/services/security/system_indices_user.ts similarity index 97% rename from packages/kbn-ftr-common-functional-ui-services/services/security/system_indices_user.ts rename to test/common/services/security/system_indices_user.ts index 9d6016ebb17e9..52e166c645093 100644 --- a/packages/kbn-ftr-common-functional-ui-services/services/security/system_indices_user.ts +++ b/test/common/services/security/system_indices_user.ts @@ -13,7 +13,7 @@ import { createEsClientForFtrConfig, createRemoteEsClientForFtrConfig, } from '@kbn/test'; -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrProviderContext } from '../../ftr_provider_context'; const SYSTEM_INDICES_SUPERUSER_ROLE = 'system_indices_superuser'; diff --git a/packages/kbn-ftr-common-functional-ui-services/services/security/test_user.ts b/test/common/services/security/test_user.ts similarity index 96% rename from packages/kbn-ftr-common-functional-ui-services/services/security/test_user.ts rename to test/common/services/security/test_user.ts index 10a6da9f707dd..f3012304a95bd 100644 --- a/packages/kbn-ftr-common-functional-ui-services/services/security/test_user.ts +++ b/test/common/services/security/test_user.ts @@ -9,11 +9,10 @@ import { format as formatUrl } from 'url'; import supertest from 'supertest'; -import type { Browser } from '../browser'; -import type { TestSubjects } from '../test_subjects'; +import { type Browser, TestSubjects } from '@kbn/ftr-common-functional-ui-services'; import { Role } from './role'; import { User } from './user'; -import { FtrService, FtrProviderContext } from '../ftr_provider_context'; +import { FtrService, FtrProviderContext } from '../../ftr_provider_context'; const TEST_USER_NAME = 'test_user'; const TEST_USER_PASSWORD = 'changeme'; diff --git a/packages/kbn-ftr-common-functional-ui-services/services/security/user.ts b/test/common/services/security/user.ts similarity index 100% rename from packages/kbn-ftr-common-functional-ui-services/services/security/user.ts rename to test/common/services/security/user.ts diff --git a/test/functional/apps/discover/ftr_provider_context.d.ts b/test/functional/apps/discover/ftr_provider_context.d.ts index b006a98a73d7c..5bf34af1bf9f3 100644 --- a/test/functional/apps/discover/ftr_provider_context.d.ts +++ b/test/functional/apps/discover/ftr_provider_context.d.ts @@ -7,13 +7,7 @@ */ import { GenericFtrProviderContext } from '@kbn/test'; -import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; -import { services as functionalServces } from '../../services'; +import { services } from '../../services'; import { pageObjects } from '../../page_objects'; -const services = { - ...functionalServces, - ...commonFunctionalServices, -}; - export type FtrProviderContext = GenericFtrProviderContext; diff --git a/test/functional/services/index.ts b/test/functional/services/index.ts index 01df9b5fdcbbf..83672889eff75 100644 --- a/test/functional/services/index.ts +++ b/test/functional/services/index.ts @@ -7,7 +7,7 @@ */ import { commonFunctionalUIServices } from '@kbn/ftr-common-functional-ui-services'; -import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; +import { services as commonServiceProviders } from '../../common/services'; import { AppsMenuService } from './apps_menu'; import { @@ -57,7 +57,7 @@ import { ESQLService } from './esql'; import { DataViewsService } from './data_views'; export const services = { - ...commonFunctionalServices, + ...commonServiceProviders, ...commonFunctionalUIServices, filterBar: FilterBarService, queryBar: QueryBarService, diff --git a/test/health_gateway/services/index.ts b/test/health_gateway/services/index.ts index 5a89fb849f952..b9c44e227adae 100644 --- a/test/health_gateway/services/index.ts +++ b/test/health_gateway/services/index.ts @@ -6,13 +6,11 @@ * Side Public License, v 1. */ -import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; -import { commonFunctionalUIServices } from '@kbn/ftr-common-functional-ui-services'; +import { services as commonServices } from '../../common/services'; import { HealthGatewayService } from './health_gateway'; export const services = { - ...commonFunctionalServices, - ...commonFunctionalUIServices, + ...commonServices, healthGateway: HealthGatewayService, }; diff --git a/test/server_integration/config.base.js b/test/server_integration/config.base.js index 61243595505eb..71006c258c423 100644 --- a/test/server_integration/config.base.js +++ b/test/server_integration/config.base.js @@ -12,15 +12,13 @@ import { ElasticsearchSupertestProvider, } from './services'; -import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; - export default async function ({ readConfigFile }) { const commonConfig = await readConfigFile(require.resolve('../common/config')); const functionalConfig = await readConfigFile(require.resolve('../functional/config.base.js')); return { services: { - ...commonFunctionalServices, + ...commonConfig.get('services'), supertest: createKibanaSupertestProvider(), supertestWithoutAuth: KibanaSupertestWithoutAuthProvider, esSupertest: ElasticsearchSupertestProvider, diff --git a/test/server_integration/services/types.d.ts b/test/server_integration/services/types.d.ts index 204c0ae0106e5..2df95f0297f90 100644 --- a/test/server_integration/services/types.d.ts +++ b/test/server_integration/services/types.d.ts @@ -7,14 +7,7 @@ */ import { GenericFtrProviderContext } from '@kbn/test'; -import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; -import { commonFunctionalUIServices } from '@kbn/ftr-common-functional-ui-services'; - -export const kibanaCommonServices = { - ...commonFunctionalServices, - ...commonFunctionalUIServices, -} as const; - +import { services as kibanaCommonServices } from '../../common/services'; import { services as kibanaApiIntegrationServices } from '../../api_integration/services'; export type FtrProviderContext = GenericFtrProviderContext< diff --git a/test/tsconfig.json b/test/tsconfig.json index 8b0d946bded62..9f9f062a16492 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -47,6 +47,8 @@ "@kbn/controls-plugin", "@kbn/field-formats-plugin", "@kbn/axe-config", + "@kbn/dev-cli-runner", + "@kbn/dev-cli-errors", "@kbn/data-view-field-editor-plugin", "@kbn/data-views-plugin", "@kbn/guided-onboarding-plugin", @@ -69,6 +71,7 @@ "@kbn/links-plugin", "@kbn/ftr-common-functional-ui-services", "@kbn/monaco", + "@kbn/search-types", "@kbn/console-plugin", "@kbn/core-chrome-browser", "@kbn/default-nav-ml", diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_body/empty_convo.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_body/empty_convo.tsx index 9ec703093d44f..58d47a696225f 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_body/empty_convo.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_body/empty_convo.tsx @@ -38,7 +38,7 @@ export const EmptyConvo: React.FC = ({ setIsSettingsModalVisible, }) => { return ( - + = ({ text-align: center; `} > - + diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_overlay/flyout_navigation.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_overlay/flyout_navigation.tsx index 348fbb1ad3496..89518940799ee 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_overlay/flyout_navigation.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_overlay/flyout_navigation.tsx @@ -106,6 +106,7 @@ export const FlyoutNavigation = memo( size="xs" color="primary" iconType="newChat" + data-test-subj="newChatFromOverlay" onClick={onConversationCreate} disabled={isLoading || !isAssistantEnabled} > diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_title/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_title/index.tsx index 9b75c2e9e7c53..03016f143b6b0 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_title/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_title/index.tsx @@ -59,6 +59,7 @@ export const AssistantTitle: React.FC<{ `} > ( onConversationSelected({ cId: conversation.id, cTitle: conversation.title }) } label={conversation.title} + data-test-subj={`conversation-select-${conversation.title}`} isActive={ !isEmpty(conversation.id) ? conversation.id === currentConversation?.id diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.tsx index f4e0e7aa76528..f6e7b320611b1 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.tsx @@ -456,7 +456,7 @@ const AssistantComponent: React.FC = ({ overflow: hidden; `} > - + = ({ {Object.keys(selectedPromptContexts) .sort() - .map((id) => ( + .map((id, i) => ( { render({option.dropdownDisplay}); - expect(screen.getByTestId('name')).toHaveTextContent(mockSystemPrompt.name); + expect(screen.getByTestId(`systemPrompt-${mockSystemPrompt.name}`)).toHaveTextContent( + mockSystemPrompt.name + ); }); it('shows the expected prompt content in the dropdownDisplay', () => { diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/helpers.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/helpers.tsx index b5efd08b28f9c..b28d13a91fcd2 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/helpers.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/helpers.tsx @@ -46,7 +46,7 @@ export const getOptionFromPrompt = ({ ), dropdownDisplay: ( <> - {name} + {name} {/* Empty content tooltip gets around :hover styles from SuperSelectOptionButton */} diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/index.tsx index 2ac6aee95386a..b480d2150bc28 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/index.tsx @@ -67,7 +67,6 @@ const SystemPromptComponent: React.FC = ({ isCleared={isCleared} refetchConversations={refetchConversations} isSettingsModalVisible={isSettingsModalVisible} - onSystemPromptSelectionChange={onSystemPromptSelectionChange} selectedPrompt={selectedPrompt} setIsSettingsModalVisible={setIsSettingsModalVisible} /> diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_settings.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_settings.tsx index 7b8e451449884..6f4cd976846c2 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_settings.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_settings.tsx @@ -33,7 +33,7 @@ export const SystemPromptSettings: React.FC = React.m return ( <> -

{i18n.SETTINGS_TITLE}

+

{i18n.SETTINGS_TITLE}

{i18n.SETTINGS_DESCRIPTION} diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/prompt_context_selector/prompt_context_selector.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/prompt_context_selector/prompt_context_selector.tsx index eedb560019f0c..835047a8da8c0 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/prompt_context_selector/prompt_context_selector.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/quick_prompts/prompt_context_selector/prompt_context_selector.tsx @@ -88,6 +88,7 @@ export const PromptContextSelector: React.FC = React.memo( = React.memo( > onClickAddQuickPrompt(badge)} onClickAriaLabel={badge.name} > diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/upgrade_license_cta/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/upgrade_license_cta/index.tsx index e07f84eb0d487..4908b580552fb 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/upgrade_license_cta/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/upgrade_license_cta/index.tsx @@ -25,6 +25,7 @@ export const UpgradeLicenseCallToAction: React.FC = ({ http }) => { const basePath = http.basePath.get(); return ( = React.memo( dropdownDisplay: ( - + {connector.name} {connectorDetails && ( diff --git a/x-pack/packages/kbn-entities-schema/src/rest_spec/create.ts b/x-pack/packages/kbn-entities-schema/src/rest_spec/create.ts index 9f223b9051f8c..c6ee26d3a3d26 100644 --- a/x-pack/packages/kbn-entities-schema/src/rest_spec/create.ts +++ b/x-pack/packages/kbn-entities-schema/src/rest_spec/create.ts @@ -6,9 +6,10 @@ */ import { z } from '@kbn/zod'; +import { BooleanFromString } from '@kbn/zod-helpers'; export const createEntityDefinitionQuerySchema = z.object({ - installOnly: z.optional(z.coerce.boolean()).default(false), + installOnly: z.optional(BooleanFromString).default(false), }); export type CreateEntityDefinitionQuery = z.infer; diff --git a/x-pack/packages/kbn-entities-schema/src/rest_spec/delete.ts b/x-pack/packages/kbn-entities-schema/src/rest_spec/delete.ts index 0ce8f1b330d89..0bac10c2be264 100644 --- a/x-pack/packages/kbn-entities-schema/src/rest_spec/delete.ts +++ b/x-pack/packages/kbn-entities-schema/src/rest_spec/delete.ts @@ -6,13 +6,14 @@ */ import { z } from '@kbn/zod'; +import { BooleanFromString } from '@kbn/zod-helpers'; export const deleteEntityDefinitionParamsSchema = z.object({ id: z.string(), }); export const deleteEntityDefinitionQuerySchema = z.object({ - deleteData: z.optional(z.coerce.boolean().default(false)), + deleteData: z.optional(BooleanFromString).default(false), }); export type DeleteEntityDefinitionQuery = z.infer; diff --git a/x-pack/packages/kbn-entities-schema/src/schema/entity_definition.ts b/x-pack/packages/kbn-entities-schema/src/schema/entity_definition.ts index ae612c27a24b5..74be36cc5d802 100644 --- a/x-pack/packages/kbn-entities-schema/src/schema/entity_definition.ts +++ b/x-pack/packages/kbn-entities-schema/src/schema/entity_definition.ts @@ -59,4 +59,20 @@ export const entityDefinitionSchema = z.object({ installStartedAt: z.optional(z.string()), }); +export const entityDefinitionUpdateSchema = entityDefinitionSchema + .omit({ + id: true, + managed: true, + installStatus: true, + installStartedAt: true, + }) + .partial() + .merge( + z.object({ + history: z.optional(entityDefinitionSchema.shape.history.partial()), + version: semVerSchema, + }) + ); + export type EntityDefinition = z.infer; +export type EntityDefinitionUpdate = z.infer; diff --git a/x-pack/packages/kbn-entities-schema/tsconfig.json b/x-pack/packages/kbn-entities-schema/tsconfig.json index ecde0c1e3a46e..0fdbba4b7e793 100644 --- a/x-pack/packages/kbn-entities-schema/tsconfig.json +++ b/x-pack/packages/kbn-entities-schema/tsconfig.json @@ -16,5 +16,6 @@ ], "kbn_references": [ "@kbn/zod", + "@kbn/zod-helpers", ] } diff --git a/x-pack/plugins/actions/server/routes/connector/get/get.ts b/x-pack/plugins/actions/server/routes/connector/get/get.ts index 29d917702af4f..4519673a400fe 100644 --- a/x-pack/plugins/actions/server/routes/connector/get/get.ts +++ b/x-pack/plugins/actions/server/routes/connector/get/get.ts @@ -27,11 +27,16 @@ export const getConnectorRoute = ( access: 'public', summary: `Get connector information`, tags: ['oas-tag:connectors'], - // description: - // 'You must have `read` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges.', }, validate: { - params: getConnectorParamsSchemaV1, + request: { + params: getConnectorParamsSchemaV1, + }, + response: { + 200: { + description: 'Indicates a successful call.', + }, + }, }, }, router.handleLegacyErrors( diff --git a/x-pack/plugins/actions/server/routes/create.ts b/x-pack/plugins/actions/server/routes/create.ts index 2803c5d31d603..f7e8b61707d70 100644 --- a/x-pack/plugins/actions/server/routes/create.ts +++ b/x-pack/plugins/actions/server/routes/create.ts @@ -63,16 +63,21 @@ export const createActionRoute = ( access: 'public', summary: 'Create a connector', tags: ['oas-tag:connectors'], - // description: - // 'You must have `all` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges.', }, validate: { - params: schema.maybe( - schema.object({ - id: schema.maybe(schema.string()), - }) - ), - body: bodySchema, + request: { + params: schema.maybe( + schema.object({ + id: schema.maybe(schema.string()), + }) + ), + body: bodySchema, + }, + response: { + 200: { + description: 'Indicates a successful call.', + }, + }, }, }, router.handleLegacyErrors( diff --git a/x-pack/plugins/actions/server/routes/delete.ts b/x-pack/plugins/actions/server/routes/delete.ts index 8e2dd21952ab6..8b25fe66c9eb2 100644 --- a/x-pack/plugins/actions/server/routes/delete.ts +++ b/x-pack/plugins/actions/server/routes/delete.ts @@ -29,11 +29,17 @@ export const deleteActionRoute = ( access: 'public', summary: `Delete a connector`, description: 'WARNING: When you delete a connector, it cannot be recovered.', - // You must have `all` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. tags: ['oas-tag:connectors'], }, validate: { - params: paramSchema, + request: { + params: paramSchema, + }, + response: { + 204: { + description: 'Indicates a successful call.', + }, + }, }, }, router.handleLegacyErrors( diff --git a/x-pack/plugins/actions/server/routes/execute.ts b/x-pack/plugins/actions/server/routes/execute.ts index 83b6e72652b06..6f2ad11e20588 100644 --- a/x-pack/plugins/actions/server/routes/execute.ts +++ b/x-pack/plugins/actions/server/routes/execute.ts @@ -46,12 +46,18 @@ export const executeActionRoute = ( summary: `Run a connector`, description: 'You can use this API to test an action that involves interaction with Kibana services or integrations with third-party systems.', - // You must have `read` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. If you use an index connector, you must also have `all`, `create`, `index`, or `write` indices privileges. tags: ['oas-tag:connectors'], }, validate: { - body: bodySchema, - params: paramSchema, + request: { + body: bodySchema, + params: paramSchema, + }, + response: { + 200: { + description: 'Indicates a successful call.', + }, + }, }, }, router.handleLegacyErrors( diff --git a/x-pack/plugins/actions/server/routes/legacy/create.ts b/x-pack/plugins/actions/server/routes/legacy/create.ts index 3fd8c77759122..d668b1fbc19d2 100644 --- a/x-pack/plugins/actions/server/routes/legacy/create.ts +++ b/x-pack/plugins/actions/server/routes/legacy/create.ts @@ -40,7 +40,14 @@ export const createActionRoute = ( deprecated: true, }, validate: { - body: bodySchema, + request: { + body: bodySchema, + }, + response: { + 200: { + description: 'Indicates a successful call.', + }, + }, }, }, router.handleLegacyErrors( diff --git a/x-pack/plugins/actions/server/routes/legacy/delete.ts b/x-pack/plugins/actions/server/routes/legacy/delete.ts index e2df62e5ec4bd..2204095e03801 100644 --- a/x-pack/plugins/actions/server/routes/legacy/delete.ts +++ b/x-pack/plugins/actions/server/routes/legacy/delete.ts @@ -35,7 +35,14 @@ export const deleteActionRoute = ( deprecated: true, }, validate: { - params: paramSchema, + request: { + params: paramSchema, + }, + response: { + 204: { + description: 'Indicates a successful call.', + }, + }, }, }, router.handleLegacyErrors(async function (context, req, res) { diff --git a/x-pack/plugins/actions/server/routes/legacy/execute.ts b/x-pack/plugins/actions/server/routes/legacy/execute.ts index abe8971baf1cf..88e75aadb627c 100644 --- a/x-pack/plugins/actions/server/routes/legacy/execute.ts +++ b/x-pack/plugins/actions/server/routes/legacy/execute.ts @@ -40,8 +40,15 @@ export const executeActionRoute = ( tags: ['oas-tag:connectors'], }, validate: { - body: bodySchema, - params: paramSchema, + request: { + body: bodySchema, + params: paramSchema, + }, + response: { + 200: { + description: 'Indicates a successful call.', + }, + }, }, }, router.handleLegacyErrors(async function (context, req, res) { diff --git a/x-pack/plugins/actions/server/routes/legacy/get.ts b/x-pack/plugins/actions/server/routes/legacy/get.ts index e95c88fca7c45..524b3522ed541 100644 --- a/x-pack/plugins/actions/server/routes/legacy/get.ts +++ b/x-pack/plugins/actions/server/routes/legacy/get.ts @@ -34,7 +34,14 @@ export const getActionRoute = ( tags: ['oas-tag:connectors'], }, validate: { - params: paramSchema, + request: { + params: paramSchema, + }, + response: { + 200: { + description: 'Indicates a successful call.', + }, + }, }, }, router.handleLegacyErrors(async function (context, req, res) { diff --git a/x-pack/plugins/actions/server/routes/legacy/update.ts b/x-pack/plugins/actions/server/routes/legacy/update.ts index f07682706fc19..5f234c8bf55a9 100644 --- a/x-pack/plugins/actions/server/routes/legacy/update.ts +++ b/x-pack/plugins/actions/server/routes/legacy/update.ts @@ -40,8 +40,15 @@ export const updateActionRoute = ( tags: ['oas-tag:connectors'], }, validate: { - body: bodySchema, - params: paramSchema, + request: { + body: bodySchema, + params: paramSchema, + }, + response: { + 200: { + description: 'Indicates a successful call.', + }, + }, }, }, router.handleLegacyErrors(async function (context, req, res) { diff --git a/x-pack/plugins/actions/server/routes/update.ts b/x-pack/plugins/actions/server/routes/update.ts index 0077c1632877b..efb4d036b1e48 100644 --- a/x-pack/plugins/actions/server/routes/update.ts +++ b/x-pack/plugins/actions/server/routes/update.ts @@ -58,12 +58,17 @@ export const updateActionRoute = ( access: 'public', summary: `Update a connector`, tags: ['oas-tag:connectors'], - // description: - // 'You must have `all` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges.', }, validate: { - body: bodySchema, - params: paramSchema, + request: { + body: bodySchema, + params: paramSchema, + }, + response: { + 200: { + description: 'Indicates a successful call.', + }, + }, }, }, router.handleLegacyErrors( diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/built_in/services.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/built_in/services.ts index 87b876249f33f..4c97d5cec72ed 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/built_in/services.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/built_in/services.ts @@ -42,9 +42,6 @@ export const builtInServicesFromLogsEntityDefinition: EntityDefinition = syncDelay: '2m', }, }, - latest: { - lookback: '5m', - }, identityFields: ['service.name', { field: 'service.environment', optional: true }], displayNameTemplate: '{{service.name}}{{#service.environment}}:{{.}}{{/service.environment}}', metadata: [ diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/find_entity_definition.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/find_entity_definition.ts index d683c39a44890..0ea681676e9b1 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/find_entity_definition.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/find_entity_definition.ts @@ -58,6 +58,25 @@ export async function findEntityDefinitions({ ); } +export async function findEntityDefinitionById({ + id, + esClient, + soClient, +}: { + id: string; + esClient: ElasticsearchClient; + soClient: SavedObjectsClientContract; +}) { + const [definition] = await findEntityDefinitions({ + esClient, + soClient, + id, + perPage: 1, + }); + + return definition; +} + async function getEntityDefinitionState( esClient: ElasticsearchClient, definition: EntityDefinition diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/merge_definition_update.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/merge_definition_update.ts new file mode 100644 index 0000000000000..7352bfed33c62 --- /dev/null +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/helpers/merge_definition_update.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EntityDefinition, EntityDefinitionUpdate } from '@kbn/entities-schema'; +import { mergeWith, omit } from 'lodash'; + +export function mergeEntityDefinitionUpdate( + definition: EntityDefinition, + update: EntityDefinitionUpdate +) { + const updatedDefinition = mergeWith(definition, update, (value, other) => { + // we don't want to merge arrays (metrics, metadata..) but overwrite them + if (Array.isArray(value)) { + return other; + } + }); + + return omit(updatedDefinition, ['state']) as EntityDefinition; +} diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/install_entity_definition.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/install_entity_definition.ts index cbebbb804d538..7b0b18bc08a57 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/install_entity_definition.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/install_entity_definition.ts @@ -8,7 +8,7 @@ import semver from 'semver'; import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import { EntityDefinition } from '@kbn/entities-schema'; +import { EntityDefinition, EntityDefinitionUpdate } from '@kbn/entities-schema'; import { Logger } from '@kbn/logging'; import { generateHistoryIndexTemplateId, @@ -26,7 +26,7 @@ import { import { validateDefinitionCanCreateValidTransformIds } from './transform/validate_transform_ids'; import { deleteEntityDefinition } from './delete_entity_definition'; import { deleteHistoryIngestPipeline, deleteLatestIngestPipeline } from './delete_ingest_pipeline'; -import { findEntityDefinitions } from './find_entity_definition'; +import { findEntityDefinitionById } from './find_entity_definition'; import { entityDefinitionExists, saveEntityDefinition, @@ -44,6 +44,7 @@ import { generateEntitiesHistoryIndexTemplateConfig } from './templates/entities import { EntityIdConflict } from './errors/entity_id_conflict_error'; import { EntityDefinitionNotFound } from './errors/entity_not_found'; import { EntityDefinitionWithState } from './types'; +import { mergeEntityDefinitionUpdate } from './helpers/merge_definition_update'; export interface InstallDefinitionParams { esClient: ElasticsearchClient; @@ -140,13 +141,13 @@ export async function installBuiltInEntityDefinitions({ logger.debug(`Starting installation of ${definitions.length} built-in definitions`); const installPromises = definitions.map(async (builtInDefinition) => { - const installedDefinitions = await findEntityDefinitions({ + const installedDefinition = await findEntityDefinitionById({ esClient, soClient, id: builtInDefinition.id, }); - if (installedDefinitions.length === 0) { + if (!installedDefinition) { return await installEntityDefinition({ definition: builtInDefinition, esClient, @@ -156,20 +157,19 @@ export async function installBuiltInEntityDefinitions({ } // verify existing installation - const installedDefinition = installedDefinitions[0]; - if (!shouldReinstall(installedDefinition, builtInDefinition)) { + if (!shouldReinstallBuiltinDefinition(installedDefinition, builtInDefinition)) { return installedDefinition; } logger.debug( `Detected failed or outdated installation of definition [${installedDefinition.id}] v${installedDefinition.version}, installing v${builtInDefinition.version}` ); - return await reinstall({ + return await reinstallEntityDefinition({ soClient, esClient, logger, definition: installedDefinition, - latestDefinition: builtInDefinition, + definitionUpdate: builtInDefinition, }); }); @@ -223,30 +223,36 @@ async function install({ ]).then(throwIfRejected); await updateEntityDefinition(soClient, definition.id, { installStatus: 'installed' }); - return { ...definition, installStatus: 'installed' }; } // stop and delete the current transforms and reinstall all the components -async function reinstall({ +export async function reinstallEntityDefinition({ esClient, soClient, definition, - latestDefinition, + definitionUpdate, logger, -}: InstallDefinitionParams & { latestDefinition: EntityDefinition }): Promise { - logger.debug( - `Reinstalling definition ${definition.id} from v${definition.version} to v${latestDefinition.version}` - ); - +}: InstallDefinitionParams & { + definitionUpdate: EntityDefinitionUpdate; +}): Promise { try { - await updateEntityDefinition(soClient, latestDefinition.id, { - ...latestDefinition, + const updatedDefinition = mergeEntityDefinitionUpdate(definition, definitionUpdate); + + logger.debug( + () => + `Reinstalling definition ${definition.id} from v${definition.version} to v${ + definitionUpdate.version + }\n${JSON.stringify(updatedDefinition, null, 2)}` + ); + + await updateEntityDefinition(soClient, definition.id, { + ...updatedDefinition, installStatus: 'upgrading', installStartedAt: new Date().toISOString(), }); - logger.debug(`Stopping transforms for definition ${definition.id} v${definition.version}`); + logger.debug(`Deleting transforms for definition ${definition.id} v${definition.version}`); await Promise.all([ stopAndDeleteHistoryTransform(esClient, definition, logger), isBackfillEnabled(definition) @@ -259,10 +265,10 @@ async function reinstall({ esClient, soClient, logger, - definition: latestDefinition, + definition: updatedDefinition, }); } catch (err) { - await updateEntityDefinition(soClient, latestDefinition.id, { + await updateEntityDefinition(soClient, definition.id, { installStatus: 'failed', }); @@ -271,19 +277,34 @@ async function reinstall({ } const INSTALLATION_TIMEOUT = 5 * 60 * 1000; -const shouldReinstall = ( - definition: EntityDefinitionWithState, - latestDefinition: EntityDefinition -) => { +export const installationInProgress = (definition: EntityDefinition) => { + const { installStatus, installStartedAt } = definition; + + return ( + (installStatus === 'installing' || installStatus === 'upgrading') && + Date.now() - Date.parse(installStartedAt!) < INSTALLATION_TIMEOUT + ); +}; + +const installationTimedOut = (definition: EntityDefinition) => { const { installStatus, installStartedAt } = definition; - const isStale = + return ( (installStatus === 'installing' || installStatus === 'upgrading') && - Date.now() - Date.parse(installStartedAt!) >= INSTALLATION_TIMEOUT; - const isOutdated = - installStatus === 'installed' && semver.neq(definition.version, latestDefinition.version); - const isFailed = installStatus === 'failed'; - const isPartial = installStatus === 'installed' && !definition.state.installed; + Date.now() - Date.parse(installStartedAt!) >= INSTALLATION_TIMEOUT + ); +}; + +const shouldReinstallBuiltinDefinition = ( + installedDefinition: EntityDefinitionWithState, + latestDefinition: EntityDefinition +) => { + const { installStatus, version, state } = installedDefinition; + + const timedOut = installationTimedOut(installedDefinition); + const outdated = installStatus === 'installed' && semver.neq(version, latestDefinition.version); + const failed = installStatus === 'failed'; + const partial = installStatus === 'installed' && !state.installed; - return isStale || isOutdated || isFailed || isPartial; + return timedOut || outdated || failed || partial; }; diff --git a/x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/enable.ts b/x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/enable.ts index cd90f7f167b68..382e9a26ac6e5 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/enable.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/enable.ts @@ -10,7 +10,6 @@ import { CreateEntityDefinitionQuery, createEntityDefinitionQuerySchema, } from '@kbn/entities-schema'; -import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { SetupRouteOptions } from '../types'; import { canEnableEntityDiscovery, @@ -71,7 +70,7 @@ export function enableEntityDiscoveryRoute({ { path: '/internal/entities/managed/enablement', validate: { - query: buildRouteValidationWithZod(createEntityDefinitionQuerySchema), + query: createEntityDefinitionQuerySchema, }, }, async (context, req, res) => { diff --git a/x-pack/plugins/observability_solution/entity_manager/server/routes/entities/create.ts b/x-pack/plugins/observability_solution/entity_manager/server/routes/entities/create.ts index 3472578ef4c0b..973ba3507c455 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/routes/entities/create.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/routes/entities/create.ts @@ -12,13 +12,13 @@ import { createEntityDefinitionQuerySchema, CreateEntityDefinitionQuery, } from '@kbn/entities-schema'; -import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { SetupRouteOptions } from '../types'; import { EntityIdConflict } from '../../lib/entities/errors/entity_id_conflict_error'; import { EntitySecurityException } from '../../lib/entities/errors/entity_security_exception'; import { InvalidTransformError } from '../../lib/entities/errors/invalid_transform_error'; import { startTransform } from '../../lib/entities/start_transform'; import { installEntityDefinition } from '../../lib/entities/install_entity_definition'; +import { EntityDefinitionIdInvalid } from '../../lib/entities/errors/entity_definition_id_invalid'; /** * @openapi @@ -62,8 +62,8 @@ export function createEntityDefinitionRoute({ { path: '/internal/entities/definition', validate: { - body: buildRouteValidationWithZod(entityDefinitionSchema.strict()), - query: buildRouteValidationWithZod(createEntityDefinitionQuerySchema), + body: entityDefinitionSchema.strict(), + query: createEntityDefinitionQuerySchema, }, }, async (context, req, res) => { @@ -71,6 +71,7 @@ export function createEntityDefinitionRoute({ const core = await context.core; const soClient = core.savedObjects.client; const esClient = core.elasticsearch.client.asCurrentUser; + try { const definition = await installEntityDefinition({ soClient, @@ -85,12 +86,20 @@ export function createEntityDefinitionRoute({ return res.ok({ body: definition }); } catch (e) { + logger.error(e); + + if (e instanceof EntityDefinitionIdInvalid) { + return res.badRequest({ body: e }); + } + if (e instanceof EntityIdConflict) { return res.conflict({ body: e }); } + if (e instanceof EntitySecurityException || e instanceof InvalidTransformError) { return res.customError({ body: e, statusCode: 400 }); } + return res.customError({ body: e, statusCode: 500 }); } } diff --git a/x-pack/plugins/observability_solution/entity_manager/server/routes/entities/delete.ts b/x-pack/plugins/observability_solution/entity_manager/server/routes/entities/delete.ts index 65fb7ed2b3e2b..d0748e5b52e67 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/routes/entities/delete.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/routes/entities/delete.ts @@ -6,7 +6,6 @@ */ import { RequestHandlerContext } from '@kbn/core/server'; -import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { deleteEntityDefinitionParamsSchema, deleteEntityDefinitionQuerySchema, @@ -55,18 +54,18 @@ import { uninstallEntityDefinition } from '../../lib/entities/uninstall_entity_d export function deleteEntityDefinitionRoute({ router, server, + logger, }: SetupRouteOptions) { router.delete<{ id: string }, { deleteData?: boolean }, unknown>( { path: '/internal/entities/definition/{id}', validate: { - params: buildRouteValidationWithZod(deleteEntityDefinitionParamsSchema.strict()), - query: buildRouteValidationWithZod(deleteEntityDefinitionQuerySchema.strict()), + params: deleteEntityDefinitionParamsSchema.strict(), + query: deleteEntityDefinitionQuerySchema.strict(), }, }, async (context, req, res) => { try { - const { logger } = server; const soClient = (await context.core).savedObjects.client; const esClient = (await context.core).elasticsearch.client.asCurrentUser; @@ -81,6 +80,8 @@ export function deleteEntityDefinitionRoute({ return res.ok({ body: { acknowledged: true } }); } catch (e) { + logger.error(e); + if (e instanceof EntityDefinitionNotFound) { return res.notFound({ body: e }); } diff --git a/x-pack/plugins/observability_solution/entity_manager/server/routes/entities/get.ts b/x-pack/plugins/observability_solution/entity_manager/server/routes/entities/get.ts index b91e1115894ff..8039ee176a9b1 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/routes/entities/get.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/routes/entities/get.ts @@ -5,8 +5,8 @@ * 2.0. */ +import { z } from '@kbn/zod'; import { RequestHandlerContext } from '@kbn/core/server'; -import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { getEntityDefinitionQuerySchema } from '@kbn/entities-schema'; import { SetupRouteOptions } from '../types'; import { findEntityDefinitions } from '../../lib/entities/find_entity_definition'; @@ -52,12 +52,14 @@ import { findEntityDefinitions } from '../../lib/entities/find_entity_definition */ export function getEntityDefinitionRoute({ router, + logger, }: SetupRouteOptions) { - router.get( + router.get<{ id?: string }, { page?: number; perPage?: number }, unknown>( { - path: '/internal/entities/definition', + path: '/internal/entities/definition/{id?}', validate: { - query: buildRouteValidationWithZod(getEntityDefinitionQuerySchema.strict()), + query: getEntityDefinitionQuerySchema.strict(), + params: z.object({ id: z.optional(z.string()) }), }, }, async (context, req, res) => { @@ -69,9 +71,11 @@ export function getEntityDefinitionRoute({ soClient, page: req.query.page ?? 1, perPage: req.query.perPage ?? 10, + id: req.params.id, }); return res.ok({ body: { definitions } }); } catch (e) { + logger.error(e); return res.customError({ body: e, statusCode: 500 }); } } diff --git a/x-pack/plugins/observability_solution/entity_manager/server/routes/entities/reset.ts b/x-pack/plugins/observability_solution/entity_manager/server/routes/entities/reset.ts index 7755fcf65b3c3..b1fd385d22102 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/routes/entities/reset.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/routes/entities/reset.ts @@ -6,7 +6,6 @@ */ import { RequestHandlerContext } from '@kbn/core/server'; -import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { resetEntityDefinitionParamsSchema } from '@kbn/entities-schema'; import { SetupRouteOptions } from '../types'; import { EntitySecurityException } from '../../lib/entities/errors/entity_security_exception'; @@ -43,7 +42,7 @@ export function resetEntityDefinitionRoute({ { path: '/internal/entities/definition/{id}/_reset', validate: { - params: buildRouteValidationWithZod(resetEntityDefinitionParamsSchema.strict()), + params: resetEntityDefinitionParamsSchema.strict(), }, }, async (context, req, res) => { @@ -75,6 +74,8 @@ export function resetEntityDefinitionRoute({ return res.ok({ body: { acknowledged: true } }); } catch (e) { + logger.error(e); + if (e instanceof EntityDefinitionNotFound) { return res.notFound({ body: e }); } diff --git a/x-pack/plugins/observability_solution/entity_manager/server/routes/entities/update.ts b/x-pack/plugins/observability_solution/entity_manager/server/routes/entities/update.ts new file mode 100644 index 0000000000000..0668287d6d765 --- /dev/null +++ b/x-pack/plugins/observability_solution/entity_manager/server/routes/entities/update.ts @@ -0,0 +1,131 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from '@kbn/zod'; +import { RequestHandlerContext } from '@kbn/core/server'; +import { + createEntityDefinitionQuerySchema, + CreateEntityDefinitionQuery, + entityDefinitionUpdateSchema, + EntityDefinitionUpdate, +} from '@kbn/entities-schema'; +import { SetupRouteOptions } from '../types'; +import { EntitySecurityException } from '../../lib/entities/errors/entity_security_exception'; +import { InvalidTransformError } from '../../lib/entities/errors/invalid_transform_error'; +import { startTransform } from '../../lib/entities/start_transform'; +import { + installationInProgress, + reinstallEntityDefinition, +} from '../../lib/entities/install_entity_definition'; +import { findEntityDefinitionById } from '../../lib/entities/find_entity_definition'; + +/** + * @openapi + * /internal/entities/definition: + * put: + * description: Update an entity definition. + * tags: + * - definitions + * parameters: + * - in: query + * name: installOnly + * description: If true, the definition transforms will not be started + * required: false + * schema: + * type: boolean + * default: false + * requestBody: + * description: The definition properties to update + * required: true + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/entityDefinitionUpdateSchema' + * responses: + * 200: + * description: Success + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/entityDefinitionSchema' + * 400: + * description: The entity definition cannot be installed; see the error for more details + * 404: + * description: The entity definition does not exist + * 403: + * description: User is not allowed to update the entity definition + * 409: + * description: The entity definition is being updated by another request + */ +export function updateEntityDefinitionRoute({ + router, + server, +}: SetupRouteOptions) { + router.patch<{ id: string }, CreateEntityDefinitionQuery, EntityDefinitionUpdate>( + { + path: '/internal/entities/definition/{id}', + validate: { + body: entityDefinitionUpdateSchema.strict(), + query: createEntityDefinitionQuerySchema, + params: z.object({ id: z.string() }), + }, + }, + async (context, req, res) => { + const { logger } = server; + const core = await context.core; + const soClient = core.savedObjects.client; + const esClient = core.elasticsearch.client.asCurrentUser; + + try { + const installedDefinition = await findEntityDefinitionById({ + soClient, + esClient, + id: req.params.id, + }); + + if (!installedDefinition) { + return res.notFound({ + body: { message: `Entity definition [${req.params.id}] not found` }, + }); + } + + if (installedDefinition.managed) { + return res.forbidden({ + body: { message: `Managed definition cannot be modified` }, + }); + } + + if (installationInProgress(installedDefinition)) { + return res.conflict({ + body: { message: `Entity definition [${req.params.id}] has changes in progress` }, + }); + } + + const updatedDefinition = await reinstallEntityDefinition({ + soClient, + esClient, + logger, + definition: installedDefinition, + definitionUpdate: req.body, + }); + + if (!req.query.installOnly) { + await startTransform(esClient, updatedDefinition, logger); + } + + return res.ok({ body: updatedDefinition }); + } catch (e) { + logger.error(e); + + if (e instanceof EntitySecurityException || e instanceof InvalidTransformError) { + return res.customError({ body: e, statusCode: 400 }); + } + return res.customError({ body: e, statusCode: 500 }); + } + } + ); +} diff --git a/x-pack/plugins/observability_solution/entity_manager/server/routes/index.ts b/x-pack/plugins/observability_solution/entity_manager/server/routes/index.ts index 939bc755ae994..2fb9734187119 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/routes/index.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/routes/index.ts @@ -11,6 +11,7 @@ import { createEntityDefinitionRoute } from './entities/create'; import { deleteEntityDefinitionRoute } from './entities/delete'; import { resetEntityDefinitionRoute } from './entities/reset'; import { getEntityDefinitionRoute } from './entities/get'; +import { updateEntityDefinitionRoute } from './entities/update'; import { checkEntityDiscoveryEnabledRoute } from './enablement/check'; import { enableEntityDiscoveryRoute } from './enablement/enable'; import { disableEntityDiscoveryRoute } from './enablement/disable'; @@ -23,4 +24,5 @@ export function setupRoutes(dependencies: Setup checkEntityDiscoveryEnabledRoute(dependencies); enableEntityDiscoveryRoute(dependencies); disableEntityDiscoveryRoute(dependencies); + updateEntityDefinitionRoute(dependencies); } diff --git a/x-pack/plugins/observability_solution/entity_manager/tsconfig.json b/x-pack/plugins/observability_solution/entity_manager/tsconfig.json index 66dbddf1a261e..cba8dbcd50fee 100644 --- a/x-pack/plugins/observability_solution/entity_manager/tsconfig.json +++ b/x-pack/plugins/observability_solution/entity_manager/tsconfig.json @@ -24,10 +24,10 @@ "@kbn/core-saved-objects-api-server-mocks", "@kbn/entities-schema", "@kbn/es-query", - "@kbn/zod-helpers", "@kbn/security-plugin", "@kbn/encrypted-saved-objects-plugin", "@kbn/logging-mocks", "@kbn/licensing-plugin", + "@kbn/zod", ] } diff --git a/x-pack/plugins/search_indices/kibana.jsonc b/x-pack/plugins/search_indices/kibana.jsonc index 287719e99b350..7f23aa80fef15 100644 --- a/x-pack/plugins/search_indices/kibana.jsonc +++ b/x-pack/plugins/search_indices/kibana.jsonc @@ -18,6 +18,8 @@ "console", "usageCollection", ], - "requiredBundles": [] + "requiredBundles": [ + "kibanaReact", + ] } } diff --git a/x-pack/plugins/search_indices/public/application.tsx b/x-pack/plugins/search_indices/public/application.tsx new file mode 100644 index 0000000000000..e87834ccc59d2 --- /dev/null +++ b/x-pack/plugins/search_indices/public/application.tsx @@ -0,0 +1,43 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { CoreStart } from '@kbn/core/public'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { I18nProvider } from '@kbn/i18n-react'; +import { QueryClientProvider } from '@tanstack/react-query'; + +import { UsageTrackerContextProvider } from './contexts/usage_tracker_context'; +import { initQueryClient } from './services/query_client'; +import { SearchIndicesServicesContext } from './types'; + +export const renderApp = async ( + App: React.FC<{}>, + core: CoreStart, + services: Partial, + element: HTMLElement +) => { + const queryClient = initQueryClient(core.notifications.toasts); + ReactDOM.render( + + + + + + + + + + + , + element + ); + + return () => ReactDOM.unmountComponentAtNode(element); +}; diff --git a/x-pack/plugins/search_indices/public/components/start/start_page.tsx b/x-pack/plugins/search_indices/public/components/start/start_page.tsx new file mode 100644 index 0000000000000..0982a67843cce --- /dev/null +++ b/x-pack/plugins/search_indices/public/components/start/start_page.tsx @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo } from 'react'; + +import { EuiLoadingLogo, EuiPageTemplate } from '@elastic/eui'; +import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; + +import { useKibana } from '../../hooks/use_kibana'; + +export const ElasticsearchStartPage = () => { + const { console: consolePlugin } = useKibana().services; + + const embeddableConsole = useMemo( + () => (consolePlugin?.EmbeddableConsole ? : null), + [consolePlugin] + ); + + return ( + + + + + {embeddableConsole} + + ); +}; diff --git a/x-pack/plugins/search_indices/public/contexts/usage_tracker_context.tsx b/x-pack/plugins/search_indices/public/contexts/usage_tracker_context.tsx new file mode 100644 index 0000000000000..298c4a2430d40 --- /dev/null +++ b/x-pack/plugins/search_indices/public/contexts/usage_tracker_context.tsx @@ -0,0 +1,43 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { createContext, useContext, useMemo } from 'react'; +import type { + UsageCollectionSetup, + UsageCollectionStart, +} from '@kbn/usage-collection-plugin/public'; + +import { createUsageTracker, createEmptyUsageTracker } from '../services/usage_tracker'; +import { AppUsageTracker } from '../types'; + +const UsageTrackerContext = createContext(createEmptyUsageTracker()); + +export interface UsageTrackerContextProviderProps { + children: React.ReactNode | React.ReactNode[]; + usageCollection?: UsageCollectionSetup | UsageCollectionStart; +} +export function UsageTrackerContextProvider({ + children, + usageCollection, +}: UsageTrackerContextProviderProps) { + const usageTracker = useMemo(() => { + const searchIndicesUsageTracker = createUsageTracker(usageCollection); + searchIndicesUsageTracker.load('opened_app'); + return searchIndicesUsageTracker; + }, [usageCollection]); + return ( + {children} + ); +} + +export const useUsageTracker = () => { + const ctx = useContext(UsageTrackerContext); + if (!ctx) { + throw new Error('UsageTrackerContext should be used inside of the UsageTrackerContextProvider'); + } + return ctx; +}; diff --git a/x-pack/plugins/search_indices/public/hooks/use_kibana.ts b/x-pack/plugins/search_indices/public/hooks/use_kibana.ts new file mode 100644 index 0000000000000..8c626418f4373 --- /dev/null +++ b/x-pack/plugins/search_indices/public/hooks/use_kibana.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useKibana as _useKibana } from '@kbn/kibana-react-plugin/public'; +import { SearchIndicesServicesContext } from '../types'; + +export const useKibana = () => _useKibana(); diff --git a/x-pack/plugins/search_indices/public/hooks/use_usage_tracker.ts b/x-pack/plugins/search_indices/public/hooks/use_usage_tracker.ts new file mode 100644 index 0000000000000..44b2c85d048e2 --- /dev/null +++ b/x-pack/plugins/search_indices/public/hooks/use_usage_tracker.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { useUsageTracker } from '../contexts/usage_tracker_context'; diff --git a/x-pack/plugins/search_indices/public/plugin.ts b/x-pack/plugins/search_indices/public/plugin.ts index 88df6c1e4c10e..5ede34fa604a4 100644 --- a/x-pack/plugins/search_indices/public/plugin.ts +++ b/x-pack/plugins/search_indices/public/plugin.ts @@ -6,13 +6,41 @@ */ import type { CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; -import type { SearchIndicesPluginSetup, SearchIndicesPluginStart } from './types'; +import { i18n } from '@kbn/i18n'; +import type { + SearchIndicesAppPluginStartDependencies, + SearchIndicesPluginSetup, + SearchIndicesPluginStart, + SearchIndicesServicesContext, +} from './types'; export class SearchIndicesPlugin implements Plugin { - public setup(core: CoreSetup): SearchIndicesPluginSetup { - return {}; + public setup( + core: CoreSetup + ): SearchIndicesPluginSetup { + core.application.register({ + id: 'elasticsearchStart', + appRoute: '/app/elasticsearch/start', + title: i18n.translate('xpack.searchIndices.startAppTitle', { + defaultMessage: 'Elasticsearch Start', + }), + async mount({ element, history }) { + const { renderApp } = await import('./application'); + const { ElasticsearchStartPage } = await import('./components/start/start_page'); + const [coreStart, depsStart] = await core.getStartServices(); + const startDeps: Partial = { + ...depsStart, + history, + }; + return renderApp(ElasticsearchStartPage, coreStart, startDeps, element); + }, + }); + + return { + enabled: true, + }; } public start(core: CoreStart): SearchIndicesPluginStart { diff --git a/x-pack/plugins/search_indices/public/services/query_client.ts b/x-pack/plugins/search_indices/public/services/query_client.ts new file mode 100644 index 0000000000000..6c1797c49d0a4 --- /dev/null +++ b/x-pack/plugins/search_indices/public/services/query_client.ts @@ -0,0 +1,41 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { IToasts } from '@kbn/core/public'; +import { QueryClient, MutationCache, QueryCache } from '@tanstack/react-query'; +import { getErrorCode, getErrorMessage, isKibanaServerError } from '../utils/errors'; + +export function initQueryClient(toasts: IToasts): QueryClient { + return new QueryClient({ + mutationCache: new MutationCache({ + onError: (e, _vars, _ctx, _mutation) => { + // TODO: can we verify this instead of a blind cast? + const error = e as Error; + toasts.addError(error, { + title: error.name, + toastMessage: getErrorMessage(error), + toastLifeTimeMs: 1000, + }); + }, + }), + queryCache: new QueryCache({ + onError: (error) => { + // 404s are often functionally okay and shouldn't show toasts by default + if (getErrorCode(error) === 404) { + return; + } + if (isKibanaServerError(error) && !error.skipToast) { + toasts.addError(error, { + title: error.name, + toastMessage: getErrorMessage(error), + toastLifeTimeMs: 1000, + }); + } + }, + }), + }); +} diff --git a/x-pack/plugins/search_indices/public/services/usage_tracker.ts b/x-pack/plugins/search_indices/public/services/usage_tracker.ts new file mode 100644 index 0000000000000..717f7a00cbaaa --- /dev/null +++ b/x-pack/plugins/search_indices/public/services/usage_tracker.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { METRIC_TYPE, UiCounterMetricType } from '@kbn/analytics'; +import type { + UsageCollectionSetup, + UsageCollectionStart, +} from '@kbn/usage-collection-plugin/public'; +import { AppUsageTracker } from '../types'; + +const APP_TRACKER_NAME = 'searchIndices'; + +export function createUsageTracker( + usageCollection?: UsageCollectionSetup | UsageCollectionStart +): AppUsageTracker { + const track = (type: UiCounterMetricType, name: string | string[]) => + usageCollection?.reportUiCounter(APP_TRACKER_NAME, type, name); + + return { + click: (eventName: string | string[]) => { + track(METRIC_TYPE.CLICK, eventName); + }, + count: (eventName: string | string[]) => { + track(METRIC_TYPE.COUNT, eventName); + }, + load: (eventName: string | string[]) => { + track(METRIC_TYPE.LOADED, eventName); + }, + }; +} + +export function createEmptyUsageTracker(): AppUsageTracker { + return { + click: (_eventName: string | string[]) => {}, + count: (_eventName: string | string[]) => {}, + load: (_eventName: string | string[]) => {}, + }; +} diff --git a/x-pack/plugins/search_indices/public/types.ts b/x-pack/plugins/search_indices/public/types.ts index b16086803be53..e0b895a332d17 100644 --- a/x-pack/plugins/search_indices/public/types.ts +++ b/x-pack/plugins/search_indices/public/types.ts @@ -5,13 +5,35 @@ * 2.0. */ +import type { ConsolePluginStart } from '@kbn/console-plugin/public'; +import type { AppMountParameters, CoreStart } from '@kbn/core/public'; import type { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public'; +import type { SharePluginStart } from '@kbn/share-plugin/public'; +import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface SearchIndicesPluginSetup {} +export interface SearchIndicesPluginSetup { + enabled: boolean; +} // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface SearchIndicesPluginStart {} export interface AppPluginStartDependencies { navigation: NavigationPublicPluginStart; } + +export interface SearchIndicesAppPluginStartDependencies { + console?: ConsolePluginStart; + share: SharePluginStart; + usageCollection?: UsageCollectionStart; +} + +export type SearchIndicesServicesContext = CoreStart & + SearchIndicesAppPluginStartDependencies & { + history: AppMountParameters['history']; + }; + +export interface AppUsageTracker { + click: (eventName: string | string[]) => void; + count: (eventName: string | string[]) => void; + load: (eventName: string | string[]) => void; +} diff --git a/x-pack/plugins/search_indices/public/utils/errors.ts b/x-pack/plugins/search_indices/public/utils/errors.ts new file mode 100644 index 0000000000000..4625b2cf5240c --- /dev/null +++ b/x-pack/plugins/search_indices/public/utils/errors.ts @@ -0,0 +1,43 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { KibanaServerError } from '@kbn/kibana-utils-plugin/common'; + +export function getErrorMessage(error: unknown, defaultMessage?: string): string { + if (typeof error === 'string') { + return error; + } + if (isKibanaServerError(error)) { + return error.body.message; + } + + if (typeof error === 'object' && (error as { name: string }).name) { + return (error as { name: string }).name; + } + + return defaultMessage ?? ''; +} + +export function getErrorCode(error: unknown): number | undefined { + if (isKibanaServerError(error)) { + return error.body.statusCode; + } + return undefined; +} + +export function isKibanaServerError( + input: unknown +): input is Error & { body: KibanaServerError; name: string; skipToast?: boolean } { + if ( + typeof input === 'object' && + (input as { body: KibanaServerError }).body && + typeof (input as { body: KibanaServerError }).body.message === 'string' + ) { + return true; + } + return false; +} diff --git a/x-pack/plugins/search_indices/tsconfig.json b/x-pack/plugins/search_indices/tsconfig.json index 406b9de2400ce..dc1929035310f 100644 --- a/x-pack/plugins/search_indices/tsconfig.json +++ b/x-pack/plugins/search_indices/tsconfig.json @@ -16,6 +16,16 @@ "@kbn/config-schema", "@kbn/core-elasticsearch-server", "@kbn/logging", + "@kbn/react-kibana-context-render", + "@kbn/kibana-react-plugin", + "@kbn/i18n-react", + "@kbn/shared-ux-page-kibana-template", + "@kbn/usage-collection-plugin", + "@kbn/i18n", + "@kbn/analytics", + "@kbn/console-plugin", + "@kbn/share-plugin", + "@kbn/kibana-utils-plugin", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/security_solution/public/assistant/get_comments/index.tsx b/x-pack/plugins/security_solution/public/assistant/get_comments/index.tsx index 05e0dad25e1e6..efbb56ebcf540 100644 --- a/x-pack/plugins/security_solution/public/assistant/get_comments/index.tsx +++ b/x-pack/plugins/security_solution/public/assistant/get_comments/index.tsx @@ -184,6 +184,7 @@ export const getComments = ({ content={transformedMessage.content} index={index} isControlsEnabled={isControlsEnabled} + isError={message.isError} // reader is used to determine if streaming controls are shown reader={transformedMessage.reader} regenerateMessage={regenerateMessageOfConversation} diff --git a/x-pack/plugins/security_solution/public/assistant/get_comments/stream/index.tsx b/x-pack/plugins/security_solution/public/assistant/get_comments/stream/index.tsx index f25a93b017bf5..8ca072e805458 100644 --- a/x-pack/plugins/security_solution/public/assistant/get_comments/stream/index.tsx +++ b/x-pack/plugins/security_solution/public/assistant/get_comments/stream/index.tsx @@ -102,7 +102,14 @@ export const StreamComment = ({ return ( } + body={ + + } error={error ? new Error(error) : undefined} controls={controls} /> diff --git a/x-pack/plugins/security_solution/public/assistant/get_comments/stream/message_text.tsx b/x-pack/plugins/security_solution/public/assistant/get_comments/stream/message_text.tsx index c40b0c04043ad..ff526c33497c3 100644 --- a/x-pack/plugins/security_solution/public/assistant/get_comments/stream/message_text.tsx +++ b/x-pack/plugins/security_solution/public/assistant/get_comments/stream/message_text.tsx @@ -30,6 +30,7 @@ interface Props { content: string; index: number; loading: boolean; + ['data-test-subj']?: string; } const ANIMATION_TIME = 1; @@ -143,7 +144,7 @@ const getPluginDependencies = () => { }; }; -export function MessageText({ loading, content, index }: Props) { +export function MessageText({ loading, content, index, 'data-test-subj': dataTestSubj }: Props) { const containerClassName = css` overflow-wrap: anywhere; `; @@ -151,7 +152,7 @@ export function MessageText({ loading, content, index }: Props) { const { parsingPluginList, processingPluginList } = getPluginDependencies(); return ( - + @@ -270,6 +271,7 @@ export const SendToTimelineButton: FC diff --git a/x-pack/test/api_integration/apis/cloud_security_posture/helper.ts b/x-pack/test/api_integration/apis/cloud_security_posture/helper.ts index 7c21e6df09f8c..13bc2ee7de9d2 100644 --- a/x-pack/test/api_integration/apis/cloud_security_posture/helper.ts +++ b/x-pack/test/api_integration/apis/cloud_security_posture/helper.ts @@ -11,7 +11,7 @@ import expect from '@kbn/expect'; import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; import type { IndexDetails } from '@kbn/cloud-security-posture-common'; import { CLOUD_SECURITY_PLUGIN_VERSION } from '@kbn/cloud-security-posture-plugin/common/constants'; -import { SecurityService } from '@kbn/ftr-common-functional-ui-services'; +import { SecurityService } from '@kbn/test-suites-src/common/services/security/security'; export interface RoleCredentials { apiKey: { id: string; name: string }; diff --git a/x-pack/test/api_integration/apis/entity_manager/builtin_definitions.ts b/x-pack/test/api_integration/apis/entity_manager/builtin_definitions.ts index d5d92e75179fe..66ca29c10cc9d 100644 --- a/x-pack/test/api_integration/apis/entity_manager/builtin_definitions.ts +++ b/x-pack/test/api_integration/apis/entity_manager/builtin_definitions.ts @@ -73,10 +73,9 @@ export default function ({ getService }: FtrProviderContext) { const enableResponse = await enableEntityDiscovery(authorizedUser, 200); expect(enableResponse.success).to.eql(true, "authorized user can't enable EEM"); - const definitionsResponse = await getInstalledDefinitions( - supertestWithoutAuth, - authorizedUser - ); + const definitionsResponse = await getInstalledDefinitions(supertestWithoutAuth, { + auth: authorizedUser, + }); expect(definitionsResponse.definitions.length).to.eql(builtInDefinitions.length); expect( builtInDefinitions.every((builtin) => @@ -91,7 +90,7 @@ export default function ({ getService }: FtrProviderContext) { ); const disableResponse = await disableEntityDiscovery(authorizedUser, 200, { - deleteData: false, + deleteData: true, }); expect(disableResponse.success).to.eql( true, @@ -121,7 +120,9 @@ export default function ({ getService }: FtrProviderContext) { await disableEntityDiscovery(unauthorizedUser, 403); - const disableResponse = await disableEntityDiscovery(authorizedUser, 200); + const disableResponse = await disableEntityDiscovery(authorizedUser, 200, { + deleteData: true, + }); expect(disableResponse.success).to.eql(true, "authorized user can't disable EEM"); }); }); @@ -165,7 +166,7 @@ export default function ({ getService }: FtrProviderContext) { ) ).to.eql(true, 'all builtin definitions are not installed/running'); - await disableEntityDiscovery(authorizedUser, 200); + await disableEntityDiscovery(authorizedUser, 200, { deleteData: true }); }); }); @@ -186,7 +187,7 @@ export default function ({ getService }: FtrProviderContext) { isInstalledAndRunning(mockBuiltInEntityDefinition, definitionsResponse.definitions) ).to.ok(); - await disableEntityDiscovery(authorizedUser, 200); + await disableEntityDiscovery(authorizedUser, 200, { deleteData: true }); }); }); } diff --git a/x-pack/test/api_integration/apis/entity_manager/definitions.ts b/x-pack/test/api_integration/apis/entity_manager/definitions.ts index 0b901b1d6efbc..466b5e0232bf0 100644 --- a/x-pack/test/api_integration/apis/entity_manager/definitions.ts +++ b/x-pack/test/api_integration/apis/entity_manager/definitions.ts @@ -5,6 +5,7 @@ * 2.0. */ +import semver from 'semver'; import expect from '@kbn/expect'; import { entityLatestSchema } from '@kbn/entities-schema'; import { @@ -14,7 +15,12 @@ import { import { PartialConfig, cleanup, generate } from '@kbn/data-forge'; import { generateLatestIndexName } from '@kbn/entityManager-plugin/server/lib/entities/helpers/generate_component_id'; import { FtrProviderContext } from '../../ftr_provider_context'; -import { installDefinition, uninstallDefinition, getInstalledDefinitions } from './helpers/request'; +import { + installDefinition, + uninstallDefinition, + updateDefinition, + getInstalledDefinitions, +} from './helpers/request'; import { waitForDocumentInIndex } from '../../../alerting_api_integration/observability/helpers/alerting_wait_for_helpers'; export default function ({ getService }: FtrProviderContext) { @@ -27,43 +33,98 @@ export default function ({ getService }: FtrProviderContext) { describe('Entity definitions', () => { describe('definitions installations', () => { it('can install multiple definitions', async () => { - await installDefinition(supertest, mockDefinition); - await installDefinition(supertest, mockBackfillDefinition); + await installDefinition(supertest, { definition: mockDefinition }); + await installDefinition(supertest, { definition: mockBackfillDefinition }); const { definitions } = await getInstalledDefinitions(supertest); expect(definitions.length).to.eql(2); expect( - definitions.find( + definitions.some( (definition) => definition.id === mockDefinition.id && definition.state.installed === true && definition.state.running === true ) - ); + ).to.eql(true); expect( - definitions.find( + definitions.some( (definition) => definition.id === mockBackfillDefinition.id && definition.state.installed === true && definition.state.running === true ) - ); + ).to.eql(true); - await uninstallDefinition(supertest, mockDefinition.id); - await uninstallDefinition(supertest, mockBackfillDefinition.id); + await Promise.all([ + uninstallDefinition(supertest, { id: mockDefinition.id, deleteData: true }), + uninstallDefinition(supertest, { id: mockBackfillDefinition.id, deleteData: true }), + ]); }); it('does not start transforms when specified', async () => { - await installDefinition(supertest, mockDefinition, { installOnly: true }); + await installDefinition(supertest, { definition: mockDefinition, installOnly: true }); const { definitions } = await getInstalledDefinitions(supertest); expect(definitions.length).to.eql(1); expect(definitions[0].state.installed).to.eql(true); expect(definitions[0].state.running).to.eql(false); - await uninstallDefinition(supertest, mockDefinition.id); + await uninstallDefinition(supertest, { id: mockDefinition.id }); + }); + }); + + describe('definitions update', () => { + it('returns 404 if the definitions does not exist', async () => { + await updateDefinition(supertest, { + id: 'i-dont-exist', + update: { version: '1.0.0' }, + expectedCode: 404, + }); + }); + + it('accepts partial updates', async () => { + const incVersion = semver.inc(mockDefinition.version, 'major'); + await installDefinition(supertest, { definition: mockDefinition, installOnly: true }); + await updateDefinition(supertest, { + id: mockDefinition.id, + update: { + version: incVersion!, + history: { + timestampField: '@updatedTimestampField', + }, + }, + }); + + const { + definitions: [updatedDefinition], + } = await getInstalledDefinitions(supertest); + expect(updatedDefinition.version).to.eql(incVersion); + expect(updatedDefinition.history.timestampField).to.eql('@updatedTimestampField'); + + await uninstallDefinition(supertest, { id: mockDefinition.id }); + }); + + it('rejects updates to managed definitions', async () => { + await installDefinition(supertest, { + definition: { ...mockDefinition, managed: true }, + installOnly: true, + }); + + await updateDefinition(supertest, { + id: mockDefinition.id, + update: { + version: '1.0.0', + history: { + timestampField: '@updatedTimestampField', + }, + }, + expectedCode: 403, + }); + + await uninstallDefinition(supertest, { id: mockDefinition.id }); }); }); + describe('entity data', () => { let dataForgeConfig: PartialConfig; let dataForgeIndices: string[]; @@ -95,12 +156,12 @@ export default function ({ getService }: FtrProviderContext) { after(async () => { await esDeleteAllIndices(dataForgeIndices); - await uninstallDefinition(supertest, mockDefinition.id, true); + await uninstallDefinition(supertest, { id: mockDefinition.id, deleteData: true }); await cleanup({ client: esClient, config: dataForgeConfig, logger }); }); it('should create the proper entities in the latest index', async () => { - await installDefinition(supertest, mockDefinition); + await installDefinition(supertest, { definition: mockDefinition }); const sample = await waitForDocumentInIndex({ esClient, indexName: generateLatestIndexName(mockDefinition), @@ -108,6 +169,7 @@ export default function ({ getService }: FtrProviderContext) { retryService, logger, }); + const parsedSample = entityLatestSchema.safeParse(sample.hits.hits[0]._source); expect(parsedSample.success).to.be(true); }); diff --git a/x-pack/test/api_integration/apis/entity_manager/helpers/request.ts b/x-pack/test/api_integration/apis/entity_manager/helpers/request.ts index e69cb950afe42..822ab2e7ce24a 100644 --- a/x-pack/test/api_integration/apis/entity_manager/helpers/request.ts +++ b/x-pack/test/api_integration/apis/entity_manager/helpers/request.ts @@ -6,7 +6,7 @@ */ import { Agent } from 'supertest'; -import { EntityDefinition } from '@kbn/entities-schema'; +import { EntityDefinition, EntityDefinitionUpdate } from '@kbn/entities-schema'; import { EntityDefinitionWithState } from '@kbn/entityManager-plugin/server/lib/entities/types'; export interface Auth { @@ -16,9 +16,12 @@ export interface Auth { export const getInstalledDefinitions = async ( supertest: Agent, - auth?: Auth + params: { auth?: Auth; id?: string } = {} ): Promise<{ definitions: EntityDefinitionWithState[] }> => { - let req = supertest.get('/internal/entities/definition').set('kbn-xsrf', 'xxx'); + const { auth, id } = params; + let req = supertest + .get(`/internal/entities/definition${id ? `/${id}` : ''}`) + .set('kbn-xsrf', 'xxx'); if (auth) { req = req.auth(auth.username, auth.password); } @@ -28,18 +31,28 @@ export const getInstalledDefinitions = async ( export const installDefinition = async ( supertest: Agent, - definition: EntityDefinition, - query: Record = {} + params: { + definition: EntityDefinition; + installOnly?: boolean; + } ) => { + const { definition, installOnly = false } = params; return supertest .post('/internal/entities/definition') - .query(query) + .query({ installOnly }) .set('kbn-xsrf', 'xxx') .send(definition) .expect(200); }; -export const uninstallDefinition = (supertest: Agent, id: string, deleteData = false) => { +export const uninstallDefinition = ( + supertest: Agent, + params: { + id: string; + deleteData?: boolean; + } +) => { + const { id, deleteData = false } = params; return supertest .delete(`/internal/entities/definition/${id}`) .query({ deleteData }) @@ -48,6 +61,22 @@ export const uninstallDefinition = (supertest: Agent, id: string, deleteData = f .expect(200); }; +export const updateDefinition = ( + supertest: Agent, + params: { + id: string; + update: EntityDefinitionUpdate; + expectedCode?: number; + } +) => { + const { id, update, expectedCode = 200 } = params; + return supertest + .patch(`/internal/entities/definition/${id}`) + .set('kbn-xsrf', 'xxx') + .send(update) + .expect(expectedCode); +}; + export const upgradeBuiltinDefinitions = async ( supertest: Agent, definitions: EntityDefinition[] diff --git a/x-pack/test/api_integration/apis/telemetry/telemetry.ts b/x-pack/test/api_integration/apis/telemetry/telemetry.ts index 5e034ce3a1847..bae5c5b8cdc44 100644 --- a/x-pack/test/api_integration/apis/telemetry/telemetry.ts +++ b/x-pack/test/api_integration/apis/telemetry/telemetry.ts @@ -26,7 +26,7 @@ import { ELASTIC_HTTP_VERSION_HEADER, X_ELASTIC_INTERNAL_ORIGIN_REQUEST, } from '@kbn/core-http-common'; -import type { SecurityService } from '@kbn/ftr-common-functional-ui-services'; +import type { SecurityService } from '@kbn/test-suites-src/common/services/security/security'; import basicClusterFixture from './fixtures/basiccluster.json'; import multiClusterFixture from './fixtures/multicluster.json'; import type { FtrProviderContext } from '../../ftr_provider_context'; diff --git a/x-pack/test/common/services/bsearch_secure.ts b/x-pack/test/common/services/bsearch_secure.ts index ccd1866ddd66e..f454aa3818ea6 100644 --- a/x-pack/test/common/services/bsearch_secure.ts +++ b/x-pack/test/common/services/bsearch_secure.ts @@ -5,8 +5,8 @@ * 2.0. */ -// NOTE: This is pretty much a copy/paste from packages/kbn-ftr-common-functional-services/services/bsearch.ts -// but with the ability to provide custom auth +// NOTE: This is pretty much a copy/paste from test/common/services/bsearch.ts but with the ability +// to provide custom auth import expect from '@kbn/expect'; import request from 'superagent'; diff --git a/x-pack/test/common/services/index.ts b/x-pack/test/common/services/index.ts index edf64c828e944..5e931b440654a 100644 --- a/x-pack/test/common/services/index.ts +++ b/x-pack/test/common/services/index.ts @@ -6,8 +6,7 @@ */ import { services as kibanaApiIntegrationServices } from '@kbn/test-suites-src/api_integration/services'; -import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; -import { commonFunctionalUIServices } from '@kbn/ftr-common-functional-ui-services'; +import { services as kibanaCommonServices } from '@kbn/test-suites-src/common/services'; import { InfraLogViewsServiceProvider } from './infra_log_views'; import { SpacesServiceProvider } from './spaces'; import { BsearchSecureService } from './bsearch_secure'; @@ -15,8 +14,7 @@ import { ApmSynthtraceKibanaClientProvider } from './apm_synthtrace_kibana_clien import { InfraSynthtraceKibanaClientProvider } from './infra_synthtrace_kibana_client'; export const services = { - ...commonFunctionalServices, - ...commonFunctionalUIServices, + ...kibanaCommonServices, infraLogViews: InfraLogViewsServiceProvider, supertest: kibanaApiIntegrationServices.supertest, spaces: SpacesServiceProvider, diff --git a/x-pack/test/fleet_api_integration/apis/test_users.ts b/x-pack/test/fleet_api_integration/apis/test_users.ts index 74581fd681af7..f84c1f72fb657 100644 --- a/x-pack/test/fleet_api_integration/apis/test_users.ts +++ b/x-pack/test/fleet_api_integration/apis/test_users.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { SecurityService } from '@kbn/ftr-common-functional-ui-services'; +import type { SecurityService } from '@kbn/test-suites-src/common/services/security/security'; export const testUsers: { [rollName: string]: { username: string; password: string; permissions?: any }; diff --git a/x-pack/test/fleet_cypress/services.ts b/x-pack/test/fleet_cypress/services.ts index 8986c0928d750..272cf7eb8da4e 100644 --- a/x-pack/test/fleet_cypress/services.ts +++ b/x-pack/test/fleet_cypress/services.ts @@ -4,10 +4,5 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; -import { commonFunctionalUIServices } from '@kbn/ftr-common-functional-ui-services'; -export const services = { - ...commonFunctionalServices, - ...commonFunctionalUIServices, -} as const; +export * from '@kbn/test-suites-src/common/services'; diff --git a/x-pack/test/osquery_cypress/services.ts b/x-pack/test/osquery_cypress/services.ts index 95fd493e6f668..272cf7eb8da4e 100644 --- a/x-pack/test/osquery_cypress/services.ts +++ b/x-pack/test/osquery_cypress/services.ts @@ -5,10 +5,4 @@ * 2.0. */ -import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; -import { commonFunctionalUIServices } from '@kbn/ftr-common-functional-ui-services'; - -export const services = { - ...commonFunctionalServices, - ...commonFunctionalUIServices, -} as const; +export * from '@kbn/test-suites-src/common/services'; diff --git a/x-pack/test/profiling_api_integration/common/create_profiling_users/helpers/create_or_update_user.ts b/x-pack/test/profiling_api_integration/common/create_profiling_users/helpers/create_or_update_user.ts index 2f467d4507e7f..679a750af410b 100644 --- a/x-pack/test/profiling_api_integration/common/create_profiling_users/helpers/create_or_update_user.ts +++ b/x-pack/test/profiling_api_integration/common/create_profiling_users/helpers/create_or_update_user.ts @@ -8,7 +8,7 @@ /* eslint-disable no-console */ import { difference, union } from 'lodash'; -import type { SecurityService } from '@kbn/ftr-common-functional-ui-services'; +import { SecurityService } from '@kbn/test-suites-src/common/services/security/security'; import { Elasticsearch, Kibana } from '..'; import { callKibana, isAxiosError } from './call_kibana'; diff --git a/x-pack/test/profiling_api_integration/common/create_profiling_users/index.ts b/x-pack/test/profiling_api_integration/common/create_profiling_users/index.ts index 18a056b001c56..d7c101dd52eaa 100644 --- a/x-pack/test/profiling_api_integration/common/create_profiling_users/index.ts +++ b/x-pack/test/profiling_api_integration/common/create_profiling_users/index.ts @@ -5,7 +5,7 @@ * 2.0. */ import { asyncForEach } from '@kbn/std'; -import type { SecurityService } from '@kbn/ftr-common-functional-ui-services'; +import { SecurityService } from '@kbn/test-suites-src/common/services/security/security'; import { ProfilingUsername, profilingUsers } from './authentication'; import { AbortError, callKibana } from './helpers/call_kibana'; import { createOrUpdateUser } from './helpers/create_or_update_user'; diff --git a/x-pack/test/saved_objects_field_count/config.ts b/x-pack/test/saved_objects_field_count/config.ts index 603a325ca0479..eb2aeb1df90a9 100644 --- a/x-pack/test/saved_objects_field_count/config.ts +++ b/x-pack/test/saved_objects_field_count/config.ts @@ -6,7 +6,6 @@ */ import { FtrConfigProviderContext } from '@kbn/test'; -import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { const kibanaCommonTestsConfig = await readConfigFile( @@ -16,10 +15,6 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { return { ...kibanaCommonTestsConfig.getAll(), - services: { - ...commonFunctionalServices, - }, - testFiles: [require.resolve('./test')], esTestCluster: { diff --git a/x-pack/test/security_solution_api_integration/config/services/security_solution_serverless_utils.ts b/x-pack/test/security_solution_api_integration/config/services/security_solution_serverless_utils.ts index 00df4f0374c27..da57ccf64860e 100644 --- a/x-pack/test/security_solution_api_integration/config/services/security_solution_serverless_utils.ts +++ b/x-pack/test/security_solution_api_integration/config/services/security_solution_serverless_utils.ts @@ -9,7 +9,7 @@ import supertest from 'supertest'; import { format as formatUrl } from 'url'; import { IEsSearchResponse } from '@kbn/search-types'; import { RoleCredentials } from '@kbn/test-suites-serverless/shared/services'; -import type { SendOptions } from '@kbn/ftr-common-functional-services'; +import type { SendOptions } from '@kbn/test-suites-src/common/services/bsearch'; import type { SendOptions as SecureBsearchSendOptions } from '@kbn/test-suites-serverless/shared/services/bsearch_secure'; import type { FtrProviderContext } from '../../ftr_provider_context'; import type { SecuritySolutionUtilsInterface } from './types'; diff --git a/x-pack/test/security_solution_api_integration/config/services/types.ts b/x-pack/test/security_solution_api_integration/config/services/types.ts index 838f31e69412e..72397582dad00 100644 --- a/x-pack/test/security_solution_api_integration/config/services/types.ts +++ b/x-pack/test/security_solution_api_integration/config/services/types.ts @@ -9,7 +9,7 @@ import TestAgent from 'supertest/lib/agent'; import type { IEsSearchResponse } from '@kbn/search-types'; import type { BsearchSecureService } from '@kbn/test-suites-serverless/shared/services/bsearch_secure'; -import type { BsearchService, SendOptions } from '@kbn/ftr-common-functional-services'; +import type { BsearchService, SendOptions } from '@kbn/test-suites-src/common/services/bsearch'; export interface SecuritySolutionServerlessBsearch extends Omit { send: (options: SendOptions) => Promise; diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/users_and_roles.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/users_and_roles.ts index a9663cd943a78..3bccaf4a00fe3 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/users_and_roles.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/users_and_roles.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { SecurityService } from '@kbn/ftr-common-functional-ui-services'; +import { SecurityService } from '@kbn/test-suites-src/common/services/security/security'; export const usersAndRolesFactory = (security: SecurityService) => ({ createRole: async ({ name, privileges }: { name: string; privileges: any }) => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/tests/host_details.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/tests/host_details.ts index cf2ac65f1086b..1c74a987e4fec 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/tests/host_details.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/tests/host_details.ts @@ -11,7 +11,7 @@ import { HostsQueries, } from '@kbn/security-solution-plugin/common/search_strategy'; import TestAgent from 'supertest/lib/agent'; -import { BsearchService } from '@kbn/ftr-common-functional-services'; +import { BsearchService } from '@kbn/test-suites-src/common/services/bsearch'; import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; import { hostDetailsFilebeatExpectedResult } from '../mocks/host_details'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/tests/hosts.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/tests/hosts.ts index 80052ef3ddf56..a39da25c81aa3 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/tests/hosts.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/tests/hosts.ts @@ -16,7 +16,7 @@ import { FirstLastSeenStrategyResponse, } from '@kbn/security-solution-plugin/common/search_strategy'; import TestAgent from 'supertest/lib/agent'; -import { BsearchService } from '@kbn/ftr-common-functional-services'; +import { BsearchService } from '@kbn/test-suites-src/common/services/bsearch'; import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/tests/uncommon_processes.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/tests/uncommon_processes.ts index 22001c26b66b5..19710d4eedf45 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/tests/uncommon_processes.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/explore/hosts/trial_license_complete_tier/tests/uncommon_processes.ts @@ -12,7 +12,7 @@ import { HostsUncommonProcessesStrategyResponse, } from '@kbn/security-solution-plugin/common/search_strategy'; import TestAgent from 'supertest/lib/agent'; -import { BsearchService } from '@kbn/ftr-common-functional-services'; +import { BsearchService } from '@kbn/test-suites-src/common/services/bsearch'; import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; const FROM = '2000-01-01T00:00:00.000Z'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/tests/network_details.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/tests/network_details.ts index 166af42ba5702..5e9040424713b 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/tests/network_details.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/tests/network_details.ts @@ -11,7 +11,7 @@ import { NetworkQueries, } from '@kbn/security-solution-plugin/common/search_strategy'; import TestAgent from 'supertest/lib/agent'; -import { BsearchService } from '@kbn/ftr-common-functional-services'; +import { BsearchService } from '@kbn/test-suites-src/common/services/bsearch'; import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/tests/network_dns.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/tests/network_dns.ts index 22edc8cff64de..7254dc6e99a5e 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/tests/network_dns.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/tests/network_dns.ts @@ -14,7 +14,7 @@ import { NetworkDnsStrategyResponse, } from '@kbn/security-solution-plugin/common/search_strategy'; import TestAgent from 'supertest/lib/agent'; -import { BsearchService } from '@kbn/ftr-common-functional-services'; +import { BsearchService } from '@kbn/test-suites-src/common/services/bsearch'; import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; export default function ({ getService }: FtrProviderContextWithSpaces) { diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/tests/network_top_n_flow.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/tests/network_top_n_flow.ts index 8b1adb16975f6..2306861471073 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/tests/network_top_n_flow.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/tests/network_top_n_flow.ts @@ -15,7 +15,7 @@ import { NetworkTopNFlowStrategyResponse, } from '@kbn/security-solution-plugin/common/search_strategy'; import TestAgent from 'supertest/lib/agent'; -import { BsearchService } from '@kbn/ftr-common-functional-services'; +import { BsearchService } from '@kbn/test-suites-src/common/services/bsearch'; import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/tests/tls.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/tests/tls.ts index 36b2b677a1949..4c555ca0d6555 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/tests/tls.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/explore/network/trial_license_complete_tier/tests/tls.ts @@ -14,8 +14,7 @@ import { NetworkTlsStrategyResponse, } from '@kbn/security-solution-plugin/common/search_strategy'; import TestAgent from 'supertest/lib/agent'; - -import { BsearchService } from '@kbn/ftr-common-functional-services'; +import { BsearchService } from '@kbn/test-suites-src/common/services/bsearch'; import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/tests/overview_host.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/tests/overview_host.ts index ffb287239ac0f..d99fbd296ba3e 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/tests/overview_host.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/tests/overview_host.ts @@ -12,7 +12,7 @@ import { HostsOverviewStrategyResponse, } from '@kbn/security-solution-plugin/common/search_strategy'; import TestAgent from 'supertest/lib/agent'; -import { BsearchService } from '@kbn/ftr-common-functional-services'; +import { BsearchService } from '@kbn/test-suites-src/common/services/bsearch'; import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; export default function ({ getService }: FtrProviderContextWithSpaces) { diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/tests/overview_network.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/tests/overview_network.ts index f8d4aa80c0e3d..952e3eed8f8af 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/tests/overview_network.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/explore/overview/trial_license_complete_tier/tests/overview_network.ts @@ -11,7 +11,7 @@ import { NetworkQueries, } from '@kbn/security-solution-plugin/common/search_strategy'; import TestAgent from 'supertest/lib/agent'; -import { BsearchService } from '@kbn/ftr-common-functional-services'; +import { BsearchService } from '@kbn/test-suites-src/common/services/bsearch'; import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; export default function ({ getService }: FtrProviderContextWithSpaces) { diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/tests/authentications.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/tests/authentications.ts index 39dddc7a0c046..d7329a597e2e0 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/tests/authentications.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/tests/authentications.ts @@ -15,7 +15,7 @@ import { import type { UserAuthenticationsRequestOptions } from '@kbn/security-solution-plugin/common/api/search_strategy'; import TestAgent from 'supertest/lib/agent'; -import { BsearchService } from '@kbn/ftr-common-functional-services'; +import { BsearchService } from '@kbn/test-suites-src/common/services/bsearch'; import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; const FROM = '2000-01-01T00:00:00.000Z'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/tests/users.ts b/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/tests/users.ts index 6765e6d2bb164..65b44bf4cbc5e 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/tests/users.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/explore/users/trial_license_complete_tier/tests/users.ts @@ -14,8 +14,7 @@ import { NetworkUsersStrategyResponse, } from '@kbn/security-solution-plugin/common/search_strategy'; import TestAgent from 'supertest/lib/agent'; - -import { BsearchService } from '@kbn/ftr-common-functional-services'; +import { BsearchService } from '@kbn/test-suites-src/common/services/bsearch'; import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/events.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/events.ts index c42ac64de4a23..c66978bbe1b42 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/events.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/events.ts @@ -14,9 +14,8 @@ import { TimelineEventsAllStrategyResponse, } from '@kbn/security-solution-plugin/common/search_strategy'; import TestAgent from 'supertest/lib/agent'; -import { BsearchService } from '@kbn/ftr-common-functional-services'; +import { BsearchService } from '@kbn/test-suites-src/common/services/bsearch'; import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; - import { getFieldsToRequest, getFilterValue } from '../../../../utils'; const TO = '3000-01-01T00:00:00.000Z'; diff --git a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/timeline_details.ts b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/timeline_details.ts index 12539d43a145f..1e3119260455d 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/timeline_details.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/investigation/timeline/trial_license_complete_tier/tests/timeline_details.ts @@ -13,8 +13,7 @@ import { TimelineKpiStrategyResponse, } from '@kbn/security-solution-plugin/common/search_strategy'; import TestAgent from 'supertest/lib/agent'; - -import { BsearchService } from '@kbn/ftr-common-functional-services'; +import { BsearchService } from '@kbn/test-suites-src/common/services/bsearch'; import { FtrProviderContextWithSpaces } from '../../../../../ftr_provider_context_with_spaces'; import { timelineDetailsFilebeatExpectedResults as EXPECTED_DATA } from '../mocks/timeline_details'; diff --git a/x-pack/test/security_solution_api_integration/tsconfig.json b/x-pack/test/security_solution_api_integration/tsconfig.json index 1f558e3c3f051..2f420920027d5 100644 --- a/x-pack/test/security_solution_api_integration/tsconfig.json +++ b/x-pack/test/security_solution_api_integration/tsconfig.json @@ -49,6 +49,6 @@ "@kbn/dev-cli-runner", "@kbn/search-types", "@kbn/security-plugin", - "@kbn/ftr-common-functional-ui-services", + "@kbn/test-suites-src", ] } diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/ai_assistant/ai_assistant_feature_complete.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/ai_assistant/ai_assistant_feature_complete.cy.ts deleted file mode 100644 index 8f241dfe9d560..0000000000000 --- a/x-pack/test/security_solution_cypress/cypress/e2e/ai_assistant/ai_assistant_feature_complete.cy.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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { AI_ASSISTANT_BUTTON } from '../../screens/ai_assistant'; -import { login } from '../../tasks/login'; -import { visitGetStartedPage } from '../../tasks/navigation'; - -describe( - 'App Features for Security Complete', - { - tags: ['@serverless'], - env: { - ftrConfig: { - productTypes: [ - { product_line: 'security', product_tier: 'complete' }, - { product_line: 'endpoint', product_tier: 'complete' }, - ], - }, - }, - }, - () => { - beforeEach(() => { - login(); - }); - - it('should have have AI Assistant available', () => { - visitGetStartedPage(); - cy.get(AI_ASSISTANT_BUTTON).should('exist'); - }); - } -); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/ai_assistant/conversations.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/ai_assistant/conversations.cy.ts new file mode 100644 index 0000000000000..286627367c46e --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/e2e/ai_assistant/conversations.cy.ts @@ -0,0 +1,173 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EXPLAIN_THEN_SUMMARIZE_RULE_DETAILS, + RULE_MANAGEMENT_CONTEXT_DESCRIPTION, +} from '@kbn/security-solution-plugin/public/detections/pages/detection_engine/rules/translations'; +import { EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE_NON_I18N } from '@kbn/security-solution-plugin/public/assistant/content/prompts/user/translations'; +import { + assertConnectorSelected, + assertNewConversation, + closeAssistant, + openAssistant, + selectConnector, + createNewChat, + selectConversation, + assertMessageSent, + typeAndSendMessage, + assertErrorResponse, + selectRule, + assertErrorToastShown, + updateConversationTitle, + assertSystemPrompt, +} from '../../tasks/assistant'; +import { deleteConversations } from '../../tasks/api_calls/assistant'; +import { + azureConnectorAPIPayload, + bedrockConnectorAPIPayload, + createAzureConnector, + createBedrockConnector, +} from '../../tasks/api_calls/connectors'; +import { expandFirstAlert } from '../../tasks/alerts'; +import { ALERTS_URL } from '../../urls/navigation'; +import { waitForAlertsToPopulate } from '../../tasks/create_new_rule'; +import { visitRulesManagementTable } from '../../tasks/rules_management'; +import { deleteAlertsAndRules, deleteConnectors } from '../../tasks/api_calls/common'; +import { createRule } from '../../tasks/api_calls/rules'; +import { getExistingRule, getNewRule } from '../../objects/rule'; +import { login } from '../../tasks/login'; +import { + CONNECTOR_MISSING_CALLOUT, + PROMPT_CONTEXT_BUTTON, + USER_PROMPT, +} from '../../screens/ai_assistant'; +import { visit, visitGetStartedPage } from '../../tasks/navigation'; + +describe('AI Assistant Conversations', { tags: ['@ess', '@serverless'] }, () => { + beforeEach(() => { + deleteConnectors(); + deleteConversations(); + deleteAlertsAndRules(); + login(); + }); + describe('No connectors or conversations exist', () => { + it('Shows welcome setup when no connectors or conversations exist', () => { + visitGetStartedPage(); + openAssistant(); + assertNewConversation(true, 'Welcome'); + }); + }); + describe('When no conversations exist but connectors do exist, show empty convo', () => { + beforeEach(() => { + createAzureConnector(); + }); + it('When invoked on AI Assistant click', () => { + visitGetStartedPage(); + openAssistant(); + assertNewConversation(false, 'Welcome'); + assertConnectorSelected(azureConnectorAPIPayload.name); + assertSystemPrompt('Default system prompt'); + cy.get(USER_PROMPT).should('not.have.text'); + }); + it('When invoked from rules page', () => { + createRule(getExistingRule({ rule_id: 'rule1', enabled: true })).then((createdRule) => { + visitRulesManagementTable(); + selectRule(createdRule?.body?.id); + openAssistant('rule'); + assertNewConversation(false, 'Detection Rules'); + assertConnectorSelected(azureConnectorAPIPayload.name); + assertSystemPrompt('Default system prompt'); + cy.get(USER_PROMPT).should('have.text', EXPLAIN_THEN_SUMMARIZE_RULE_DETAILS); + cy.get(PROMPT_CONTEXT_BUTTON(0)).should('have.text', RULE_MANAGEMENT_CONTEXT_DESCRIPTION); + }); + }); + it('When invoked from alert details', () => { + createRule(getNewRule()); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlert(); + openAssistant('alert'); + assertNewConversation(false, 'Alert summary'); + assertConnectorSelected(azureConnectorAPIPayload.name); + assertSystemPrompt('Default system prompt'); + cy.get(USER_PROMPT).should( + 'have.text', + EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE_NON_I18N + ); + cy.get(PROMPT_CONTEXT_BUTTON(0)).should('have.text', 'Alert (from summary)'); + }); + it('Shows empty connector callout when a conversation that had a connector no longer does', () => { + visitGetStartedPage(); + openAssistant(); + assertConnectorSelected(azureConnectorAPIPayload.name); + closeAssistant(); + deleteConnectors(); + openAssistant(); + cy.get(CONNECTOR_MISSING_CALLOUT).should('be.visible'); + }); + }); + describe('Changing conversations', () => { + beforeEach(() => { + createAzureConnector(); + createBedrockConnector(); + }); + + it('Last conversation persists in memory from page to page', () => { + createRule(getNewRule()); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlert(); + openAssistant('alert'); + assertNewConversation(false, 'Alert summary'); + closeAssistant(); + visitGetStartedPage(); + openAssistant(); + assertNewConversation(false, 'Alert summary'); + }); + it('Properly switches back and forth between conversations', () => { + visitGetStartedPage(); + openAssistant(); + assertNewConversation(false, 'Welcome'); + assertConnectorSelected(azureConnectorAPIPayload.name); + typeAndSendMessage('hello'); + assertMessageSent('hello', true); + assertErrorResponse(); + selectConversation('Alert summary'); + selectConnector(bedrockConnectorAPIPayload.name); + typeAndSendMessage('goodbye'); + assertMessageSent('goodbye', true); + assertErrorResponse(); + selectConversation('Welcome'); + assertConnectorSelected(azureConnectorAPIPayload.name); + assertMessageSent('hello', true); + selectConversation('Alert summary'); + assertConnectorSelected(bedrockConnectorAPIPayload.name); + assertMessageSent('goodbye', true); + }); + // This test is flakey due to the issue linked below and will be skipped until it is fixed + it.skip('Only allows one conversation called "New chat" at a time', () => { + visitGetStartedPage(); + openAssistant(); + createNewChat(); + assertNewConversation(false, 'New chat'); + assertConnectorSelected(azureConnectorAPIPayload.name); + typeAndSendMessage('hello'); + // TODO fix bug with new chat and error message + // https://github.com/elastic/kibana/issues/191025 + // assertMessageSent('hello', true); + assertErrorResponse(); + selectConversation('Welcome'); + createNewChat(); + assertErrorToastShown('Error creating conversation with title New chat'); + selectConversation('New chat'); + updateConversationTitle('My other chat'); + createNewChat(); + assertNewConversation(false, 'New chat'); + }); + }); +}); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/ai_assistant/ess_basic.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/ai_assistant/ess_basic.cy.ts new file mode 100644 index 0000000000000..fea68e5f6b515 --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/e2e/ai_assistant/ess_basic.cy.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { startBasicLicense } from '../../tasks/api_calls/licensing'; +import { UPGRADE_CTA } from '../../screens/ai_assistant'; +import { login } from '../../tasks/login'; +import { assertConversationReadOnly, openAssistant } from '../../tasks/assistant'; +import { visitGetStartedPage } from '../../tasks/navigation'; + +describe('AI Assistant - Basic License', { tags: ['@ess'] }, () => { + beforeEach(() => { + login(); + startBasicLicense(); + visitGetStartedPage(); + }); + + it('user with Basic license should not be able to use assistant', () => { + openAssistant(); + cy.get(UPGRADE_CTA).should('be.visible'); + assertConversationReadOnly(); + }); +}); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/ai_assistant/feature_complete.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/ai_assistant/feature_complete.cy.ts new file mode 100644 index 0000000000000..f086690e07146 --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/e2e/ai_assistant/feature_complete.cy.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { AI_ASSISTANT_BUTTON } from '../../screens/ai_assistant'; +import { login } from '../../tasks/login'; +import { visitGetStartedPage } from '../../tasks/navigation'; + +describe('App Features for Security Complete', { tags: ['@serverless'] }, () => { + beforeEach(() => { + login(); + }); + + it('should have have AI Assistant available', () => { + visitGetStartedPage(); + cy.get(AI_ASSISTANT_BUTTON).should('be.visible'); + }); +}); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/ai_assistant/ai_assistant_feature_essentials.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/ai_assistant/feature_essentials.cy.ts similarity index 100% rename from x-pack/test/security_solution_cypress/cypress/e2e/ai_assistant/ai_assistant_feature_essentials.cy.ts rename to x-pack/test/security_solution_cypress/cypress/e2e/ai_assistant/feature_essentials.cy.ts diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/ai_assistant/messages.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/ai_assistant/messages.cy.ts new file mode 100644 index 0000000000000..233efb264ede7 --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/e2e/ai_assistant/messages.cy.ts @@ -0,0 +1,72 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { MessageRole } from '@kbn/elastic-assistant-common'; +import { TIMELINE_QUERY } from '../../screens/timeline'; +import { CASES_URL } from '../../urls/navigation'; +import { SEND_TO_TIMELINE_BUTTON } from '../../screens/ai_assistant'; +import { openAssistant, selectConversation, sendQueryToTimeline } from '../../tasks/assistant'; +import { + deleteConversations, + deletePrompts, + waitForConversation, +} from '../../tasks/api_calls/assistant'; +import { createAzureConnector } from '../../tasks/api_calls/connectors'; +import { deleteConnectors } from '../../tasks/api_calls/common'; +import { login } from '../../tasks/login'; +import { visit, visitGetStartedPage } from '../../tasks/navigation'; + +describe( + 'AI Assistant Messages', + // TODO - Fix this test to work in serverless - https://github.com/elastic/kibana/pull/190152 + { tags: ['@ess', '@serverless', '@skipInServerless'] }, + () => { + const mockTimelineQuery = 'host.risk.keyword: "high"'; + const mockConvo = { + id: 'spooky', + title: 'Spooky convo', + messages: [ + { + timestamp: '2024-08-15T18:30:37.873Z', + content: + 'You are a helpful, expert assistant who answers questions about Elastic Security. Do not answer questions unrelated to Elastic Security.\nIf you answer a question related to KQL, EQL, or ES|QL, it should be immediately usable within an Elastic Security timeline; please always format the output correctly with back ticks. Any answer provided for Query DSL should also be usable in a security timeline. This means you should only ever include the "filter" portion of the query.\n\nGive a query I can run in the timeline', + role: 'user' as MessageRole, + }, + { + timestamp: '2024-08-15T18:31:24.008Z', + content: + 'To query events from a high-risk host in the Elastic Security timeline, you can use the following KQL query:\n\n```kql\n' + + mockTimelineQuery + + '\n```', + role: 'assistant' as MessageRole, + traceData: { + traceId: '74d2fac29753adebd5c479e3d9e45da3', + transactionId: 'e13d97d138b8a13c', + }, + }, + ], + }; + beforeEach(() => { + deleteConnectors(); + deleteConversations(); + deletePrompts(); + login(); + createAzureConnector(); + waitForConversation(mockConvo); + }); + it('A message with a kql query can be used in the timeline only from pages with timeline', () => { + visitGetStartedPage(); + openAssistant(); + selectConversation(mockConvo.title); + cy.get(SEND_TO_TIMELINE_BUTTON).should('be.disabled'); + visit(CASES_URL); + openAssistant(); + sendQueryToTimeline(); + cy.get(TIMELINE_QUERY).should('have.text', `${mockTimelineQuery}`); + }); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/ai_assistant/prompts.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/ai_assistant/prompts.cy.ts new file mode 100644 index 0000000000000..f31866d4ec9bf --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/e2e/ai_assistant/prompts.cy.ts @@ -0,0 +1,132 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SUPERHERO_SYSTEM_PROMPT_NON_I18N } from '@kbn/security-solution-plugin/public/assistant/content/prompts/system/translations'; +import { EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE_NON_I18N } from '@kbn/security-solution-plugin/public/assistant/content/prompts/user/translations'; +import { QUICK_PROMPT_BADGE, USER_PROMPT } from '../../screens/ai_assistant'; +import { createRule } from '../../tasks/api_calls/rules'; +import { + assertErrorResponse, + assertMessageSent, + assertSystemPrompt, + clearSystemPrompt, + createQuickPrompt, + createSystemPrompt, + openAssistant, + resetConversation, + selectConversation, + selectSystemPrompt, + sendQuickPrompt, + typeAndSendMessage, +} from '../../tasks/assistant'; +import { deleteConversations, deletePrompts } from '../../tasks/api_calls/assistant'; +import { createAzureConnector } from '../../tasks/api_calls/connectors'; +import { deleteConnectors } from '../../tasks/api_calls/common'; +import { login } from '../../tasks/login'; +import { visit, visitGetStartedPage } from '../../tasks/navigation'; +import { getNewRule } from '../../objects/rule'; +import { ALERTS_URL } from '../../urls/navigation'; +import { waitForAlertsToPopulate } from '../../tasks/create_new_rule'; +import { expandFirstAlert } from '../../tasks/alerts'; + +const testPrompt = { + title: 'Cool prompt', + prompt: 'This is a super cool prompt.', +}; +describe('AI Assistant Prompts', { tags: ['@ess', '@serverless'] }, () => { + beforeEach(() => { + deleteConnectors(); + deleteConversations(); + deletePrompts(); + login(); + createAzureConnector(); + }); + + describe('System Prompts', () => { + it('Deselecting default system prompt prevents prompt from being sent. When conversation is then cleared, the prompt is reset.', () => { + visitGetStartedPage(); + openAssistant(); + clearSystemPrompt(); + typeAndSendMessage('hello'); + assertMessageSent('hello'); + // ensure response before clearing convo + assertErrorResponse(); + resetConversation(); + typeAndSendMessage('hello'); + assertMessageSent('hello', true); + }); + + it('Last selected system prompt persists in conversation', () => { + visitGetStartedPage(); + openAssistant(); + selectSystemPrompt('Enhanced system prompt'); + typeAndSendMessage('hello'); + assertMessageSent('hello', true, SUPERHERO_SYSTEM_PROMPT_NON_I18N); + resetConversation(); + assertSystemPrompt('Enhanced system prompt'); + selectConversation('Alert summary'); + assertSystemPrompt('Default system prompt'); + selectConversation('Welcome'); + assertSystemPrompt('Enhanced system prompt'); + }); + + it('Add prompt from system prompt selector without setting a default conversation', () => { + visitGetStartedPage(); + openAssistant(); + createSystemPrompt(testPrompt.title, testPrompt.prompt); + // we did not set a default conversation, so the prompt should not be set + assertSystemPrompt('Default system prompt'); + selectSystemPrompt(testPrompt.title); + typeAndSendMessage('hello'); + assertMessageSent('hello', true, testPrompt.prompt); + }); + + it('Add prompt from system prompt selector and set multiple conversations (including current) as default conversation', () => { + visitGetStartedPage(); + openAssistant(); + createSystemPrompt(testPrompt.title, testPrompt.prompt, ['Welcome', 'Alert summary']); + assertSystemPrompt(testPrompt.title); + typeAndSendMessage('hello'); + assertMessageSent('hello', true, testPrompt.prompt); + // ensure response before changing convo + assertErrorResponse(); + selectConversation('Alert summary'); + assertSystemPrompt(testPrompt.title); + typeAndSendMessage('hello'); + assertMessageSent('hello', true, testPrompt.prompt); + }); + }); + describe('User Prompts', () => { + it('Add a quick prompt and send it in the conversation', () => { + visitGetStartedPage(); + openAssistant(); + createQuickPrompt(testPrompt.title, testPrompt.prompt); + sendQuickPrompt(testPrompt.title); + assertMessageSent(testPrompt.prompt, true); + }); + it('Add a quick prompt with context and it is only available in the selected context', () => { + visitGetStartedPage(); + openAssistant(); + createQuickPrompt(testPrompt.title, testPrompt.prompt, ['Alert (from view)']); + cy.get(QUICK_PROMPT_BADGE(testPrompt.title)).should('not.exist'); + createRule(getNewRule()); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlert(); + openAssistant('alert'); + cy.get(QUICK_PROMPT_BADGE(testPrompt.title)).should('be.visible'); + cy.get(USER_PROMPT).should( + 'have.text', + EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE_NON_I18N + ); + cy.get(QUICK_PROMPT_BADGE(testPrompt.title)).click(); + cy.get(USER_PROMPT).should('have.text', testPrompt.prompt); + }); + // TODO delete quick prompt + // I struggled to do this since the element is hidden with css and I cannot get it to show + }); +}); diff --git a/x-pack/test/security_solution_cypress/cypress/objects/assistant.ts b/x-pack/test/security_solution_cypress/cypress/objects/assistant.ts new file mode 100644 index 0000000000000..7401a0ced2a49 --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/objects/assistant.ts @@ -0,0 +1,40 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + ConversationCategory, + ConversationCreateProps, + ConversationResponse, + Provider, +} from '@kbn/elastic-assistant-common'; + +export const getMockConversation = (body?: Partial) => ({ + title: 'Test Conversation', + apiConfig: { + actionTypeId: '.gen-ai', + connectorId: '', + defaultSystemPromptId: 'default-system-prompt', + model: 'test-model', + provider: 'OpenAI' as Provider, + }, + excludeFromLastConversationStorage: false, + isDefault: false, + messages: [], + replacements: {}, + category: 'assistant' as ConversationCategory, + ...body, +}); + +export const getMockConversationResponse = ( + body?: Partial +): ConversationResponse => ({ + id: 'test-conversation-id', + createdAt: '2023-10-31T00:00:00.000Z', + users: [{ id: 'elastic', name: 'elastic@elastic.co' }], + namespace: 'default', + ...getMockConversation(body), +}); diff --git a/x-pack/test/security_solution_cypress/cypress/screens/ai_assistant.ts b/x-pack/test/security_solution_cypress/cypress/screens/ai_assistant.ts index 2abd9208dd070..341b65bf722e0 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/ai_assistant.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/ai_assistant.ts @@ -5,4 +5,49 @@ * 2.0. */ +export const ADD_NEW_CONNECTOR = '[data-test-subj="addNewConnectorButton"]'; +export const ADD_QUICK_PROMPT = '[data-test-subj="addQuickPrompt"]'; +export const ASSISTANT_SETTINGS_BUTTON = 'button[data-test-subj="settings"]'; export const AI_ASSISTANT_BUTTON = '[data-test-subj="assistantHeaderLink"]'; +export const ASSISTANT_CHAT_BODY = '[data-test-subj="assistantChat"]'; +export const CHAT_CONTEXT_MENU = '[data-test-subj="chat-context-menu"]'; +export const CHAT_ICON = '[data-test-subj="newChat"]'; +export const CHAT_ICON_SM = '[data-test-subj="newChatByTitle"]'; +export const CLEAR_CHAT = '[data-test-subj="clear-chat"]'; +export const CLEAR_SYSTEM_PROMPT = '[data-test-subj="clearSystemPrompt"]'; +export const CONFIRM_CLEAR_CHAT = '[data-test-subj="confirmModalConfirmButton"]'; +export const CONNECTOR_MISSING_CALLOUT = '[data-test-subj="connectorMissingCallout"]'; +export const CONNECTOR_SELECT = (c: string) => `[data-test-subj="connector-${c}"]`; +export const CONNECTOR_SELECTOR = '[data-test-subj="connector-selector"]'; +export const CONVERSATION_MESSAGE = '[data-test-subj="messageText"]'; +export const CONVERSATION_MESSAGE_ERROR = + '[data-test-subj="errorComment"] [data-test-subj="messageText"]'; +export const CONVERSATION_MULTI_SELECTOR = + '[data-test-subj="conversationMultiSelector"] [data-test-subj="comboBoxSearchInput"]'; +export const CONVERSATION_SELECT = (c: string) => `[data-test-subj="conversation-select-${c}"]`; +export const CONVERSATION_TITLE = '[data-test-subj="conversationTitle"]'; +export const CONVERSATION_TITLE_SAVE_BUTTON = '[data-test-subj="euiInlineEditModeSaveButton"]'; +export const CREATE_SYSTEM_PROMPT = '[data-test-subj="addSystemPrompt"]'; +export const EMPTY_CONVO = '[data-test-subj="emptyConvo"]'; +export const FLYOUT_NAV_TOGGLE = '[data-test-subj="aiAssistantFlyoutNavigationToggle"]'; +export const MODAL_SAVE_BUTTON = '[data-test-subj="save-button"]'; +export const NEW_CHAT = '[data-test-subj="newChatFromOverlay"]'; +export const PROMPT_CONTEXT_SELECTOR = + '[data-test-subj="promptContextSelector"] [data-test-subj="comboBoxSearchInput"]'; +export const PROMPT_CONTEXT_BUTTON = (i: string | number) => + `[data-test-subj="selectedPromptContext-${i}-button"]`; +export const QUICK_PROMPT_TITLE_INPUT = + '[data-test-subj="quickPromptSelector"] [data-test-subj="comboBoxSearchInput"]'; +export const QUICK_PROMPT_BADGE = (b: string) => `[data-test-subj="quickPrompt-${b}"]`; +export const QUICK_PROMPT_BODY_INPUT = '[data-test-subj="quick-prompt-prompt"]'; +export const SEND_TO_TIMELINE_BUTTON = '[data-test-subj="sendToTimelineEmptyButton"]'; +export const SHOW_ANONYMIZED_BUTTON = '[data-test-subj="showAnonymizedValues"]'; +export const SUBMIT_CHAT = '[data-test-subj="submit-chat"]'; +export const SYSTEM_PROMPT = '[data-test-subj="systemPromptText"]'; +export const SYSTEM_PROMPT_BODY_INPUT = '[data-test-subj="systemPromptModalPromptText"]'; +export const SYSTEM_PROMPT_TITLE_INPUT = + '[data-test-subj="systemPromptSelector"] [data-test-subj="comboBoxSearchInput"]'; +export const SYSTEM_PROMPT_SELECT = (c: string) => `[data-test-subj="systemPrompt-${c}"]`; +export const UPGRADE_CTA = '[data-test-subj="upgradeLicenseCallToAction"]'; +export const USER_PROMPT = '[data-test-subj="prompt-textarea"]'; +export const WELCOME_SETUP = '[data-test-subj="welcome-setup"]'; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/assistant.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/assistant.ts new file mode 100644 index 0000000000000..a8898f1652e1e --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/assistant.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ConversationCreateProps, ConversationResponse } from '@kbn/elastic-assistant-common'; +import { deleteAllDocuments } from './elasticsearch'; +import { getMockConversation } from '../../objects/assistant'; +import { getSpaceUrl } from '../space'; +import { rootRequest, waitForRootRequest } from './common'; + +const createConversation = ( + body?: Partial +): Cypress.Chainable> => + cy.currentSpace().then((spaceId) => + rootRequest({ + method: 'POST', + url: spaceId + ? getSpaceUrl(spaceId, `api/security_ai_assistant/current_user/conversations`) + : `api/security_ai_assistant/current_user/conversations`, + body: getMockConversation(body), + }) + ); + +export const waitForConversation = (body?: Partial) => + waitForRootRequest(createConversation(body)); + +export const deleteConversations = () => { + cy.log('Delete all conversations'); + deleteAllDocuments(`.kibana-elastic-ai-assistant-conversations-*`); +}; + +export const deletePrompts = () => { + cy.log('Delete all prompts'); + deleteAllDocuments(`.kibana-elastic-ai-assistant-prompts-*`); +}; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/common.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/common.ts index e25b15b9b4439..7c78858070a20 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/common.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/common.ts @@ -40,6 +40,19 @@ export const rootRequest = ({ ...restOptions, }); +// a helper function to wait for the root request to be successful +// defaults to 5 second intervals for 3 attempts +// can be helpful when waiting for a resource to be created before proceeding +export const waitForRootRequest = ( + fn: Cypress.Chainable>, + interval = 5000, + timeout = 15000 +) => + cy.waitUntil(() => fn.then((response) => cy.wrap(response.status === 200)), { + interval, + timeout, + }); + export const deleteAlertsAndRules = () => { cy.log('Delete all alerts and rules'); diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/connectors.ts b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/connectors.ts index 38936d78cc360..58b679d6ebe01 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/connectors.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/api_calls/connectors.ts @@ -21,4 +21,31 @@ const slackConnectorAPIPayload = { name: 'Slack cypress test e2e connector', }; +export const azureConnectorAPIPayload = { + actionTypeId: '.gen-ai', + secrets: { + apiKey: '123', + }, + config: { + apiUrl: + 'https://goodurl.com/openai/deployments/good-gpt4o/chat/completions?api-version=2024-02-15-preview', + apiProvider: 'Azure OpenAI', + }, + name: 'Azure OpenAI cypress test e2e connector', +}; + +export const bedrockConnectorAPIPayload = { + actionTypeId: '.bedrock', + secrets: { + accessKey: '123', + secret: '123', + }, + config: { + apiUrl: 'https://bedrock.com', + }, + name: 'Bedrock cypress test e2e connector', +}; + export const createSlackConnector = () => createConnector(slackConnectorAPIPayload); +export const createAzureConnector = () => createConnector(azureConnectorAPIPayload); +export const createBedrockConnector = () => createConnector(bedrockConnectorAPIPayload); diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/assistant.ts b/x-pack/test/security_solution_cypress/cypress/tasks/assistant.ts new file mode 100644 index 0000000000000..c3f4f0cc970e0 --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/tasks/assistant.ts @@ -0,0 +1,218 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DEFAULT_SYSTEM_PROMPT_NON_I18N } from '@kbn/security-solution-plugin/public/assistant/content/prompts/system/translations'; +import { TIMELINE_CHECKBOX } from '../screens/timelines'; +import { CLOSE_FLYOUT } from '../screens/alerts'; +import { + AI_ASSISTANT_BUTTON, + ASSISTANT_CHAT_BODY, + CHAT_ICON, + CHAT_ICON_SM, + CONNECTOR_SELECT, + CONNECTOR_SELECTOR, + CONVERSATION_TITLE, + EMPTY_CONVO, + WELCOME_SETUP, + NEW_CHAT, + CONVERSATION_SELECT, + FLYOUT_NAV_TOGGLE, + CONVERSATION_MESSAGE, + USER_PROMPT, + SUBMIT_CHAT, + CONVERSATION_MESSAGE_ERROR, + CLEAR_SYSTEM_PROMPT, + CHAT_CONTEXT_MENU, + CLEAR_CHAT, + CONFIRM_CLEAR_CHAT, + SYSTEM_PROMPT_SELECT, + SYSTEM_PROMPT, + CREATE_SYSTEM_PROMPT, + SYSTEM_PROMPT_TITLE_INPUT, + SYSTEM_PROMPT_BODY_INPUT, + CONVERSATION_MULTI_SELECTOR, + MODAL_SAVE_BUTTON, + ADD_QUICK_PROMPT, + QUICK_PROMPT_TITLE_INPUT, + QUICK_PROMPT_BODY_INPUT, + PROMPT_CONTEXT_SELECTOR, + QUICK_PROMPT_BADGE, + ADD_NEW_CONNECTOR, + SHOW_ANONYMIZED_BUTTON, + ASSISTANT_SETTINGS_BUTTON, + SEND_TO_TIMELINE_BUTTON, +} from '../screens/ai_assistant'; +import { TOASTER } from '../screens/alerts_detection_rules'; + +export const openAssistant = (context?: 'rule' | 'alert') => { + if (!context) { + cy.get(AI_ASSISTANT_BUTTON).click(); + return; + } + if (context === 'rule') { + cy.get(CHAT_ICON).should('be.visible'); + cy.get(CHAT_ICON).click(); + return; + } + if (context === 'alert') { + cy.get(CHAT_ICON_SM).should('be.visible'); + cy.get(CHAT_ICON_SM).click(); + return; + } +}; + +export const closeAssistant = () => { + cy.get(`${ASSISTANT_CHAT_BODY} ${CLOSE_FLYOUT}`).click(); +}; + +export const createNewChat = () => { + cy.get(`${NEW_CHAT}`).click(); +}; + +export const selectConnector = (connectorName: string) => { + cy.get(CONNECTOR_SELECTOR).click(); + cy.get(CONNECTOR_SELECT(connectorName)).click(); + assertConnectorSelected(connectorName); +}; +export const resetConversation = () => { + cy.get(CHAT_CONTEXT_MENU).click(); + cy.get(CLEAR_CHAT).click(); + cy.get(CONFIRM_CLEAR_CHAT).click(); + cy.get(EMPTY_CONVO).should('be.visible'); +}; +export const selectConversation = (conversationName: string) => { + cy.get(FLYOUT_NAV_TOGGLE).click(); + cy.get(CONVERSATION_SELECT(conversationName)).click(); + cy.get(CONVERSATION_TITLE + ' h2').should('have.text', conversationName); + cy.get(FLYOUT_NAV_TOGGLE).click(); +}; + +export const updateConversationTitle = (newTitle: string) => { + cy.get(CONVERSATION_TITLE + ' h2').click(); + cy.get(CONVERSATION_TITLE + ' input').clear(); + cy.get(CONVERSATION_TITLE + ' input').type(newTitle); + cy.get(CONVERSATION_TITLE + ' input').type('{enter}'); + cy.get(CONVERSATION_TITLE + ' h2').should('have.text', newTitle); +}; + +export const typeAndSendMessage = (message: string) => { + cy.get(USER_PROMPT).type(message); + cy.get(SUBMIT_CHAT).click(); +}; + +export const sendQueryToTimeline = () => { + cy.get(SEND_TO_TIMELINE_BUTTON).click(); +}; + +export const clearSystemPrompt = () => { + cy.get(CLEAR_SYSTEM_PROMPT).click(); +}; + +export const sendQuickPrompt = (prompt: string) => { + cy.get(QUICK_PROMPT_BADGE(prompt)).click(); + cy.get(SUBMIT_CHAT).click(); +}; + +export const selectSystemPrompt = (systemPrompt: string) => { + cy.get(SYSTEM_PROMPT).click(); + cy.get(SYSTEM_PROMPT_SELECT(systemPrompt)).click(); + assertSystemPrompt(systemPrompt); +}; + +export const createSystemPrompt = ( + title: string, + prompt: string, + defaultConversations?: string[] +) => { + cy.get(SYSTEM_PROMPT).click(); + cy.get(CREATE_SYSTEM_PROMPT).click(); + cy.get(SYSTEM_PROMPT_TITLE_INPUT).type(`${title}{enter}`); + cy.get(SYSTEM_PROMPT_BODY_INPUT).type(prompt); + if (defaultConversations && defaultConversations.length) { + defaultConversations.forEach((conversation) => { + cy.get(CONVERSATION_MULTI_SELECTOR).type(`${conversation}{enter}`); + }); + } + cy.get(MODAL_SAVE_BUTTON).click(); +}; + +export const createQuickPrompt = ( + title: string, + prompt: string, + defaultConversations?: string[] +) => { + cy.get(ADD_QUICK_PROMPT).click(); + cy.get(QUICK_PROMPT_TITLE_INPUT).type(`${title}{enter}`); + cy.get(QUICK_PROMPT_BODY_INPUT).type(prompt); + if (defaultConversations && defaultConversations.length) { + defaultConversations.forEach((conversation) => { + cy.get(PROMPT_CONTEXT_SELECTOR).type(`${conversation}{enter}`); + }); + } + cy.get(MODAL_SAVE_BUTTON).click(); +}; + +export const selectRule = (ruleId: string) => { + // not be.visible because of eui css + cy.get(TIMELINE_CHECKBOX(ruleId)).should('exist'); + cy.get(TIMELINE_CHECKBOX(ruleId)).click(); +}; + +/** + * Assertions + */ +export const assertNewConversation = (isWelcome: boolean, title: string) => { + if (isWelcome) { + cy.get(WELCOME_SETUP).should('be.visible'); + } else { + cy.get(EMPTY_CONVO).should('be.visible'); + } + cy.get(CONVERSATION_TITLE + ' h2').should('have.text', title); +}; + +export const assertMessageSent = (message: string, hasDefaultPrompt = false, prompt?: string) => { + cy.get(CONVERSATION_MESSAGE) + .first() + .should( + 'contain', + hasDefaultPrompt ? `${prompt ?? DEFAULT_SYSTEM_PROMPT_NON_I18N}\n${message}` : message + ); +}; + +export const assertErrorResponse = () => { + cy.get(CONVERSATION_MESSAGE_ERROR).should('be.visible'); +}; + +export const assertSystemPrompt = (systemPrompt: string) => { + cy.get(SYSTEM_PROMPT).should('have.text', systemPrompt); +}; + +export const assertConnectorSelected = (connectorName: string) => { + cy.get(CONNECTOR_SELECTOR).should('have.text', connectorName); +}; + +export const assertErrorToastShown = (message?: string) => { + cy.get(TOASTER).should('be.visible'); + if (message?.length) { + cy.get(TOASTER).should('contain', message); + } +}; + +const assertConversationTitleReadOnly = () => { + cy.get(CONVERSATION_TITLE + ' h2').click(); + cy.get(CONVERSATION_TITLE + ' input').should('not.exist'); +}; + +export const assertConversationReadOnly = () => { + assertConversationTitleReadOnly(); + cy.get(ADD_NEW_CONNECTOR).should('be.disabled'); + cy.get(SHOW_ANONYMIZED_BUTTON).should('be.disabled'); + cy.get(CHAT_CONTEXT_MENU).should('be.disabled'); + cy.get(FLYOUT_NAV_TOGGLE).should('be.disabled'); + cy.get(NEW_CHAT).should('be.disabled'); + cy.get(ASSISTANT_SETTINGS_BUTTON).should('be.disabled'); +}; diff --git a/x-pack/test/security_solution_cypress/cypress/tsconfig.json b/x-pack/test/security_solution_cypress/cypress/tsconfig.json index b7223115bbcb6..36ef2376ec1ad 100644 --- a/x-pack/test/security_solution_cypress/cypress/tsconfig.json +++ b/x-pack/test/security_solution_cypress/cypress/tsconfig.json @@ -43,5 +43,6 @@ "@kbn/alerts-ui-shared", "@kbn/securitysolution-endpoint-exceptions-common", "@kbn/repo-info", + "@kbn/elastic-assistant-common", ] } diff --git a/x-pack/test/threat_intelligence_cypress/config.ts b/x-pack/test/threat_intelligence_cypress/config.ts index c2e9a6ac8b180..963cfab55dad5 100644 --- a/x-pack/test/threat_intelligence_cypress/config.ts +++ b/x-pack/test/threat_intelligence_cypress/config.ts @@ -6,8 +6,9 @@ */ import { FtrConfigProviderContext } from '@kbn/test'; + import { CA_CERT_PATH } from '@kbn/dev-utils'; -import { services } from './services'; + export default async function ({ readConfigFile }: FtrConfigProviderContext) { const kibanaCommonTestsConfig = await readConfigFile( require.resolve('@kbn/test-suites-src/common/config') @@ -19,8 +20,6 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { return { ...kibanaCommonTestsConfig.getAll(), - services, - esTestCluster: { ...xpackFunctionalTestsConfig.get('esTestCluster'), serverArgs: [ diff --git a/x-pack/test/upgrade_assistant_integration/config.js b/x-pack/test/upgrade_assistant_integration/config.js index 9529e4bc568d3..dbdf0ade8affe 100644 --- a/x-pack/test/upgrade_assistant_integration/config.js +++ b/x-pack/test/upgrade_assistant_integration/config.js @@ -5,8 +5,6 @@ * 2.0. */ -import { commonFunctionalServices } from '@kbn/ftr-common-functional-services'; - export default async function ({ readConfigFile }) { // Read the Kibana API integration tests config file so that we can utilize its services. const kibanaAPITestsConfig = await readConfigFile( @@ -15,12 +13,15 @@ export default async function ({ readConfigFile }) { const xPackFunctionalTestsConfig = await readConfigFile( require.resolve('../functional/config.base.js') ); + const kibanaCommonConfig = await readConfigFile( + require.resolve('@kbn/test-suites-src/common/config') + ); return { testFiles: [require.resolve('./upgrade_assistant')], servers: xPackFunctionalTestsConfig.get('servers'), services: { - ...commonFunctionalServices, + ...kibanaCommonConfig.get('services'), supertest: kibanaAPITestsConfig.get('services.supertest'), }, junit: { diff --git a/x-pack/test_serverless/api_integration/test_suites/common/console/autocomplete_entities.ts b/x-pack/test_serverless/api_integration/test_suites/common/console/autocomplete_entities.ts index 3ec162ad28ffd..72640103c0ef9 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/console/autocomplete_entities.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/console/autocomplete_entities.ts @@ -11,7 +11,7 @@ import { InternalRequestHeader, RoleCredentials } from '../../../../shared/servi export default ({ getService }: FtrProviderContext) => { const svlCommonApi = getService('svlCommonApi'); - const console = getService('console'); + const consoleService = getService('console'); const svlUserManager = getService('svlUserManager'); const supertestWithoutAuth = getService('supertestWithoutAuth'); @@ -27,6 +27,17 @@ export default ({ getService }: FtrProviderContext) => { }; describe('/api/console/autocomplete_entities', function () { + let createIndex: (typeof consoleService)['helpers']['createIndex']; + let createAlias: (typeof consoleService)['helpers']['createAlias']; + let createIndexTemplate: (typeof consoleService)['helpers']['createIndexTemplate']; + let createComponentTemplate: (typeof consoleService)['helpers']['createComponentTemplate']; + let createDataStream: (typeof consoleService)['helpers']['createDataStream']; + let deleteIndex: (typeof consoleService)['helpers']['deleteIndex']; + let deleteAlias: (typeof consoleService)['helpers']['deleteAlias']; + let deleteIndexTemplate: (typeof consoleService)['helpers']['deleteIndexTemplate']; + let deleteComponentTemplate: (typeof consoleService)['helpers']['deleteComponentTemplate']; + let deleteDataStream: (typeof consoleService)['helpers']['deleteDataStream']; + const indexName = 'test-index-1'; const aliasName = 'test-alias-1'; const indexTemplateName = 'test-index-template-1'; @@ -36,26 +47,36 @@ export default ({ getService }: FtrProviderContext) => { before(async () => { roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); internalRequestHeader = svlCommonApi.getInternalRequestHeader(); + ({ + helpers: { + createIndex, + createAlias, + createIndexTemplate, + createComponentTemplate, + createDataStream, + deleteIndex, + deleteAlias, + deleteIndexTemplate, + deleteComponentTemplate, + deleteDataStream, + }, + } = consoleService); // Setup indices, aliases, templates, and data streams - await console.createIndex(indexName); - await console.createAlias(indexName, aliasName); - await console.createComponentTemplate(componentTemplateName); - await console.createIndexTemplate( - indexTemplateName, - [dataStreamName], - [componentTemplateName] - ); - await console.createDataStream(dataStreamName); + await createIndex(indexName); + await createAlias(indexName, aliasName); + await createComponentTemplate(componentTemplateName); + await createIndexTemplate(indexTemplateName, [dataStreamName], [componentTemplateName]); + await createDataStream(dataStreamName); }); after(async () => { // Cleanup indices, aliases, templates, and data streams - await console.deleteAlias(indexName, aliasName); - await console.deleteIndex(indexName); - await console.deleteDataStream(dataStreamName); - await console.deleteIndexTemplate(indexTemplateName); - await console.deleteComponentTemplate(componentTemplateName); + await deleteAlias(indexName, aliasName); + await deleteIndex(indexName); + await deleteDataStream(dataStreamName); + await deleteIndexTemplate(indexTemplateName); + await deleteComponentTemplate(componentTemplateName); await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); }); diff --git a/x-pack/test_serverless/shared/services/bsearch_secure.ts b/x-pack/test_serverless/shared/services/bsearch_secure.ts index 03f8241c9e12a..7ebe89bed8247 100644 --- a/x-pack/test_serverless/shared/services/bsearch_secure.ts +++ b/x-pack/test_serverless/shared/services/bsearch_secure.ts @@ -5,8 +5,8 @@ * 2.0. */ -// NOTE: This is pretty much a copy/paste from packages/kbn-ftr-common-functional-services/services/bsearch.ts -// but with the ability to provide custom auth +// NOTE: This is pretty much a copy/paste from test/common/services/bsearch.ts but with the ability +// to provide custom auth import expect from '@kbn/expect'; import { GenericFtrService } from '@kbn/test'; diff --git a/yarn.lock b/yarn.lock index acc904dd83ec8..02824f4b088e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14100,10 +14100,10 @@ chrome-trace-event@^1.0.2: dependencies: tslib "^1.9.0" -chromedriver@^127.0.3: - version "127.0.3" - resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-127.0.3.tgz#33abca5924eb809e0e6ef2dd30eaa8cf7dba58d4" - integrity sha512-trUHkFt0n7jGzNOgkO1srOJfz50kKyAGJ016PyV0hrtyKNIGnOC9r3Jlssz19UoEjSzI/1g2shEiIFtDbBYVaw== +chromedriver@^128.0.0: + version "128.0.0" + resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-128.0.0.tgz#7f75a984101199e0bcc2c92fe9f91917fcd1f918" + integrity sha512-Ggo21z/dFQxTOTgU0vm0V59Mi79yyR+9AUk/KiVAsRfbDRdVZQYQWfgxnIvD/x8KOKn0oB7haRzDO/KfrKyvOA== dependencies: "@testim/chrome-version" "^1.1.4" axios "^1.7.4"