diff --git a/.buildkite/pipelines/fips.yml b/.buildkite/pipelines/fips.yml index f4a5b3623bbcc..e04a5ecb8f936 100644 --- a/.buildkite/pipelines/fips.yml +++ b/.buildkite/pipelines/fips.yml @@ -40,14 +40,15 @@ steps: machineType: n2-standard-2 preemptible: true - - command: .buildkite/scripts/steps/fips/smoke_test.sh - label: 'Pick Smoke Test Group Run Order' + - command: .buildkite/scripts/steps/test/pick_test_group_run_order.sh + label: 'Pick Test Group Run Order' depends_on: build timeout_in_minutes: 10 env: FTR_CONFIGS_SCRIPT: '.buildkite/scripts/steps/test/ftr_configs.sh' FTR_EXTRA_ARGS: '$FTR_EXTRA_ARGS' - LIMIT_CONFIG_TYPE: 'functional' + JEST_UNIT_SCRIPT: '.buildkite/scripts/steps/test/jest.sh' + JEST_INTEGRATION_SCRIPT: '.buildkite/scripts/steps/test/jest_integration.sh' retry: automatic: - exit_status: '*' diff --git a/.buildkite/scripts/steps/fips/smoke_test.sh b/.buildkite/scripts/steps/fips/smoke_test.sh deleted file mode 100755 index 685bb111ff81a..0000000000000 --- a/.buildkite/scripts/steps/fips/smoke_test.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -# Limit the FTR configs for now to avoid running all the tests. Once we're -# ready to utilize the full FTR suite in FIPS mode, we can remove this file and -# call pick_test_group_run_order.sh directly in .buildkite/pipelines/fips.yml. -configs=( - "x-pack/test/reporting_functional/reporting_and_security.config.ts" - "x-pack/test/saved_object_api_integration/security_and_spaces/config_trial.ts" - "x-pack/test/alerting_api_integration/security_and_spaces/group1/config.ts" - "x-pack/test/alerting_api_integration/security_and_spaces/group2/config.ts" - "x-pack/test/alerting_api_integration/security_and_spaces/group3/config.ts" - "x-pack/test/alerting_api_integration/security_and_spaces/group4/config.ts" - "x-pack/test/functional/apps/saved_objects_management/config.ts" - "x-pack/test/functional/apps/user_profiles/config.ts" - "x-pack/test/functional/apps/security/config.ts" -) - -printf -v FTR_CONFIG_PATTERNS '%s,' "${configs[@]}" -FTR_CONFIG_PATTERNS="${FTR_CONFIG_PATTERNS%,}" -export FTR_CONFIG_PATTERNS - -.buildkite/scripts/steps/test/pick_test_group_run_order.sh diff --git a/.buildkite/scripts/steps/test/jest_parallel.sh b/.buildkite/scripts/steps/test/jest_parallel.sh index 2a7cf780f5787..648c3b225141d 100755 --- a/.buildkite/scripts/steps/test/jest_parallel.sh +++ b/.buildkite/scripts/steps/test/jest_parallel.sh @@ -60,7 +60,14 @@ while read -r config; do # --trace-warnings to debug # Node.js process-warning detected: # Warning: Closing file descriptor 24 on garbage collection - cmd="NODE_OPTIONS=\"--max-old-space-size=12288 --trace-warnings\" node ./scripts/jest --config=\"$config\" $parallelism --coverage=false --passWithNoTests" + cmd="NODE_OPTIONS=\"--max-old-space-size=12288 --trace-warnings" + + if [ "${KBN_ENABLE_FIPS:-}" == "true" ]; then + cmd=$cmd" --enable-fips --openssl-config=$HOME/nodejs.cnf" + fi + + cmd=$cmd"\" node ./scripts/jest --config=\"$config\" $parallelism --coverage=false --passWithNoTests" + echo "actual full command is:" echo "$cmd" echo "" diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 030f888c3e9ba..268f1d5148d43 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -659,7 +659,7 @@ src/plugins/files @elastic/appex-sharedux src/plugins/files_management @elastic/appex-sharedux src/plugins/ftr_apis @elastic/kibana-core src/plugins/guided_onboarding @elastic/appex-sharedux -src/plugins/home @elastic/kibana-core +src/plugins/home @elastic/appex-sharedux src/plugins/image_embeddable @elastic/appex-sharedux src/plugins/input_control_vis @elastic/kibana-presentation src/plugins/inspector @elastic/kibana-presentation @@ -768,6 +768,7 @@ x-pack/examples/triggers_actions_ui_example @elastic/response-ops x-pack/examples/ui_actions_enhanced_examples @elastic/appex-sharedux x-pack/packages/ai-infra/inference-common @elastic/appex-ai-infra x-pack/packages/ai-infra/product-doc-artifact-builder @elastic/appex-ai-infra +x-pack/packages/ai-infra/product-doc-common @elastic/appex-ai-infra x-pack/packages/index-lifecycle-management/index_lifecycle_management_common_shared @elastic/kibana-management x-pack/packages/index-management/index_management_shared_types @elastic/kibana-management x-pack/packages/kbn-ai-assistant @elastic/search-kibana @@ -857,6 +858,8 @@ x-pack/packages/security/role_management_model @elastic/kibana-security x-pack/packages/security/ui_components @elastic/kibana-security x-pack/performance @elastic/appex-qa x-pack/plugins/actions @elastic/response-ops +x-pack/plugins/ai_infra/llm_tasks @elastic/appex-ai-infra +x-pack/plugins/ai_infra/product_doc_base @elastic/appex-ai-infra x-pack/plugins/aiops @elastic/ml-ui x-pack/plugins/alerting @elastic/response-ops x-pack/plugins/banners @elastic/appex-sharedux @@ -1039,9 +1042,21 @@ x-pack/test_serverless/api_integration/test_suites/common/platform_security @ela /x-pack/plugins/entity_manager @elastic/obs-entities /x-pack/test/api_integration/apis/entity_manager @elastic/obs-entities + # Data Discovery -/test/plugin_functional/plugins/data_search @elastic/kibana-data-discovery +/test/functional/fixtures/es_archiver/alias @elastic/kibana-data-discovery +/test/functional/page_objects/context_page.ts @elastic/kibana-data-discovery +/test/functional/services/data_views.ts @elastic/kibana-data-discovery +/test/functional/services/saved_objects_finder.ts @elastic/kibana-data-discovery /test/plugin_functional/plugins/index_patterns @elastic/kibana-data-discovery +/test/plugin_functional/plugins/data_search @elastic/kibana-data-discovery +/test/functional/page_objects/discover_page.ts @elastic/kibana-data-discovery +/test/functional/fixtures/es_archiver/index_pattern_without_timefield @elastic/kibana-data-discovery +/test/functional/fixtures/es_archiver/huge_fields @elastic/kibana-data-discovery +/test/functional/fixtures/es_archiver/date_n* @elastic/kibana-data-discovery +/test/functional/firefox/discover.config.ts @elastic/kibana-data-discovery +/test/functional/fixtures/es_archiver/discover @elastic/kibana-data-discovery +/test/api_integration/apis/saved_queries @elastic/kibana-data-discovery /x-pack/test/api_integration/apis/kibana/kql_telemetry @elastic/kibana-data-discovery @elastic/kibana-visualizations /x-pack/test_serverless/functional/es_archives/pre_calculated_histogram @elastic/kibana-data-discovery /x-pack/test_serverless/functional/es_archives/kibana_sample_data_flights_index_pattern @elastic/kibana-data-discovery @@ -1096,7 +1111,6 @@ x-pack/test_serverless/api_integration/test_suites/common/platform_security @ela src/plugins/discover/public/context_awareness/profile_providers/security @elastic/kibana-data-discovery @elastic/security-threat-hunting-investigations # Platform Docs -/x-pack/test/functional/services/sample_data @elastic/platform-docs /x-pack/test_serverless/functional/test_suites/security/screenshot_creation/index.ts @elastic/platform-docs /x-pack/test_serverless/functional/test_suites/security/config.screenshots.ts @elastic/platform-docs /x-pack/test/screenshot_creation @elastic/platform-docs @@ -2158,6 +2172,7 @@ x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout @elastic/ /x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/ @elastic/security-defend-workflows /x-pack/plugins/security_solution/common/endpoint/ @elastic/security-defend-workflows /x-pack/plugins/security_solution/common/api/endpoint/ @elastic/security-defend-workflows +x-pack/plugins/security_solution/server/assistant/tools/defend_insights @elastic/security-defend-workflows /x-pack/plugins/security_solution/server/endpoint/ @elastic/security-defend-workflows /x-pack/plugins/security_solution/server/lists_integration/endpoint/ @elastic/security-defend-workflows /x-pack/plugins/security_solution/server/lib/license/ @elastic/security-defend-workflows @@ -2169,6 +2184,10 @@ x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout @elastic/ /x-pack/plugins/security_solution_serverless/public/upselling/sections/endpoint_management @elastic/security-defend-workflows /x-pack/plugins/security_solution_serverless/public/upselling/pages/endpoint_management @elastic/security-defend-workflows /x-pack/plugins/security_solution_serverless/server/endpoint @elastic/security-defend-workflows +x-pack/packages/kbn-elastic-assistant-common/impl/schemas/defend_insights @elastic/security-defend-workflows +x-pack/plugins/elastic_assistant/server/__mocks__/defend_insights_schema.mock.ts @elastic/security-defend-workflows +x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/defend_insights @elastic/security-defend-workflows +x-pack/plugins/elastic_assistant/server/routes/defend_insights @elastic/security-defend-workflows ## Security Solution sub teams - security-telemetry (Data Engineering) x-pack/plugins/security_solution/server/usage/ @elastic/security-data-analytics @@ -2270,6 +2289,10 @@ x-pack/test/profiling_api_integration @elastic/obs-ux-infra_services-team x-pack/plugins/observability_solution/observability_shared/public/components/profiling @elastic/obs-ux-infra_services-team # Shared UX +/test/api_integration/apis/short_url/**/*.ts @elastic/appex-sharedux # Assigned per https://github.com/elastic/kibana/pull/200209/files#r1846654156 +/test/functional/page_objects/share_page.ts @elastic/appex-sharedux # Assigned per https://github.com/elastic/kibana/pull/200209/files#r1846648444 +/test/accessibility/apps/kibana_overview_* @elastic/appex-sharedux # Assigned per https://github.com/elastic/kibana/pull/200209/files/cab99bce5ac2082fa77222beebe3b61ff836b94b#r1846659920 +/x-pack/test/functional/services/sample_data @elastic/appex-sharedux # Assigned per https://github.com/elastic/kibana/pull/200142#discussion_r1846512756 /test/functional/page_objects/files_management.ts @elastic/appex-sharedux # Assigned per https://github.com/elastic/kibana/pull/200017#discussion_r1840477291 /test/accessibility/apps/home.ts @elastic/appex-sharedux # Assigned per https://github.com/elastic/kibana/pull/199771/files#r1840077237 /test/api_integration/apis/home/*.ts @elastic/appex-sharedux # Assigned per https://github.com/elastic/kibana/pull/199771/files#r1840077065 diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 71ab26400f496..ea31863576115 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -690,6 +690,10 @@ the infrastructure monitoring use-case within Kibana. using the CURL scripts in the scripts folder. +|{kib-repo}blob/{branch}/x-pack/plugins/ai_infra/llm_tasks/README.md[llmTasks] +|This plugin contains various LLM tasks. + + |{kib-repo}blob/{branch}/x-pack/plugins/observability_solution/logs_data_access/README.md[logsDataAccess] |Exposes services to access logs data. @@ -767,6 +771,10 @@ Elastic. |This plugin helps users learn how to use the Painless scripting language. +|{kib-repo}blob/{branch}/x-pack/plugins/ai_infra/product_doc_base/README.md[productDocBase] +|This plugin contains the product documentation base service. + + |{kib-repo}blob/{branch}/x-pack/plugins/observability_solution/profiling/README.md[profiling] |Universal Profiling provides fleet-wide, whole-system, continuous profiling with zero instrumentation. Get a comprehensive understanding of what lines of code are consuming compute resources throughout your entire fleet by visualizing your data in Kibana using the flamegraph, stacktraces, and top functions views. diff --git a/docs/user/security/audit-logging.asciidoc b/docs/user/security/audit-logging.asciidoc index 1ac40bcc7764a..ef12f4303c1b4 100644 --- a/docs/user/security/audit-logging.asciidoc +++ b/docs/user/security/audit-logging.asciidoc @@ -148,6 +148,9 @@ Refer to the corresponding {es} logs for potential write errors. | `success` | Creating trained model. | `failure` | Failed to create trained model. +.1+| `product_documentation_create` +| `unknown` | User requested to install the product documentation for use in AI Assistants. + 3+a| ====== Type: change @@ -334,6 +337,9 @@ Refer to the corresponding {es} logs for potential write errors. | `success` | Updating trained model deployment. | `failure` | Failed to update trained model deployment. +.1+| `product_documentation_update` +| `unknown` | User requested to update the product documentation for use in AI Assistants. + 3+a| ====== Type: deletion @@ -425,6 +431,9 @@ Refer to the corresponding {es} logs for potential write errors. | `success` | Deleting trained model. | `failure` | Failed to delete trained model. +.1+| `product_documentation_delete` +| `unknown` | User requested to delete the product documentation for use in AI Assistants. + 3+a| ====== Type: access diff --git a/oas_docs/output/kibana.serverless.yaml b/oas_docs/output/kibana.serverless.yaml index 55dd5277d1d93..4b35e4d9c78fc 100644 --- a/oas_docs/output/kibana.serverless.yaml +++ b/oas_docs/output/kibana.serverless.yaml @@ -7395,6 +7395,43 @@ paths: tags: - Security Endpoint Management API x-beta: true + /api/entity_store/enable: + post: + operationId: InitEntityStore + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + type: object + properties: + fieldHistoryLength: + default: 10 + description: The number of historical values to keep for each field. + type: integer + filter: + type: string + indexPattern: + $ref: '#/components/schemas/Security_Entity_Analytics_API_IndexPattern' + description: Schema for the entity store initialization + required: true + responses: + '200': + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + type: object + properties: + engines: + items: + $ref: '#/components/schemas/Security_Entity_Analytics_API_EngineDescriptor' + type: array + succeeded: + type: boolean + description: Successful response + summary: Initialize the Entity Store + tags: + - Security Entity Analytics API + x-beta: true /api/entity_store/engines: get: operationId: ListEntityEngines @@ -7713,6 +7750,27 @@ paths: tags: - Security Entity Analytics API x-beta: true + /api/entity_store/status: + get: + operationId: GetEntityStoreStatus + responses: + '200': + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + type: object + properties: + engines: + items: + $ref: '#/components/schemas/Security_Entity_Analytics_API_EngineDescriptor' + type: array + status: + $ref: '#/components/schemas/Security_Entity_Analytics_API_StoreStatus' + description: Successful response + summary: Get the status of the Entity Store + tags: + - Security Entity Analytics API + x-beta: true /api/exception_lists: delete: description: Delete an exception list using the `id` or `list_id` field. @@ -45880,6 +45938,14 @@ components: - index - description - category + Security_Entity_Analytics_API_StoreStatus: + enum: + - not_installed + - installing + - running + - stopped + - error + type: string Security_Entity_Analytics_API_TaskManagerUnavailableResponse: description: Task manager is unavailable type: object diff --git a/oas_docs/output/kibana.yaml b/oas_docs/output/kibana.yaml index b2c3ae00be9d0..94e987510c649 100644 --- a/oas_docs/output/kibana.yaml +++ b/oas_docs/output/kibana.yaml @@ -10282,6 +10282,42 @@ paths: summary: Create or update a protection updates note tags: - Security Endpoint Management API + /api/entity_store/enable: + post: + operationId: InitEntityStore + requestBody: + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + type: object + properties: + fieldHistoryLength: + default: 10 + description: The number of historical values to keep for each field. + type: integer + filter: + type: string + indexPattern: + $ref: '#/components/schemas/Security_Entity_Analytics_API_IndexPattern' + description: Schema for the entity store initialization + required: true + responses: + '200': + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + type: object + properties: + engines: + items: + $ref: '#/components/schemas/Security_Entity_Analytics_API_EngineDescriptor' + type: array + succeeded: + type: boolean + description: Successful response + summary: Initialize the Entity Store + tags: + - Security Entity Analytics API /api/entity_store/engines: get: operationId: ListEntityEngines @@ -10591,6 +10627,26 @@ paths: summary: List Entity Store Entities tags: - Security Entity Analytics API + /api/entity_store/status: + get: + operationId: GetEntityStoreStatus + responses: + '200': + content: + application/json; Elastic-Api-Version=2023-10-31: + schema: + type: object + properties: + engines: + items: + $ref: '#/components/schemas/Security_Entity_Analytics_API_EngineDescriptor' + type: array + status: + $ref: '#/components/schemas/Security_Entity_Analytics_API_StoreStatus' + description: Successful response + summary: Get the status of the Entity Store + tags: + - Security Entity Analytics API /api/exception_lists: delete: description: Delete an exception list using the `id` or `list_id` field. @@ -53601,6 +53657,14 @@ components: - index - description - category + Security_Entity_Analytics_API_StoreStatus: + enum: + - not_installed + - installing + - running + - stopped + - error + type: string Security_Entity_Analytics_API_TaskManagerUnavailableResponse: description: Task manager is unavailable type: object diff --git a/package.json b/package.json index a8c60004e6040..eed5b3a9b61cb 100644 --- a/package.json +++ b/package.json @@ -118,7 +118,8 @@ "@elastic/ecs": "^8.11.1", "@elastic/elasticsearch": "^8.15.2", "@elastic/ems-client": "8.5.3", - "@elastic/eui": "97.3.1", + "@elastic/eui": "97.3.1-borealis.2", + "@elastic/eui-theme-borealis": "0.0.2", "@elastic/filesaver": "1.1.2", "@elastic/node-crypto": "^1.2.3", "@elastic/numeral": "^2.5.1", @@ -617,6 +618,7 @@ "@kbn/licensing-plugin": "link:x-pack/plugins/licensing", "@kbn/links-plugin": "link:src/plugins/links", "@kbn/lists-plugin": "link:x-pack/plugins/lists", + "@kbn/llm-tasks-plugin": "link:x-pack/plugins/ai_infra/llm_tasks", "@kbn/locator-examples-plugin": "link:examples/locator_examples", "@kbn/locator-explorer-plugin": "link:examples/locator_explorer", "@kbn/logging": "link:packages/kbn-logging", @@ -721,6 +723,8 @@ "@kbn/presentation-panel-plugin": "link:src/plugins/presentation_panel", "@kbn/presentation-publishing": "link:packages/presentation/presentation_publishing", "@kbn/presentation-util-plugin": "link:src/plugins/presentation_util", + "@kbn/product-doc-base-plugin": "link:x-pack/plugins/ai_infra/product_doc_base", + "@kbn/product-doc-common": "link:x-pack/packages/ai-infra/product-doc-common", "@kbn/profiling-data-access-plugin": "link:x-pack/plugins/observability_solution/profiling_data_access", "@kbn/profiling-plugin": "link:x-pack/plugins/observability_solution/profiling", "@kbn/profiling-utils": "link:packages/kbn-profiling-utils", @@ -1068,7 +1072,7 @@ "@turf/length": "^6.0.2", "@xstate/react": "^3.2.2", "@xstate5/react": "npm:@xstate/react@^4.1.2", - "@xyflow/react": "^12.3.4", + "@xyflow/react": "^12.3.5", "adm-zip": "^0.5.9", "ai": "^2.2.33", "ajv": "^8.12.0", diff --git a/packages/core/rendering/core-rendering-server-internal/src/views/styles.tsx b/packages/core/rendering/core-rendering-server-internal/src/views/styles.tsx index ceeb6f4b7f9e2..54e8559ad25c1 100644 --- a/packages/core/rendering/core-rendering-server-internal/src/views/styles.tsx +++ b/packages/core/rendering/core-rendering-server-internal/src/views/styles.tsx @@ -8,17 +8,18 @@ */ import React, { FC } from 'react'; -import type { DarkModeValue } from '@kbn/core-ui-settings-common'; +import { type DarkModeValue, ThemeName } from '@kbn/core-ui-settings-common'; interface Props { darkMode: DarkModeValue; + themeName: ThemeName; stylesheetPaths: string[]; } -export const Styles: FC = ({ darkMode, stylesheetPaths }) => { +export const Styles: FC = ({ darkMode, themeName, stylesheetPaths }) => { return ( <> - {darkMode !== 'system' && } + {darkMode !== 'system' && } {stylesheetPaths.map((path) => ( ))} @@ -26,7 +27,27 @@ export const Styles: FC = ({ darkMode, stylesheetPaths }) => { ); }; -const InlineStyles: FC<{ darkMode: boolean }> = ({ darkMode }) => { +const InlineStyles: FC<{ darkMode: boolean; themeName: ThemeName }> = ({ darkMode, themeName }) => { + const getThemeStyles = (theme: ThemeName) => { + if (theme === 'borealis') { + return { + pageBackground: darkMode ? '#07101F' : '#F6F9FC', // colors.body + welcomeText: darkMode ? '#8E9FBC' : '#5A6D8C', // colors.subduedText + progress: darkMode ? '#172336' : '#ECF1F9', // colors.lightestShade + progressBefore: darkMode ? '#599DFF' : '#0B64DD', // colors.primary + }; + } + + return { + pageBackground: darkMode ? '#141519' : '#F8FAFD', + welcomeText: darkMode ? '#98A2B3' : '#69707D', + progress: darkMode ? '#25262E' : '#F5F7FA', + progressBefore: darkMode ? '#1BA9F5' : '#006DE4', + }; + }; + + const themeStyles = getThemeStyles(themeName); + // must be kept in sync with // packages/core/apps/core-apps-server-internal/assets/legacy_theme.js /* eslint-disable react/no-danger */ @@ -36,19 +57,19 @@ const InlineStyles: FC<{ darkMode: boolean }> = ({ darkMode }) => { __html: ` html { - background-color: ${darkMode ? '#141519' : '#F8FAFD'} + background-color: ${themeStyles.pageBackground} } .kbnWelcomeText { - color: ${darkMode ? '#98A2B3' : '#69707D'}; + color: ${themeStyles.welcomeText}; } .kbnProgress { - background-color: ${darkMode ? '#25262E' : '#F5F7FA'}; + background-color: ${themeStyles.progress}; } .kbnProgress:before { - background-color: ${darkMode ? '#1BA9F5' : '#006DE4'}; + background-color: ${themeStyles.progressBefore}; } `, diff --git a/packages/core/rendering/core-rendering-server-internal/src/views/template.tsx b/packages/core/rendering/core-rendering-server-internal/src/views/template.tsx index fdbade121445d..d3556287a0333 100644 --- a/packages/core/rendering/core-rendering-server-internal/src/views/template.tsx +++ b/packages/core/rendering/core-rendering-server-internal/src/views/template.tsx @@ -56,7 +56,11 @@ export const Template: FunctionComponent = ({ {/* Inject EUI reset and global styles before all other component styles */} - + {scriptPaths.map((path) => (

Some Title

Some Body
Action#1
Action#2
"`; +exports[`PromptPage renders as expected with additional scripts 1`] = `"ElasticMockedFonts

Some Title

Some Body
Action#1
Action#2
"`; -exports[`PromptPage renders as expected without additional scripts 1`] = `"ElasticMockedFonts

Some Title

Some Body
Action#1
Action#2
"`; +exports[`PromptPage renders as expected without additional scripts 1`] = `"ElasticMockedFonts

Some Title

Some Body
Action#1
Action#2
"`; diff --git a/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap b/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap index 80a7e7a24e1e9..ab94f2c2efc8d 100644 --- a/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap +++ b/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`UnauthenticatedPage renders as expected 1`] = `"ElasticMockedFonts

We hit an authentication error

Try logging in again, and if the problem persists, contact your system administrator.

"`; +exports[`UnauthenticatedPage renders as expected 1`] = `"ElasticMockedFonts

We hit an authentication error

Try logging in again, and if the problem persists, contact your system administrator.

"`; -exports[`UnauthenticatedPage renders as expected with custom title 1`] = `"My Company NameMockedFonts

We hit an authentication error

Try logging in again, and if the problem persists, contact your system administrator.

"`; +exports[`UnauthenticatedPage renders as expected with custom title 1`] = `"My Company NameMockedFonts

We hit an authentication error

Try logging in again, and if the problem persists, contact your system administrator.

"`; diff --git a/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap b/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap index e7a902015afa7..fcab54e925cfb 100644 --- a/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap +++ b/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ResetSessionPage renders as expected 1`] = `"ElasticMockedFonts

You do not have permission to access the requested page

Either go back to the previous page or log in as a different user.

"`; +exports[`ResetSessionPage renders as expected 1`] = `"ElasticMockedFonts

You do not have permission to access the requested page

Either go back to the previous page or log in as a different user.

"`; -exports[`ResetSessionPage renders as expected with custom page title 1`] = `"My Company NameMockedFonts

You do not have permission to access the requested page

Either go back to the previous page or log in as a different user.

"`; +exports[`ResetSessionPage renders as expected with custom page title 1`] = `"My Company NameMockedFonts

You do not have permission to access the requested page

Either go back to the previous page or log in as a different user.

"`; diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.gen.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.gen.ts index 228bf1e515675..7e419dbe6453c 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.gen.ts +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.gen.ts @@ -39,6 +39,11 @@ export const EngineDescriptor = z.object({ error: z.object({}).optional(), }); +export type StoreStatus = z.infer; +export const StoreStatus = z.enum(['not_installed', 'installing', 'running', 'stopped', 'error']); +export type StoreStatusEnum = typeof StoreStatus.enum; +export const StoreStatusEnum = StoreStatus.enum; + export type InspectQuery = z.infer; export const InspectQuery = z.object({ response: z.array(z.string()), diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.schema.yaml index 00b100516b76c..9a42191a556ac 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/common.schema.yaml @@ -41,6 +41,15 @@ components: - stopped - updating - error + + StoreStatus: + type: string + enum: + - not_installed + - installing + - running + - stopped + - error IndexPattern: type: string diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/enablement.gen.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/enablement.gen.ts new file mode 100644 index 0000000000000..9644a1a333d16 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/enablement.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Enable Entity Store + * version: 2023-10-31 + */ + +import { z } from '@kbn/zod'; + +import { IndexPattern, EngineDescriptor, StoreStatus } from './common.gen'; + +export type GetEntityStoreStatusResponse = z.infer; +export const GetEntityStoreStatusResponse = z.object({ + status: StoreStatus.optional(), + engines: z.array(EngineDescriptor).optional(), +}); + +export type InitEntityStoreRequestBody = z.infer; +export const InitEntityStoreRequestBody = z.object({ + /** + * The number of historical values to keep for each field. + */ + fieldHistoryLength: z.number().int().optional().default(10), + indexPattern: IndexPattern.optional(), + filter: z.string().optional(), +}); +export type InitEntityStoreRequestBodyInput = z.input; + +export type InitEntityStoreResponse = z.infer; +export const InitEntityStoreResponse = z.object({ + succeeded: z.boolean().optional(), + engines: z.array(EngineDescriptor).optional(), +}); diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/enablement.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/enablement.schema.yaml new file mode 100644 index 0000000000000..306e876dfc4a7 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/entity_store/enablement.schema.yaml @@ -0,0 +1,64 @@ +openapi: 3.0.0 + +info: + title: Enable Entity Store + version: '2023-10-31' +paths: + /api/entity_store/enable: + post: + x-labels: [ess, serverless] + x-codegen-enabled: true + operationId: InitEntityStore + summary: Initialize the Entity Store + + requestBody: + description: Schema for the entity store initialization + required: true + content: + application/json: + schema: + type: object + properties: + fieldHistoryLength: + type: integer + description: The number of historical values to keep for each field. + default: 10 + indexPattern: + $ref: './common.schema.yaml#/components/schemas/IndexPattern' + filter: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + succeeded: + type: boolean + engines: + type: array + items: + $ref: './common.schema.yaml#/components/schemas/EngineDescriptor' + + /api/entity_store/status: + get: + x-labels: [ess, serverless] + x-codegen-enabled: true + operationId: GetEntityStoreStatus + summary: Get the status of the Entity Store + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + status: + $ref: './common.schema.yaml#/components/schemas/StoreStatus' + engines: + type: array + items: + $ref: './common.schema.yaml#/components/schemas/EngineDescriptor' diff --git a/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts b/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts index 264d0eaa14fee..513e2163f932f 100644 --- a/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts +++ b/x-pack/plugins/security_solution/common/api/quickstart_client.gen.ts @@ -231,6 +231,11 @@ import type { InternalUploadAssetCriticalityRecordsResponse, UploadAssetCriticalityRecordsResponse, } from './entity_analytics/asset_criticality/upload_asset_criticality_csv.gen'; +import type { + GetEntityStoreStatusResponse, + InitEntityStoreRequestBodyInput, + InitEntityStoreResponse, +} from './entity_analytics/entity_store/enablement.gen'; import type { ApplyEntityEngineDataviewIndicesResponse } from './entity_analytics/entity_store/engine/apply_dataview_indices.gen'; import type { DeleteEntityEngineRequestQueryInput, @@ -1301,6 +1306,18 @@ finalize it. }) .catch(catchAxiosErrorFormatAndThrow); } + async getEntityStoreStatus() { + this.log.info(`${new Date().toISOString()} Calling API GetEntityStoreStatus`); + return this.kbnClient + .request({ + path: '/api/entity_store/status', + headers: { + [ELASTIC_HTTP_VERSION_HEADER]: '2023-10-31', + }, + method: 'GET', + }) + .catch(catchAxiosErrorFormatAndThrow); + } /** * Get all notes for a given document. */ @@ -1529,6 +1546,19 @@ finalize it. }) .catch(catchAxiosErrorFormatAndThrow); } + async initEntityStore(props: InitEntityStoreProps) { + this.log.info(`${new Date().toISOString()} Calling API InitEntityStore`); + return this.kbnClient + .request({ + path: '/api/entity_store/enable', + headers: { + [ELASTIC_HTTP_VERSION_HEADER]: '2023-10-31', + }, + method: 'POST', + body: props.body, + }) + .catch(catchAxiosErrorFormatAndThrow); + } /** * Initializes the Risk Engine by creating the necessary indices and mappings, removing old transforms, and starting the new risk engine */ @@ -2290,6 +2320,9 @@ export interface InitEntityEngineProps { params: InitEntityEngineRequestParamsInput; body: InitEntityEngineRequestBodyInput; } +export interface InitEntityStoreProps { + body: InitEntityStoreRequestBodyInput; +} export interface InstallPrepackedTimelinesProps { body: InstallPrepackedTimelinesRequestBodyInput; } diff --git a/x-pack/plugins/security_solution/common/endpoint/constants.ts b/x-pack/plugins/security_solution/common/endpoint/constants.ts index 0ab749735d06c..b53c7ae761547 100644 --- a/x-pack/plugins/security_solution/common/endpoint/constants.ts +++ b/x-pack/plugins/security_solution/common/endpoint/constants.ts @@ -18,6 +18,7 @@ export const ENDPOINT_ACTION_RESPONSES_INDEX = `${ENDPOINT_ACTION_RESPONSES_DS}- export const ENDPOINT_ACTION_RESPONSES_INDEX_PATTERN = `${ENDPOINT_ACTION_RESPONSES_DS}-*`; export const eventsIndexPattern = 'logs-endpoint.events.*'; +export const FILE_EVENTS_INDEX_PATTERN = 'logs-endpoint.events.file-*'; export const alertsIndexPattern = 'logs-endpoint.alerts-*'; // metadata datastream diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index dc6495e1d9737..7fcdabad3b36c 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -236,6 +236,11 @@ export const allowedExperimentalValues = Object.freeze({ * Enables the siem migrations feature */ siemMigrationsEnabled: false, + + /** + * Enables the Defend Insights feature + */ + defendInsights: false, }); type ExperimentalConfigKeys = Array; diff --git a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml index 60bd38c246f34..fa79b170f3513 100644 --- a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml @@ -267,6 +267,42 @@ paths: summary: List asset criticality records tags: - Security Entity Analytics API + /api/entity_store/enable: + post: + operationId: InitEntityStore + requestBody: + content: + application/json: + schema: + type: object + properties: + fieldHistoryLength: + default: 10 + description: The number of historical values to keep for each field. + type: integer + filter: + type: string + indexPattern: + $ref: '#/components/schemas/IndexPattern' + description: Schema for the entity store initialization + required: true + responses: + '200': + content: + application/json: + schema: + type: object + properties: + engines: + items: + $ref: '#/components/schemas/EngineDescriptor' + type: array + succeeded: + type: boolean + description: Successful response + summary: Initialize the Entity Store + tags: + - Security Entity Analytics API /api/entity_store/engines: get: operationId: ListEntityEngines @@ -576,6 +612,26 @@ paths: summary: List Entity Store Entities tags: - Security Entity Analytics API + /api/entity_store/status: + get: + operationId: GetEntityStoreStatus + responses: + '200': + content: + application/json: + schema: + type: object + properties: + engines: + items: + $ref: '#/components/schemas/EngineDescriptor' + type: array + status: + $ref: '#/components/schemas/StoreStatus' + description: Successful response + summary: Get the status of the Entity Store + tags: + - Security Entity Analytics API /api/risk_score/engine/dangerously_delete_data: delete: description: >- @@ -1046,6 +1102,14 @@ components: - index - description - category + StoreStatus: + enum: + - not_installed + - installing + - running + - stopped + - error + type: string TaskManagerUnavailableResponse: description: Task manager is unavailable type: object diff --git a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml index fc63924118968..9c2b3d62b1650 100644 --- a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml @@ -267,6 +267,42 @@ paths: summary: List asset criticality records tags: - Security Entity Analytics API + /api/entity_store/enable: + post: + operationId: InitEntityStore + requestBody: + content: + application/json: + schema: + type: object + properties: + fieldHistoryLength: + default: 10 + description: The number of historical values to keep for each field. + type: integer + filter: + type: string + indexPattern: + $ref: '#/components/schemas/IndexPattern' + description: Schema for the entity store initialization + required: true + responses: + '200': + content: + application/json: + schema: + type: object + properties: + engines: + items: + $ref: '#/components/schemas/EngineDescriptor' + type: array + succeeded: + type: boolean + description: Successful response + summary: Initialize the Entity Store + tags: + - Security Entity Analytics API /api/entity_store/engines: get: operationId: ListEntityEngines @@ -576,6 +612,26 @@ paths: summary: List Entity Store Entities tags: - Security Entity Analytics API + /api/entity_store/status: + get: + operationId: GetEntityStoreStatus + responses: + '200': + content: + application/json: + schema: + type: object + properties: + engines: + items: + $ref: '#/components/schemas/EngineDescriptor' + type: array + status: + $ref: '#/components/schemas/StoreStatus' + description: Successful response + summary: Get the status of the Entity Store + tags: + - Security Entity Analytics API /api/risk_score/engine/dangerously_delete_data: delete: description: >- @@ -1046,6 +1102,14 @@ components: - index - description - category + StoreStatus: + enum: + - not_installed + - installing + - running + - stopped + - error + type: string TaskManagerUnavailableResponse: description: Task manager is unavailable type: object diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/link_to_app.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/link_to_app.tsx index 2adecd4a42391..74b18c32c63a0 100644 --- a/x-pack/plugins/security_solution/public/common/components/endpoint/link_to_app.tsx +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/link_to_app.tsx @@ -51,7 +51,7 @@ export const LinkToApp = memo( {children} ) : ( - + {children} )} diff --git a/x-pack/plugins/security_solution/server/assistant/tools/defend_insights/errors.ts b/x-pack/plugins/security_solution/server/assistant/tools/defend_insights/errors.ts new file mode 100644 index 0000000000000..03633d2ae1eed --- /dev/null +++ b/x-pack/plugins/security_solution/server/assistant/tools/defend_insights/errors.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EndpointError } from '../../../../common/endpoint/errors'; + +export class InvalidDefendInsightTypeError extends EndpointError { + constructor() { + super('invalid defend insight type'); + } +} diff --git a/x-pack/plugins/security_solution/server/assistant/tools/defend_insights/get_events/get_file_events_query.ts b/x-pack/plugins/security_solution/server/assistant/tools/defend_insights/get_events/get_file_events_query.ts new file mode 100644 index 0000000000000..fa8f6fa1e33b4 --- /dev/null +++ b/x-pack/plugins/security_solution/server/assistant/tools/defend_insights/get_events/get_file_events_query.ts @@ -0,0 +1,49 @@ +/* + * 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 { SearchRequest } from '@elastic/elasticsearch/lib/api/types'; + +import { FILE_EVENTS_INDEX_PATTERN } from '../../../../../common/endpoint/constants'; + +const SIZE = 200; + +export function getFileEventsQuery({ endpointIds }: { endpointIds: string[] }): SearchRequest { + return { + allow_no_indices: true, + fields: ['_id', 'agent.id', 'process.executable'], + query: { + bool: { + must: [ + { + terms: { + 'agent.id': endpointIds, + }, + }, + { + range: { + '@timestamp': { + gte: 'now-24h', + lte: 'now', + }, + }, + }, + ], + }, + }, + size: SIZE, + sort: [ + { + '@timestamp': { + order: 'desc', + }, + }, + ], + _source: false, + ignore_unavailable: true, + index: [FILE_EVENTS_INDEX_PATTERN], + }; +} diff --git a/x-pack/plugins/security_solution/server/assistant/tools/defend_insights/get_events/index.test.ts b/x-pack/plugins/security_solution/server/assistant/tools/defend_insights/get_events/index.test.ts new file mode 100644 index 0000000000000..7c2fd9f61e255 --- /dev/null +++ b/x-pack/plugins/security_solution/server/assistant/tools/defend_insights/get_events/index.test.ts @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ElasticsearchClient } from '@kbn/core/server'; + +import { DefendInsightType, transformRawData } from '@kbn/elastic-assistant-common'; + +import { InvalidDefendInsightTypeError } from '../errors'; +import { getFileEventsQuery } from './get_file_events_query'; +import { getAnonymizedEvents } from '.'; + +jest.mock('@kbn/elastic-assistant-common', () => { + const originalModule = jest.requireActual('@kbn/elastic-assistant-common'); + return { + ...originalModule, + transformRawData: jest.fn(), + }; +}); + +jest.mock('./get_file_events_query', () => ({ + getFileEventsQuery: jest.fn(), +})); + +describe('getAnonymizedEvents', () => { + let mockEsClient: jest.Mocked; + + const mockHits = [ + { _index: 'test-index', fields: { field1: ['value1'] } }, + { _index: 'test-index', fields: { field2: ['value2'] } }, + ]; + + beforeEach(() => { + (getFileEventsQuery as jest.Mock).mockReturnValue({ index: 'test-index', body: {} }); + (transformRawData as jest.Mock).mockImplementation( + ({ rawData }) => `anonymized_${Object.values(rawData)[0]}` + ); + mockEsClient = { + search: jest.fn().mockResolvedValue({ + took: 1, + timed_out: false, + _shards: { + total: 1, + successful: 1, + skipped: 0, + failed: 0, + }, + hits: { + hits: mockHits, + }, + }), + } as unknown as jest.Mocked; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should return anonymized events successfully', async () => { + const result = await getAnonymizedEvents({ + endpointIds: ['endpoint1'], + type: DefendInsightType.Enum.incompatible_antivirus, + esClient: mockEsClient, + }); + + expect(result).toEqual(['anonymized_value1', 'anonymized_value2']); + expect(getFileEventsQuery).toHaveBeenCalledWith({ endpointIds: ['endpoint1'] }); + expect(mockEsClient.search).toHaveBeenCalledWith({ index: 'test-index', body: {} }); + expect(transformRawData).toHaveBeenCalledTimes(2); + }); + + it('should throw InvalidDefendInsightTypeError for invalid type', async () => { + await expect( + getAnonymizedEvents({ + endpointIds: ['endpoint1'], + type: 'invalid_type' as DefendInsightType, + esClient: mockEsClient, + }) + ).rejects.toThrow(InvalidDefendInsightTypeError); + }); +}); diff --git a/x-pack/plugins/security_solution/server/assistant/tools/defend_insights/get_events/index.ts b/x-pack/plugins/security_solution/server/assistant/tools/defend_insights/get_events/index.ts new file mode 100644 index 0000000000000..4d9fcaf89a34a --- /dev/null +++ b/x-pack/plugins/security_solution/server/assistant/tools/defend_insights/get_events/index.ts @@ -0,0 +1,93 @@ +/* + * 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 { SearchRequest, SearchResponse } from '@elastic/elasticsearch/lib/api/types'; +import type { ElasticsearchClient } from '@kbn/core/server'; +import type { Replacements } from '@kbn/elastic-assistant-common'; +import type { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/schemas/anonymization_fields/bulk_crud_anonymization_fields_route.gen'; + +import { + getAnonymizedValue, + transformRawData, + DefendInsightType, + getRawDataOrDefault, +} from '@kbn/elastic-assistant-common'; + +import { getFileEventsQuery } from './get_file_events_query'; +import { InvalidDefendInsightTypeError } from '../errors'; + +export async function getAnonymizedEvents({ + endpointIds, + type, + anonymizationFields, + esClient, + onNewReplacements, + replacements, +}: { + endpointIds: string[]; + type: DefendInsightType; + anonymizationFields?: AnonymizationFieldResponse[]; + esClient: ElasticsearchClient; + onNewReplacements?: (replacements: Replacements) => void; + replacements?: Replacements; +}): Promise { + const query = getQuery(type, { endpointIds }); + + return getAnonymized({ + query, + anonymizationFields, + esClient, + onNewReplacements, + replacements, + }); +} + +function getQuery(type: DefendInsightType, options: { endpointIds: string[] }): SearchRequest { + if (type === DefendInsightType.Enum.incompatible_antivirus) { + const { endpointIds } = options; + return getFileEventsQuery({ + endpointIds, + }); + } + + throw new InvalidDefendInsightTypeError(); +} + +const getAnonymized = async ({ + query, + anonymizationFields, + esClient, + onNewReplacements, + replacements, +}: { + query: SearchRequest; + anonymizationFields?: AnonymizationFieldResponse[]; + esClient: ElasticsearchClient; + onNewReplacements?: (replacements: Replacements) => void; + replacements?: Replacements; +}): Promise => { + const result = await esClient.search(query); + + // Accumulate replacements locally so we can, for example use the same + // replacement for a hostname when we see it in multiple alerts: + let localReplacements = { ...(replacements ?? {}) }; + const localOnNewReplacements = (newReplacements: Replacements) => { + localReplacements = { ...localReplacements, ...newReplacements }; + + onNewReplacements?.(localReplacements); // invoke the callback with the latest replacements + }; + + return result.hits?.hits?.map((hit) => + transformRawData({ + anonymizationFields, + currentReplacements: localReplacements, // <-- the latest local replacements + getAnonymizedValue, + onNewReplacements: localOnNewReplacements, // <-- the local callback + rawData: getRawDataOrDefault(hit.fields), + }) + ); +}; diff --git a/x-pack/plugins/security_solution/server/assistant/tools/defend_insights/index.test.ts b/x-pack/plugins/security_solution/server/assistant/tools/defend_insights/index.test.ts new file mode 100644 index 0000000000000..5ef5aaeedf364 --- /dev/null +++ b/x-pack/plugins/security_solution/server/assistant/tools/defend_insights/index.test.ts @@ -0,0 +1,69 @@ +/* + * 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 { DynamicTool } from '@langchain/core/tools'; + +import { requestHasRequiredAnonymizationParams } from '@kbn/elastic-assistant-plugin/server/lib/langchain/helpers'; +import { DEFEND_INSIGHTS_TOOL_ID, DefendInsightType } from '@kbn/elastic-assistant-common'; +import { elasticsearchServiceMock } from '@kbn/core/server/mocks'; + +import type { DefendInsightsToolParams } from '.'; + +import { APP_UI_ID } from '../../../../common'; +import { DEFEND_INSIGHTS_TOOL, DEFEND_INSIGHTS_TOOL_DESCRIPTION } from '.'; + +jest.mock('@kbn/elastic-assistant-plugin/server/lib/langchain/helpers', () => ({ + requestHasRequiredAnonymizationParams: jest.fn(), +})); + +describe('DEFEND_INSIGHTS_TOOL', () => { + const mockLLM = {}; + const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); + const mockRequest = {}; + const mockParams: DefendInsightsToolParams = { + endpointIds: ['endpoint1'], + insightType: DefendInsightType.Enum.incompatible_antivirus, + anonymizationFields: [], + esClient: mockEsClient, + langChainTimeout: 1000, + llm: mockLLM, + onNewReplacements: jest.fn(), + replacements: {}, + request: mockRequest, + } as unknown as DefendInsightsToolParams; + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should have correct properties', () => { + expect(DEFEND_INSIGHTS_TOOL.id).toBe(DEFEND_INSIGHTS_TOOL_ID); + expect(DEFEND_INSIGHTS_TOOL.name).toBe('defendInsightsTool'); + expect(DEFEND_INSIGHTS_TOOL.description).toBe(DEFEND_INSIGHTS_TOOL_DESCRIPTION); + expect(DEFEND_INSIGHTS_TOOL.sourceRegister).toBe(APP_UI_ID); + }); + + it('should return tool if supported', () => { + (requestHasRequiredAnonymizationParams as jest.Mock).mockReturnValue(true); + const tool = DEFEND_INSIGHTS_TOOL.getTool(mockParams); + expect(tool).toBeInstanceOf(DynamicTool); + }); + + it('should return null if not request missing anonymization params', () => { + (requestHasRequiredAnonymizationParams as jest.Mock).mockReturnValue(false); + const tool = DEFEND_INSIGHTS_TOOL.getTool(mockParams); + expect(tool).toBeNull(); + }); + + it('should return null if LLM is not provided', () => { + (requestHasRequiredAnonymizationParams as jest.Mock).mockReturnValue(true); + const paramsWithoutLLM = { ...mockParams, llm: undefined }; + const tool = DEFEND_INSIGHTS_TOOL.getTool(paramsWithoutLLM) as DynamicTool; + + expect(tool).toBeNull(); + }); +}); diff --git a/x-pack/plugins/security_solution/server/assistant/tools/defend_insights/index.ts b/x-pack/plugins/security_solution/server/assistant/tools/defend_insights/index.ts new file mode 100644 index 0000000000000..1ea26b88a15cf --- /dev/null +++ b/x-pack/plugins/security_solution/server/assistant/tools/defend_insights/index.ts @@ -0,0 +1,114 @@ +/* + * 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 { PromptTemplate } from '@langchain/core/prompts'; +import { DynamicTool } from '@langchain/core/tools'; +import { LLMChain } from 'langchain/chains'; +import { OutputFixingParser } from 'langchain/output_parsers'; + +import type { AssistantTool, AssistantToolParams } from '@kbn/elastic-assistant-plugin/server'; +import type { DefendInsightType } from '@kbn/elastic-assistant-common'; + +import { requestHasRequiredAnonymizationParams } from '@kbn/elastic-assistant-plugin/server/lib/langchain/helpers'; +import { DEFEND_INSIGHTS_TOOL_ID } from '@kbn/elastic-assistant-common'; + +import { APP_UI_ID } from '../../../../common'; +import { getAnonymizedEvents } from './get_events'; +import { getDefendInsightsOutputParser } from './output_parsers'; +import { getDefendInsightsPrompt } from './prompts'; + +export const DEFEND_INSIGHTS_TOOL_DESCRIPTION = 'Call this for Elastic Defend insights.'; + +export interface DefendInsightsToolParams extends AssistantToolParams { + endpointIds: string[]; + insightType: DefendInsightType; +} + +/** + * Returns a tool for generating Elastic Defend configuration insights + */ +export const DEFEND_INSIGHTS_TOOL: AssistantTool = Object.freeze({ + id: DEFEND_INSIGHTS_TOOL_ID, + name: 'defendInsightsTool', + description: DEFEND_INSIGHTS_TOOL_DESCRIPTION, + sourceRegister: APP_UI_ID, + + isSupported: (params: AssistantToolParams): boolean => { + const { llm, request } = params; + + return requestHasRequiredAnonymizationParams(request) && llm != null; + }, + + getTool(params: AssistantToolParams): DynamicTool | null { + if (!this.isSupported(params)) return null; + + const { + endpointIds, + insightType, + anonymizationFields, + esClient, + langChainTimeout, + llm, + onNewReplacements, + replacements, + } = params as DefendInsightsToolParams; + + return new DynamicTool({ + name: 'DefendInsightsTool', + description: DEFEND_INSIGHTS_TOOL_DESCRIPTION, + func: async () => { + if (llm == null) { + throw new Error('LLM is required for Defend Insights'); + } + + const anonymizedEvents = await getAnonymizedEvents({ + endpointIds, + type: insightType, + anonymizationFields, + esClient, + onNewReplacements, + replacements, + }); + + const eventsContextCount = anonymizedEvents.length; + if (eventsContextCount === 0) { + return JSON.stringify({ eventsContextCount, insights: [] }, null, 2); + } + + const outputParser = getDefendInsightsOutputParser({ type: insightType }); + const outputFixingParser = OutputFixingParser.fromLLM(llm, outputParser); + + const prompt = new PromptTemplate({ + template: `Answer the user's question as best you can:\n{format_instructions}\n{query}`, + inputVariables: ['query'], + partialVariables: { + format_instructions: outputFixingParser.getFormatInstructions(), + }, + }); + + const answerFormattingChain = new LLMChain({ + llm, + prompt, + outputKey: 'records', + outputParser: outputFixingParser, + }); + + const result = await answerFormattingChain.call({ + query: getDefendInsightsPrompt({ + type: insightType, + events: anonymizedEvents, + }), + timeout: langChainTimeout, + }); + const insights = result.records; + + return JSON.stringify({ eventsContextCount, insights }, null, 2); + }, + tags: [DEFEND_INSIGHTS_TOOL_ID], + }); + }, +}); diff --git a/x-pack/plugins/security_solution/server/assistant/tools/defend_insights/output_parsers/incompatible_antivirus.ts b/x-pack/plugins/security_solution/server/assistant/tools/defend_insights/output_parsers/incompatible_antivirus.ts new file mode 100644 index 0000000000000..b6430e4408355 --- /dev/null +++ b/x-pack/plugins/security_solution/server/assistant/tools/defend_insights/output_parsers/incompatible_antivirus.ts @@ -0,0 +1,28 @@ +/* + * 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 { StructuredOutputParser } from 'langchain/output_parsers'; + +import { z } from '@kbn/zod'; + +export function getIncompatibleVirusOutputParser() { + return StructuredOutputParser.fromZodSchema( + z.array( + z.object({ + group: z.string().describe('The program which is triggering the events'), + events: z + .object({ + id: z.string().describe('The event ID'), + endpointId: z.string().describe('The endpoint ID'), + value: z.string().describe('The process.executable value of the event'), + }) + .array() + .describe('The events that the insight is based on'), + }) + ) + ); +} diff --git a/x-pack/plugins/security_solution/server/assistant/tools/defend_insights/output_parsers/index.ts b/x-pack/plugins/security_solution/server/assistant/tools/defend_insights/output_parsers/index.ts new file mode 100644 index 0000000000000..78933b72702bf --- /dev/null +++ b/x-pack/plugins/security_solution/server/assistant/tools/defend_insights/output_parsers/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DefendInsightType } from '@kbn/elastic-assistant-common'; + +import { InvalidDefendInsightTypeError } from '../errors'; +import { getIncompatibleVirusOutputParser } from './incompatible_antivirus'; + +export function getDefendInsightsOutputParser({ type }: { type: DefendInsightType }) { + if (type === DefendInsightType.Enum.incompatible_antivirus) { + return getIncompatibleVirusOutputParser(); + } + + throw new InvalidDefendInsightTypeError(); +} diff --git a/x-pack/plugins/security_solution/server/assistant/tools/defend_insights/prompts/incompatible_antivirus.ts b/x-pack/plugins/security_solution/server/assistant/tools/defend_insights/prompts/incompatible_antivirus.ts new file mode 100644 index 0000000000000..516de86a30975 --- /dev/null +++ b/x-pack/plugins/security_solution/server/assistant/tools/defend_insights/prompts/incompatible_antivirus.ts @@ -0,0 +1,16 @@ +/* + * 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 function getIncompatibleAntivirusPrompt({ events }: { events: string[] }): string { + return `You are an Elastic Security user tasked with analyzing file events from Elastic Security to identify antivirus processes. Only focus on detecting antivirus processes. Ignore processes that belong to Elastic Agent or Elastic Defend, that are not antivirus processes, or are typical processes built into the operating system. Accuracy is of the utmost importance, try to minimize false positives. Group the processes by the antivirus program, keeping track of the agent.id and _id associated to each of the individual events as endpointId and eventId respectively. If there are no events, ignore the group field. Escape backslashes to respect JSON validation. New lines must always be escaped with double backslashes, i.e. \\\\n to ensure valid JSON. Only return JSON output, as described above. Do not add any additional text to describe your output. + + Use context from the following process events to provide insights: + """ + ${events.join('\n\n')} + """ + `; +} diff --git a/x-pack/plugins/security_solution/server/assistant/tools/defend_insights/prompts/index.ts b/x-pack/plugins/security_solution/server/assistant/tools/defend_insights/prompts/index.ts new file mode 100644 index 0000000000000..d58778c3c544b --- /dev/null +++ b/x-pack/plugins/security_solution/server/assistant/tools/defend_insights/prompts/index.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DefendInsightType } from '@kbn/elastic-assistant-common'; + +import { InvalidDefendInsightTypeError } from '../errors'; +import { getIncompatibleAntivirusPrompt } from './incompatible_antivirus'; + +export function getDefendInsightsPrompt({ + type, + events, +}: { + type: DefendInsightType; + events: string[]; +}): string { + if (type === DefendInsightType.Enum.incompatible_antivirus) { + return getIncompatibleAntivirusPrompt({ events }); + } + + throw new InvalidDefendInsightTypeError(); +} diff --git a/x-pack/plugins/security_solution/server/assistant/tools/index.ts b/x-pack/plugins/security_solution/server/assistant/tools/index.ts index 9bb85f5beedae..f7824e688afe2 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/index.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/index.ts @@ -10,12 +10,14 @@ import type { AssistantTool } from '@kbn/elastic-assistant-plugin/server'; import { NL_TO_ESQL_TOOL } from './esql/nl_to_esql_tool'; import { ALERT_COUNTS_TOOL } from './alert_counts/alert_counts_tool'; import { OPEN_AND_ACKNOWLEDGED_ALERTS_TOOL } from './open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool'; +import { DEFEND_INSIGHTS_TOOL } from './defend_insights'; import { KNOWLEDGE_BASE_RETRIEVAL_TOOL } from './knowledge_base/knowledge_base_retrieval_tool'; import { KNOWLEDGE_BASE_WRITE_TOOL } from './knowledge_base/knowledge_base_write_tool'; import { SECURITY_LABS_KNOWLEDGE_BASE_TOOL } from './security_labs/security_labs_tool'; export const assistantTools: AssistantTool[] = [ ALERT_COUNTS_TOOL, + DEFEND_INSIGHTS_TOOL, NL_TO_ESQL_TOOL, KNOWLEDGE_BASE_RETRIEVAL_TOOL, KNOWLEDGE_BASE_WRITE_TOOL, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_all_integrations/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_all_integrations/route.ts index 5b4eab27f71ab..4b5642b9d199b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_all_integrations/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_all_integrations/route.ts @@ -23,8 +23,10 @@ export const getAllIntegrationsRoute = (router: SecuritySolutionPluginRouter) => .get({ access: 'internal', path: GET_ALL_INTEGRATIONS_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/route.ts index 3a3d159d1337f..27b1c4b103ab7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/fleet_integrations/api/get_installed_integrations/route.ts @@ -21,8 +21,10 @@ export const getInstalledIntegrationsRoute = (router: SecuritySolutionPluginRout .get({ access: 'internal', path: GET_INSTALLED_INTEGRATIONS_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.ts index d17435a543320..8d3788a2cf7f8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/bootstrap_prebuilt_rules/bootstrap_prebuilt_rules.ts @@ -21,8 +21,10 @@ export const bootstrapPrebuiltRulesRoute = (router: SecuritySolutionPluginRouter .post({ access: 'internal', path: BOOTSTRAP_PREBUILT_RULES_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.ts index 3713176e919c5..dc9c15ac5b5f7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.ts @@ -30,8 +30,10 @@ export const getPrebuiltRulesAndTimelinesStatusRoute = (router: SecuritySolution .get({ access: 'public', path: PREBUILT_RULES_STATUS_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_status/get_prebuilt_rules_status_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_status/get_prebuilt_rules_status_route.ts index 86809a3a79a93..0561c826e0c78 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_status/get_prebuilt_rules_status_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_status/get_prebuilt_rules_status_route.ts @@ -20,8 +20,10 @@ export const getPrebuiltRulesStatusRoute = (router: SecuritySolutionPluginRouter .get({ access: 'internal', path: GET_PREBUILT_RULES_STATUS_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/install_prebuilt_rules_and_timelines_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/install_prebuilt_rules_and_timelines_route.ts index 11e841ed50431..8740d27fce817 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/install_prebuilt_rules_and_timelines_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/install_prebuilt_rules_and_timelines/install_prebuilt_rules_and_timelines_route.ts @@ -33,8 +33,12 @@ export const installPrebuiltRulesAndTimelinesRoute = (router: SecuritySolutionPl .put({ access: 'public', path: PREBUILT_RULES_URL, + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, + }, options: { - tags: ['access:securitySolution'], timeout: { idleSocket: PREBUILT_RULES_OPERATION_SOCKET_TIMEOUT_MS, }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_installation/perform_rule_installation_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_installation/perform_rule_installation_route.ts index 1a29568ca496b..8b4d38bd2f4a4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_installation/perform_rule_installation_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_installation/perform_rule_installation_route.ts @@ -34,8 +34,12 @@ export const performRuleInstallationRoute = (router: SecuritySolutionPluginRoute .post({ access: 'internal', path: PERFORM_RULE_INSTALLATION_URL, + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, + }, options: { - tags: ['access:securitySolution'], timeout: { idleSocket: PREBUILT_RULES_OPERATION_SOCKET_TIMEOUT_MS, }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_route.ts index c8b5d459f6787..db5f7a186d303 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_route.ts @@ -35,8 +35,12 @@ export const performRuleUpgradeRoute = ( .post({ access: 'internal', path: PERFORM_RULE_UPGRADE_URL, + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, + }, options: { - tags: ['access:securitySolution'], timeout: { idleSocket: PREBUILT_RULES_OPERATION_SOCKET_TIMEOUT_MS, }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_installation/review_rule_installation_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_installation/review_rule_installation_route.ts index 00fc5e2beb5b8..c1c45532f61bb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_installation/review_rule_installation_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_installation/review_rule_installation_route.ts @@ -26,8 +26,12 @@ export const reviewRuleInstallationRoute = (router: SecuritySolutionPluginRouter .post({ access: 'internal', path: REVIEW_RULE_INSTALLATION_URL, + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, + }, options: { - tags: ['access:securitySolution'], timeout: { idleSocket: PREBUILT_RULES_OPERATION_SOCKET_TIMEOUT_MS, }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/review_rule_upgrade_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/review_rule_upgrade_route.ts index 382ec27a1bf35..3da62bd9bb21d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/review_rule_upgrade_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/review_rule_upgrade_route.ts @@ -35,8 +35,12 @@ export const reviewRuleUpgradeRoute = (router: SecuritySolutionPluginRouter) => .post({ access: 'internal', path: REVIEW_RULE_UPGRADE_URL, + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, + }, options: { - tags: ['access:securitySolution'], timeout: { idleSocket: PREBUILT_RULES_OPERATION_SOCKET_TIMEOUT_MS, }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts index 658a9b193e0a2..e599ff4a936ef 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts @@ -61,8 +61,13 @@ export const performBulkActionRoute = ( .post({ access: 'public', path: DETECTION_ENGINE_RULES_BULK_ACTION, + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, + }, options: { - tags: ['access:securitySolution', routeLimitedConcurrencyTag(MAX_ROUTE_CONCURRENCY)], + tags: [routeLimitedConcurrencyTag(MAX_ROUTE_CONCURRENCY)], timeout: { idleSocket: RULE_MANAGEMENT_BULK_ACTION_SOCKET_TIMEOUT_MS, }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.ts index 9a3751bfb1d04..225782ef01942 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.ts @@ -39,8 +39,12 @@ export const bulkCreateRulesRoute = (router: SecuritySolutionPluginRouter, logge .post({ access: 'public', path: DETECTION_ENGINE_RULES_BULK_CREATE, + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, + }, options: { - tags: ['access:securitySolution'], timeout: { idleSocket: RULE_MANAGEMENT_BULK_ACTION_SOCKET_TIMEOUT_MS, }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.ts index fc5edf0e65ac3..5bd8adaf86a38 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.ts @@ -33,8 +33,12 @@ export const bulkPatchRulesRoute = (router: SecuritySolutionPluginRouter, logger .patch({ access: 'public', path: DETECTION_ENGINE_RULES_BULK_UPDATE, + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, + }, options: { - tags: ['access:securitySolution'], timeout: { idleSocket: RULE_MANAGEMENT_BULK_ACTION_SOCKET_TIMEOUT_MS, }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.ts index cccd1656d5091..d2c985fbc70e6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.ts @@ -37,8 +37,12 @@ export const bulkUpdateRulesRoute = (router: SecuritySolutionPluginRouter, logge .put({ access: 'public', path: DETECTION_ENGINE_RULES_BULK_UPDATE, + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, + }, options: { - tags: ['access:securitySolution'], timeout: { idleSocket: RULE_MANAGEMENT_BULK_ACTION_SOCKET_TIMEOUT_MS, }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/coverage_overview/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/coverage_overview/route.ts index 0a298008dd354..a959c522c1718 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/coverage_overview/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/coverage_overview/route.ts @@ -22,8 +22,10 @@ export const getCoverageOverviewRoute = (router: SecuritySolutionPluginRouter) = .post({ access: 'internal', path: RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts index aa6425b2e673c..a5fee66d00148 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts @@ -27,8 +27,10 @@ export const createRuleRoute = (router: SecuritySolutionPluginRouter): void => { access: 'public', path: DETECTION_ENGINE_RULES_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.ts index 42a6a1c47544f..a5854e9a2caf5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.ts @@ -24,8 +24,10 @@ export const deleteRuleRoute = (router: SecuritySolutionPluginRouter) => { .delete({ access: 'public', path: DETECTION_ENGINE_RULES_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/export_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/export_rules/route.ts index 3c770c714334c..a37bb29963332 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/export_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/export_rules/route.ts @@ -33,8 +33,12 @@ export const exportRulesRoute = ( .post({ access: 'public', path: `${DETECTION_ENGINE_RULES_URL}/_export`, + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, + }, options: { - tags: ['access:securitySolution'], timeout: { idleSocket: RULE_MANAGEMENT_IMPORT_EXPORT_SOCKET_TIMEOUT_MS, }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/filters/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/filters/route.ts index 183d4f8e2f78d..05633892cdddb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/filters/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/filters/route.ts @@ -56,8 +56,10 @@ export const getRuleManagementFilters = (router: SecuritySolutionPluginRouter) = .get({ access: 'internal', path: RULE_MANAGEMENT_FILTERS_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.ts index 02ff637ab6f10..899e568e79630 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/find_rules/route.ts @@ -25,8 +25,10 @@ export const findRulesRoute = (router: SecuritySolutionPluginRouter, logger: Log .get({ access: 'public', path: DETECTION_ENGINE_RULES_URL_FIND, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts index e9131050d9629..d6a5213fcbea6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts @@ -47,8 +47,12 @@ export const importRulesRoute = (router: SecuritySolutionPluginRouter, config: C .post({ access: 'public', path: `${DETECTION_ENGINE_RULES_URL}/_import`, + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, + }, options: { - tags: ['access:securitySolution'], body: { maxBytes: config.maxRuleImportPayloadBytes, output: 'stream', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts index 3886f63c482b0..fcd388e81d1e7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts @@ -26,8 +26,10 @@ export const patchRuleRoute = (router: SecuritySolutionPluginRouter) => { .patch({ access: 'public', path: DETECTION_ENGINE_RULES_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/read_rule/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/read_rule/route.ts index a119d1afae912..f8826e8aad45b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/read_rule/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/read_rule/route.ts @@ -24,8 +24,10 @@ export const readRuleRoute = (router: SecuritySolutionPluginRouter, logger: Logg .get({ access: 'public', path: DETECTION_ENGINE_RULES_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts index fb7a7a9e3197d..0bedcb25de528 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts @@ -27,8 +27,10 @@ export const updateRuleRoute = (router: SecuritySolutionPluginRouter) => { .put({ access: 'public', path: DETECTION_ENGINE_RULES_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/tags/read_tags/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/tags/read_tags/route.ts index 5120603f9f674..d94f695f39179 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/tags/read_tags/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/tags/read_tags/route.ts @@ -18,8 +18,10 @@ export const readTagsRoute = (router: SecuritySolutionPluginRouter) => { .get({ access: 'public', path: DETECTION_ENGINE_TAGS_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/detection_engine_health/get_cluster_health/get_cluster_health_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/detection_engine_health/get_cluster_health/get_cluster_health_route.ts index 719f46788a524..d6d9e6843e5a2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/detection_engine_health/get_cluster_health/get_cluster_health_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/detection_engine_health/get_cluster_health/get_cluster_health_route.ts @@ -36,8 +36,10 @@ export const getClusterHealthRoute = (router: SecuritySolutionPluginRouter) => { .get({ access: 'internal', path: GET_CLUSTER_HEALTH_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, }) .addVersion( @@ -62,8 +64,10 @@ export const getClusterHealthRoute = (router: SecuritySolutionPluginRouter) => { .post({ access: 'internal', path: GET_CLUSTER_HEALTH_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/detection_engine_health/get_rule_health/get_rule_health_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/detection_engine_health/get_rule_health/get_rule_health_route.ts index a69f7961b19f8..401040b33faa5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/detection_engine_health/get_rule_health/get_rule_health_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/detection_engine_health/get_rule_health/get_rule_health_route.ts @@ -33,8 +33,10 @@ export const getRuleHealthRoute = (router: SecuritySolutionPluginRouter) => { .post({ access: 'internal', path: GET_RULE_HEALTH_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/detection_engine_health/get_space_health/get_space_health_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/detection_engine_health/get_space_health/get_space_health_route.ts index 96ced4e34151d..772de5aead760 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/detection_engine_health/get_space_health/get_space_health_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/detection_engine_health/get_space_health/get_space_health_route.ts @@ -36,8 +36,10 @@ export const getSpaceHealthRoute = (router: SecuritySolutionPluginRouter) => { .get({ access: 'internal', path: GET_SPACE_HEALTH_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, }) .addVersion( @@ -62,8 +64,10 @@ export const getSpaceHealthRoute = (router: SecuritySolutionPluginRouter) => { .post({ access: 'internal', path: GET_SPACE_HEALTH_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/detection_engine_health/setup/setup_health_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/detection_engine_health/setup/setup_health_route.ts index 685ce8f677952..0e8e5e5b676fa 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/detection_engine_health/setup/setup_health_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/detection_engine_health/setup/setup_health_route.ts @@ -22,8 +22,10 @@ export const setupHealthRoute = (router: SecuritySolutionPluginRouter) => { .post({ access: 'internal', path: SETUP_HEALTH_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts index 4e8001193b5c5..fc3c485710c1a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_events/get_rule_execution_events_route.ts @@ -27,8 +27,10 @@ export const getRuleExecutionEventsRoute = (router: SecuritySolutionPluginRouter .get({ access: 'internal', path: GET_RULE_EXECUTION_EVENTS_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_results/get_rule_execution_results_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_results/get_rule_execution_results_route.ts index bf3a9864260ac..c23396e139afc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_results/get_rule_execution_results_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_results/get_rule_execution_results_route.ts @@ -27,8 +27,10 @@ export const getRuleExecutionResultsRoute = (router: SecuritySolutionPluginRoute .get({ access: 'internal', path: GET_RULE_EXECUTION_RESULTS_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, }) .addVersion( diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/constants.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/constants.ts index 8b2e802b17b6d..dc455e3006e3d 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/constants.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/constants.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { EngineStatus } from '../../../../common/api/entity_analytics'; +import type { EngineStatus, StoreStatus } from '../../../../common/api/entity_analytics'; export const DEFAULT_LOOKBACK_PERIOD = '24h'; @@ -17,4 +17,12 @@ export const ENGINE_STATUS: Record, EngineStatus> = { ERROR: 'error', }; +export const ENTITY_STORE_STATUS: Record, StoreStatus> = { + RUNNING: 'running', + STOPPED: 'stopped', + INSTALLING: 'installing', + NOT_INSTALLED: 'not_installed', + ERROR: 'error', +}; + export const MAX_SEARCH_RESPONSE_SIZE = 10_000; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts index 7413c365b5da6..b99fa8935b692 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts @@ -20,17 +20,22 @@ import type { TaskManagerStartContract } from '@kbn/task-manager-plugin/server'; import type { DataViewsService } from '@kbn/data-views-plugin/common'; import { isEqual } from 'lodash/fp'; import moment from 'moment'; +import type { + GetEntityStoreStatusResponse, + InitEntityStoreRequestBody, + InitEntityStoreResponse, +} from '../../../../common/api/entity_analytics/entity_store/enablement.gen'; import type { AppClient } from '../../..'; +import { EntityType } from '../../../../common/api/entity_analytics'; import type { Entity, EngineDataviewUpdateResult, InitEntityEngineRequestBody, InitEntityEngineResponse, - EntityType, InspectQuery, } from '../../../../common/api/entity_analytics'; import { EngineDescriptorClient } from './saved_object/engine_descriptor'; -import { ENGINE_STATUS, MAX_SEARCH_RESPONSE_SIZE } from './constants'; +import { ENGINE_STATUS, ENTITY_STORE_STATUS, MAX_SEARCH_RESPONSE_SIZE } from './constants'; import { AssetCriticalityEcsMigrationClient } from '../asset_criticality/asset_criticality_migration_client'; import { getUnitedEntityDefinition } from './united_entity_definitions'; import { @@ -126,6 +131,44 @@ export class EntityStoreDataClient { }); } + public async enable( + { indexPattern = '', filter = '', fieldHistoryLength = 10 }: InitEntityStoreRequestBody, + { pipelineDebugMode = false }: { pipelineDebugMode?: boolean } = {} + ): Promise { + if (!this.options.taskManager) { + throw new Error('Task Manager is not available'); + } + + // Immediately defer the initialization to the next tick. This way we don't block on the init preflight checks + const run = (fn: () => Promise) => + new Promise((resolve) => setTimeout(() => fn().then(resolve), 0)); + const promises = Object.values(EntityType.Values).map((entity) => + run(() => + this.init(entity, { indexPattern, filter, fieldHistoryLength }, { pipelineDebugMode }) + ) + ); + + const engines = await Promise.all(promises); + return { engines, succeeded: true }; + } + + public async status(): Promise { + const { engines, count } = await this.engineClient.list(); + + let status = ENTITY_STORE_STATUS.RUNNING; + if (count === 0) { + status = ENTITY_STORE_STATUS.NOT_INSTALLED; + } else if (engines.some((engine) => engine.status === ENGINE_STATUS.ERROR)) { + status = ENTITY_STORE_STATUS.ERROR; + } else if (engines.every((engine) => engine.status === ENGINE_STATUS.STOPPED)) { + status = ENTITY_STORE_STATUS.STOPPED; + } else if (engines.some((engine) => engine.status === ENGINE_STATUS.INSTALLING)) { + status = ENTITY_STORE_STATUS.INSTALLING; + } + + return { engines, status }; + } + public async init( entityType: EntityType, { indexPattern = '', filter = '', fieldHistoryLength = 10 }: InitEntityEngineRequestBody, @@ -137,7 +180,16 @@ export class EntityStoreDataClient { const { config } = this.options; - await this.riskScoreDataClient.createRiskScoreLatestIndex(); + await this.riskScoreDataClient.createRiskScoreLatestIndex().catch((e) => { + if (e.meta.body.error.type === 'resource_already_exists_exception') { + this.options.logger.debug( + `Risk score index for ${entityType} already exists, skipping creation.` + ); + return; + } + + throw e; + }); const requiresMigration = await this.assetCriticalityMigrationClient.isEcsDataMigrationRequired(); diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/enablement.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/enablement.ts new file mode 100644 index 0000000000000..16813fccdf235 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/enablement.ts @@ -0,0 +1,67 @@ +/* + * 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 { IKibanaResponse, Logger } from '@kbn/core/server'; +import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; +import { transformError } from '@kbn/securitysolution-es-utils'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; + +import type { InitEntityStoreResponse } from '../../../../../common/api/entity_analytics/entity_store/enablement.gen'; +import { InitEntityStoreRequestBody } from '../../../../../common/api/entity_analytics/entity_store/enablement.gen'; +import { API_VERSIONS, APP_ID } from '../../../../../common/constants'; +import type { EntityAnalyticsRoutesDeps } from '../../types'; +import { checkAndInitAssetCriticalityResources } from '../../asset_criticality/check_and_init_asset_criticality_resources'; + +export const enableEntityStoreRoute = ( + router: EntityAnalyticsRoutesDeps['router'], + logger: Logger, + config: EntityAnalyticsRoutesDeps['config'] +) => { + router.versioned + .post({ + access: 'public', + path: '/api/entity_store/enable', + security: { + authz: { + requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`], + }, + }, + }) + .addVersion( + { + version: API_VERSIONS.public.v1, + validate: { + request: { + body: buildRouteValidationWithZod(InitEntityStoreRequestBody), + }, + }, + }, + + async (context, request, response): Promise> => { + const siemResponse = buildSiemResponse(response); + const secSol = await context.securitySolution; + const { pipelineDebugMode } = config.entityAnalytics.entityStore.developer; + + await checkAndInitAssetCriticalityResources(context, logger); + + try { + const body: InitEntityStoreResponse = await secSol + .getEntityStoreDataClient() + .enable(request.body, { pipelineDebugMode }); + + return response.ok({ body }); + } catch (e) { + const error = transformError(e); + logger.error(`Error initialising entity store: ${error.message}`); + return siemResponse.error({ + statusCode: error.statusCode, + body: error.message, + }); + } + } + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/register_entity_store_routes.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/register_entity_store_routes.ts index 9784dcd619667..c3c66d0b32e28 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/register_entity_store_routes.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/register_entity_store_routes.ts @@ -15,6 +15,8 @@ import { listEntityEnginesRoute } from './list'; import { entityStoreInternalPrivilegesRoute } from './privileges'; import { startEntityEngineRoute } from './start'; import { stopEntityEngineRoute } from './stop'; +import { getEntityStoreStatusRoute } from './status'; +import { enableEntityStoreRoute } from './enablement'; export const registerEntityStoreRoutes = ({ router, @@ -22,6 +24,8 @@ export const registerEntityStoreRoutes = ({ getStartServices, config, }: EntityAnalyticsRoutesDeps) => { + enableEntityStoreRoute(router, logger, config); + getEntityStoreStatusRoute(router, logger, config); initEntityEngineRoute(router, logger, config); startEntityEngineRoute(router, logger); stopEntityEngineRoute(router, logger); diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/status.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/status.ts new file mode 100644 index 0000000000000..7a59b59b9914a --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/routes/status.ts @@ -0,0 +1,70 @@ +/* + * 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. + */ + +/* + * 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 { IKibanaResponse, Logger } from '@kbn/core/server'; +import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; +import { transformError } from '@kbn/securitysolution-es-utils'; +import type { GetEntityStoreStatusResponse } from '../../../../../common/api/entity_analytics/entity_store/enablement.gen'; +import { API_VERSIONS, APP_ID } from '../../../../../common/constants'; +import type { EntityAnalyticsRoutesDeps } from '../../types'; +import { checkAndInitAssetCriticalityResources } from '../../asset_criticality/check_and_init_asset_criticality_resources'; + +export const getEntityStoreStatusRoute = ( + router: EntityAnalyticsRoutesDeps['router'], + logger: Logger, + config: EntityAnalyticsRoutesDeps['config'] +) => { + router.versioned + .get({ + access: 'public', + path: '/api/entity_store/status', + security: { + authz: { + requiredPrivileges: ['securitySolution', `${APP_ID}-entity-analytics`], + }, + }, + }) + .addVersion( + { + version: API_VERSIONS.public.v1, + validate: {}, + }, + + async ( + context, + request, + response + ): Promise> => { + const siemResponse = buildSiemResponse(response); + const secSol = await context.securitySolution; + + await checkAndInitAssetCriticalityResources(context, logger); + + try { + const body: GetEntityStoreStatusResponse = await secSol + .getEntityStoreDataClient() + .status(); + + return response.ok({ body }); + } catch (e) { + const error = transformError(e); + logger.error(`Error initialising entity store: ${error.message}`); + return siemResponse.error({ + statusCode: error.statusCode, + body: error.message, + }); + } + } + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.ts index 6515817f28e11..fb6ffba7995b8 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/clean_draft_timelines/index.ts @@ -31,8 +31,10 @@ export const cleanDraftTimelinesRoute = (router: SecuritySolutionPluginRouter) = router.versioned .post({ path: TIMELINE_DRAFT_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, access: 'public', }) diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.ts index 1ba3167cdefae..e83d2cc839db0 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/draft_timelines/get_draft_timelines/index.ts @@ -24,8 +24,10 @@ export const getDraftTimelinesRoute = (router: SecuritySolutionPluginRouter) => router.versioned .get({ path: TIMELINE_DRAFT_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, access: 'public', }) diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/delete_note.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/delete_note.ts index 9e6aeb5473fc2..7308801030f4a 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/delete_note.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/delete_note.ts @@ -22,8 +22,10 @@ export const deleteNoteRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .delete({ path: NOTE_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, access: 'public', }) diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/get_notes.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/get_notes.ts index 0cd7853b38a1b..3a1ae1ba27e2f 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/get_notes.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/get_notes.ts @@ -37,8 +37,10 @@ export const getNotesRoute = ( router.versioned .get({ path: NOTE_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, access: 'public', }) diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/persist_note.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/persist_note.ts index 2e825b4ff3a15..f9759444b26d8 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/persist_note.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/notes/persist_note.ts @@ -25,8 +25,10 @@ export const persistNoteRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .patch({ path: NOTE_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, access: 'public', }) diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/pinned_events/persist_pinned_event.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/pinned_events/persist_pinned_event.ts index 74db9e58d904b..51b001c9ea29e 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/pinned_events/persist_pinned_event.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/pinned_events/persist_pinned_event.ts @@ -26,8 +26,10 @@ export const persistPinnedEventRoute = (router: SecuritySolutionPluginRouter) => router.versioned .patch({ path: PINNED_EVENT_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, access: 'public', }) diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/index.ts index b1a6e2f781f45..99c4d95942f15 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines/index.ts @@ -34,8 +34,12 @@ export const installPrepackedTimelinesRoute = ( router.versioned .post({ path: `${TIMELINE_PREPACKAGED_URL}`, + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, + }, options: { - tags: ['access:securitySolution'], body: { maxBytes: config.maxTimelineImportPayloadBytes, output: 'stream', diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/copy_timeline/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/copy_timeline/index.ts index e795ec89dd926..502b43d4e347f 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/copy_timeline/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/copy_timeline/index.ts @@ -23,8 +23,10 @@ export const copyTimelineRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .post({ path: TIMELINE_COPY_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, access: 'internal', }) diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.ts index 95fb09fb28e56..a91fefc20f934 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/create_timelines/index.ts @@ -32,8 +32,10 @@ export const createTimelinesRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .post({ path: TIMELINE_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, access: 'public', }) diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/delete_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/delete_timelines/index.ts index 8dd476c9f4e44..07cffb3e13bf5 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/delete_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/delete_timelines/index.ts @@ -23,8 +23,10 @@ export const deleteTimelinesRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .delete({ path: TIMELINE_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, access: 'public', }) diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/export_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/export_timelines/index.ts index 163b212840423..5a055d54a76ce 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/export_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/export_timelines/index.ts @@ -26,8 +26,10 @@ export const exportTimelinesRoute = (router: SecuritySolutionPluginRouter, confi router.versioned .post({ path: TIMELINE_EXPORT_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, access: 'public', }) diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.ts index 870955f7e8691..a1ae2178fb6fd 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timeline/index.ts @@ -26,8 +26,10 @@ export const getTimelineRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .get({ path: TIMELINE_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, access: 'public', }) diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.ts index 52995efcf4be1..01a3801ad8672 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/get_timelines/index.ts @@ -25,8 +25,10 @@ export const getTimelinesRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .get({ path: TIMELINES_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, access: 'public', }) diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.ts index 59a86238941ab..f66c5456c0396 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/import_timelines/index.ts @@ -32,8 +32,12 @@ export const importTimelinesRoute = (router: SecuritySolutionPluginRouter, confi router.versioned .post({ path: `${TIMELINE_IMPORT_URL}`, + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, + }, options: { - tags: ['access:securitySolution'], body: { maxBytes: config.maxTimelineImportPayloadBytes, output: 'stream', diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.ts index 1297f0cb1a829..7ddea9bd5ffe7 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/patch_timelines/index.ts @@ -26,8 +26,10 @@ export const patchTimelinesRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .patch({ path: TIMELINE_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, access: 'public', }) diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/persist_favorite/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/persist_favorite/index.ts index cf66c02cf9c97..22d579229a73b 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/persist_favorite/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/persist_favorite/index.ts @@ -26,8 +26,10 @@ export const persistFavoriteRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .patch({ path: TIMELINE_FAVORITE_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, access: 'public', }) diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/resolve_timeline/index.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/resolve_timeline/index.ts index 0afc7d21ae296..773e74faaaf46 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/resolve_timeline/index.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/timelines/resolve_timeline/index.ts @@ -27,8 +27,10 @@ export const resolveTimelineRoute = (router: SecuritySolutionPluginRouter) => { router.versioned .get({ path: TIMELINE_RESOLVE_URL, - options: { - tags: ['access:securitySolution'], + security: { + authz: { + requiredPrivileges: ['securitySolution'], + }, }, access: 'public', }) diff --git a/x-pack/plugins/serverless_search/common/i18n_string.ts b/x-pack/plugins/serverless_search/common/i18n_string.ts index cf0dbad5277c8..32ec0cf8eb957 100644 --- a/x-pack/plugins/serverless_search/common/i18n_string.ts +++ b/x-pack/plugins/serverless_search/common/i18n_string.ts @@ -65,6 +65,13 @@ export const CONNECTOR_LABEL: string = i18n.translate('xpack.serverlessSearch.co defaultMessage: 'Connector', }); +export const WEB_CRAWLERS_LABEL: string = i18n.translate( + 'xpack.serverlessSearch.webCrawlersLabel', + { + defaultMessage: 'Web crawlers', + } +); + export const DELETE_CONNECTOR_LABEL = i18n.translate( 'xpack.serverlessSearch.connectors.deleteConnectorLabel', { diff --git a/x-pack/plugins/serverless_search/public/application/components/common/decorative_horizontal_stepper.tsx b/x-pack/plugins/serverless_search/public/application/components/common/decorative_horizontal_stepper.tsx new file mode 100644 index 0000000000000..99711dcb9c71e --- /dev/null +++ b/x-pack/plugins/serverless_search/public/application/components/common/decorative_horizontal_stepper.tsx @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { EuiStepsHorizontal, EuiStepsHorizontalProps } from '@elastic/eui'; +import { css } from '@emotion/react'; + +interface DecorativeHorizontalStepperProps { + stepCount?: number; +} + +export const DecorativeHorizontalStepper: React.FC = ({ + stepCount = 2, +}) => { + // Generate the steps dynamically based on the stepCount prop + const horizontalSteps: EuiStepsHorizontalProps['steps'] = Array.from( + { length: stepCount }, + (_, index) => ({ + title: '', + status: 'incomplete', + onClick: () => {}, + }) + ); + + return ( + /* This is a presentational component, not intended for user interaction: + pointer-events: none, prevents user interaction with the component. + inert prevents click, focus, and other interactive events, removing it from the tab order. + role="presentation" indicates that this component is purely decorative and not interactive for assistive technologies. + Together, these attributes help ensure the component is accesible. */ + + ); +}; diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors/connector_icon.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors/connector_icon.tsx new file mode 100644 index 0000000000000..7ae2961ef10b8 --- /dev/null +++ b/x-pack/plugins/serverless_search/public/application/components/connectors/connector_icon.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiToolTip, EuiIcon } from '@elastic/eui'; + +export const ConnectorIcon: React.FC<{ name: string; serviceType: string; iconPath?: string }> = ({ + name, + serviceType, + iconPath, +}) => ( + + + +); diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors/elastic_managed_connector_coming_soon.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors/elastic_managed_connector_coming_soon.tsx new file mode 100644 index 0000000000000..3057c6806fd73 --- /dev/null +++ b/x-pack/plugins/serverless_search/public/application/components/connectors/elastic_managed_connector_coming_soon.tsx @@ -0,0 +1,196 @@ +/* + * 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 { + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiIcon, + EuiTitle, + EuiText, + EuiBadge, + EuiButtonEmpty, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +// import { generatePath } from 'react-router-dom'; +import { SERVERLESS_ES_CONNECTORS_ID } from '@kbn/deeplinks-search/constants'; +import { useKibanaServices } from '../../hooks/use_kibana'; +import { useConnectorTypes } from '../../hooks/api/use_connector_types'; +import { useAssetBasePath } from '../../hooks/use_asset_base_path'; + +import { BACK_LABEL } from '../../../../common/i18n_string'; +// import { BASE_CONNECTORS_PATH } from '../../constants'; +import { ConnectorIcon } from './connector_icon'; +import { DecorativeHorizontalStepper } from '../common/decorative_horizontal_stepper'; + +export const ElasticManagedConnectorComingSoon: React.FC = () => { + const connectorTypes = useConnectorTypes(); + + const connectorExamples = connectorTypes.filter((connector) => + ['Gmail', 'Sharepoint Online', 'Jira Cloud', 'Dropbox'].includes(connector.name) + ); + + const { + application: { navigateToApp }, + } = useKibanaServices(); + + const assetBasePath = useAssetBasePath(); + const connectorsIcon = assetBasePath + '/connectors.svg'; + return ( + + + + + + navigateToApp(SERVERLESS_ES_CONNECTORS_ID)} + > + {BACK_LABEL} + + + + + + + +

+ {i18n.translate('xpack.serverlessSearch.elasticManagedConnectorEmpty.title', { + defaultMessage: 'Elastic managed connectors', + })} +

+
+
+ + Coming soon + + + +

+ {i18n.translate( + 'xpack.serverlessSearch.elasticManagedConnectorEmpty.description', + { + defaultMessage: + "We're actively developing Elastic managed connectors, that won't require any self-managed infrastructure. You'll be able to handle all configuration in the UI. This will simplify syncing your data into a serverless Elasticsearch project. This new workflow will have two steps:", + } + )} +

+
+
+ + + + + + + + + + + + {connectorExamples.map((connector, index) => ( + + {index === Math.floor(connectorExamples.length / 2) && ( + + + + )} + + + + + ))} + + + + +

+ {i18n.translate( + 'xpack.serverlessSearch.elasticManagedConnectorEmpty.guideOneDescription', + { + defaultMessage: + "Choose from over 30 third-party data sources you'd like to sync", + } + )} +

+
+
+
+
+ + + + + + + + + + + + + + +

+ {i18n.translate( + 'xpack.serverlessSearch.elasticManagedConnectorEmpty.guideThreeDescription', + { + defaultMessage: + 'Enter access and connection details for your data source and run your first sync using the Kibana UI', + } + )} +

+
+
+
+
+
+
+
+
+
+
+
+
+ ); +}; diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors/empty_connectors_prompt.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors/empty_connectors_prompt.tsx index 56c7a9aaf8155..0767f8cfaf276 100644 --- a/x-pack/plugins/serverless_search/public/application/components/connectors/empty_connectors_prompt.tsx +++ b/x-pack/plugins/serverless_search/public/application/components/connectors/empty_connectors_prompt.tsx @@ -14,30 +14,49 @@ import { EuiText, EuiLink, EuiButton, - EuiToolTip, + EuiBadge, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { docLinks } from '../../../../common/doc_links'; +import { useKibanaServices } from '../../hooks/use_kibana'; import { useConnectorTypes } from '../../hooks/api/use_connector_types'; import { useCreateConnector } from '../../hooks/api/use_create_connector'; import { useAssetBasePath } from '../../hooks/use_asset_base_path'; import { useConnectors } from '../../hooks/api/use_connectors'; +import { DecorativeHorizontalStepper } from '../common/decorative_horizontal_stepper'; +import { ConnectorIcon } from './connector_icon'; + +import { ELASTIC_MANAGED_CONNECTOR_PATH, BASE_CONNECTORS_PATH } from '../../constants'; export const EmptyConnectorsPrompt: React.FC = () => { const connectorTypes = useConnectorTypes(); + + const connectorExamples = connectorTypes.filter((connector) => + ['Gmail', 'Sharepoint Online', 'Jira Cloud', 'Dropbox'].includes(connector.name) + ); const { createConnector, isLoading } = useCreateConnector(); const { data } = useConnectors(); const assetBasePath = useAssetBasePath(); const connectorsPath = assetBasePath + '/connectors.svg'; + + const { + application: { navigateToUrl }, + } = useKibanaServices(); + return ( - + @@ -45,13 +64,13 @@ export const EmptyConnectorsPrompt: React.FC = () => {

{i18n.translate('xpack.serverlessSearch.connectorsEmpty.title', { - defaultMessage: 'Create a connector', + defaultMessage: 'Set up a new connector', })}

- +

{i18n.translate('xpack.serverlessSearch.connectorsEmpty.description', { defaultMessage: @@ -60,155 +79,215 @@ export const EmptyConnectorsPrompt: React.FC = () => {

- - - - - - - - - - -

- {i18n.translate( - 'xpack.serverlessSearch.connectorsEmpty.guideOneDescription', - { - defaultMessage: "Choose a data source you'd like to sync", - } - )} -

-
-
-
+ + + + + - - - - - - - -

- - {i18n.translate( - 'xpack.serverlessSearch.connectorsEmpty.sourceLabel', - { defaultMessage: 'source' } - )} - - ), - docker: ( - - {i18n.translate( - 'xpack.serverlessSearch.connectorsEmpty.dockerLabel', - { defaultMessage: 'Docker' } - )} - - ), - }} - /> -

-
-
-
-
- - - + + + + + + {connectorExamples.map((connector, index) => ( + + {index === Math.floor(connectorExamples.length / 2) && ( + + + + )} + + + + + ))} + + + + +

+ {i18n.translate( + 'xpack.serverlessSearch.connectorsEmpty.guideOneDescription', + { + defaultMessage: + "Choose from over 30 third-party data sources you'd like to sync", + } + )} +

+
+
+
+
+ + - - - - - - - + - + - - - -

- {i18n.translate( - 'xpack.serverlessSearch.connectorsEmpty.guideThreeDescription', - { - defaultMessage: - 'Enter access and connection details for your data source and run your first sync', - } - )} -

-
-
-
+ + +

+ + {i18n.translate( + 'xpack.serverlessSearch.connectorsEmpty.sourceLabel', + { defaultMessage: 'source' } + )} + + ), + docker: ( + + {i18n.translate( + 'xpack.serverlessSearch.connectorsEmpty.dockerLabel', + { defaultMessage: 'Docker' } + )} + + ), + }} + /> +

+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + +

+ {i18n.translate( + 'xpack.serverlessSearch.connectorsEmpty.guideThreeDescription', + { + defaultMessage: + 'Enter access and connection details for your data source and run your first sync', + } + )} +

+
+
+
+
+
+
+
+
+ + + createConnector()} + isLoading={isLoading} + > + {i18n.translate('xpack.serverlessSearch.connectorsEmpty.selfManagedButton', { + defaultMessage: 'Self-managed connector', + })} + + + + + + + navigateToUrl(`${BASE_CONNECTORS_PATH}/${ELASTIC_MANAGED_CONNECTOR_PATH}`) + } + > + {i18n.translate( + 'xpack.serverlessSearch.connectorsEmpty.elasticManagedButton', + { + defaultMessage: 'Elastic managed connector', + } + )} + + + + Coming soon - - - - createConnector()} - isLoading={isLoading} - > - {i18n.translate('xpack.serverlessSearch.connectorsEmpty.createConnector', { - defaultMessage: 'Create connector', - })} - - + + - - -

- {i18n.translate('xpack.serverlessSearch.connectorsEmpty.availableConnectors', { - defaultMessage: 'Available connectors', - })} -

-
-
- - - {connectorTypes.map((connectorType) => ( - - - - - - ))} - - ); }; diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors_elastic_managed.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors_elastic_managed.tsx new file mode 100644 index 0000000000000..e645ede3d67e8 --- /dev/null +++ b/x-pack/plugins/serverless_search/public/application/components/connectors_elastic_managed.tsx @@ -0,0 +1,63 @@ +/* + * 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 { EuiLink, EuiPageTemplate, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React, { useMemo } from 'react'; + +import { LEARN_MORE_LABEL } from '../../../common/i18n_string'; + +import { useKibanaServices } from '../hooks/use_kibana'; +import { ElasticManagedConnectorComingSoon } from './connectors/elastic_managed_connector_coming_soon'; + +import { docLinks } from '../../../common/doc_links'; + +export const ConnectorsElasticManaged = () => { + const { console: consolePlugin } = useKibanaServices(); + + const embeddableConsole = useMemo( + () => (consolePlugin?.EmbeddableConsole ? : null), + [consolePlugin] + ); + + return ( + + + +

+ + {LEARN_MORE_LABEL} + + ), + }} + /> +

+
+
+ + + + {embeddableConsole} +
+ ); +}; diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors_overview.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors_overview.tsx index f059a8d531eac..775cec8db1551 100644 --- a/x-pack/plugins/serverless_search/public/application/components/connectors_overview.tsx +++ b/x-pack/plugins/serverless_search/public/application/components/connectors_overview.tsx @@ -7,30 +7,36 @@ import { EuiButton, + EuiCallOut, EuiFlexGroup, EuiFlexItem, - EuiIcon, EuiLink, EuiPageTemplate, + EuiSpacer, EuiText, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import React, { useMemo } from 'react'; +import React, { useMemo, useState } from 'react'; +import { GithubLink } from '@kbn/search-api-panels'; import { docLinks } from '../../../common/doc_links'; import { LEARN_MORE_LABEL } from '../../../common/i18n_string'; -import { PLUGIN_ID } from '../../../common'; import { useConnectors } from '../hooks/api/use_connectors'; import { useCreateConnector } from '../hooks/api/use_create_connector'; import { useKibanaServices } from '../hooks/use_kibana'; import { EmptyConnectorsPrompt } from './connectors/empty_connectors_prompt'; import { ConnectorsTable } from './connectors/connectors_table'; import { ConnectorPrivilegesCallout } from './connectors/connector_config/connector_privileges_callout'; +import { useAssetBasePath } from '../hooks/use_asset_base_path'; + +import { BASE_CONNECTORS_PATH, ELASTIC_MANAGED_CONNECTOR_PATH } from '../constants'; + +const CALLOUT_KEY = 'search.connectors.ElasticManaged.ComingSoon.feedbackCallout'; export const ConnectorsOverview = () => { const { data, isLoading: connectorsLoading } = useConnectors(); - const { http, console: consolePlugin } = useKibanaServices(); + const { console: consolePlugin } = useKibanaServices(); const { createConnector, isLoading } = useCreateConnector(); const embeddableConsole = useMemo( () => (consolePlugin?.EmbeddableConsole ? : null), @@ -39,6 +45,18 @@ export const ConnectorsOverview = () => { const canManageConnectors = !data || data.canManageConnectors; + const { + application: { navigateToUrl }, + } = useKibanaServices(); + + const [showCallOut, setShowCallOut] = useState(sessionStorage.getItem(CALLOUT_KEY) !== 'hidden'); + + const onDismiss = () => { + setShowCallOut(false); + sessionStorage.setItem(CALLOUT_KEY, 'hidden'); + }; + const assetBasePath = useAssetBasePath(); + return ( { })} data-test-subj="serverlessSearchConnectorsTitle" restrictWidth - rightSideItems={[ - - - - - - - - - 0 + ? [ + + + + + + createConnector()} > - {i18n.translate('xpack.serverlessSearch.connectorsPythonLink', { - defaultMessage: 'elastic/connectors', + {i18n.translate('xpack.serverlessSearch.connectors.createConnector', { + defaultMessage: 'Create connector', })} - - - - - - - createConnector()} - > - {i18n.translate('xpack.serverlessSearch.connectors.createConnector', { - defaultMessage: 'Create connector', - })} - - - , - ]} + + + , + ] + : undefined + } >

@@ -103,7 +107,6 @@ export const ConnectorsOverview = () => { learnMoreLink: ( @@ -118,7 +121,39 @@ export const ConnectorsOverview = () => { {connectorsLoading || (data?.connectors || []).length > 0 ? ( - + <> + {showCallOut && ( + <> + +

+ {i18n.translate( + 'xpack.serverlessSearch.connectorsOverview.calloutDescription', + { + defaultMessage: + "We're actively developing Elastic managed connectors, that won't require any self-managed infrastructure. You'll be able to handle all configuration in the UI. This will simplify syncing your data into a serverless Elasticsearch project.", + } + )} +

+ + navigateToUrl(`${BASE_CONNECTORS_PATH}/${ELASTIC_MANAGED_CONNECTOR_PATH}`) + } + > + {LEARN_MORE_LABEL} + + + + + )} + + ) : ( )} diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors_router.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors_router.tsx index f8c224ed2c9c6..4085b812d1f3d 100644 --- a/x-pack/plugins/serverless_search/public/application/components/connectors_router.tsx +++ b/x-pack/plugins/serverless_search/public/application/components/connectors_router.tsx @@ -9,10 +9,15 @@ import { Route, Routes } from '@kbn/shared-ux-router'; import React from 'react'; import { EditConnector } from './connectors/edit_connector'; import { ConnectorsOverview } from './connectors_overview'; +import { ConnectorsElasticManaged } from './connectors_elastic_managed'; +import { ELASTIC_MANAGED_CONNECTOR_PATH } from '../constants'; export const ConnectorsRouter: React.FC = () => { return ( + + + diff --git a/x-pack/plugins/serverless_search/public/application/components/web_crawlers/elastic_managed_web_crawler_coming_soon.tsx b/x-pack/plugins/serverless_search/public/application/components/web_crawlers/elastic_managed_web_crawler_coming_soon.tsx new file mode 100644 index 0000000000000..ba146ed847990 --- /dev/null +++ b/x-pack/plugins/serverless_search/public/application/components/web_crawlers/elastic_managed_web_crawler_coming_soon.tsx @@ -0,0 +1,171 @@ +/* + * 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 { + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiIcon, + EuiTitle, + EuiText, + EuiBadge, + EuiButtonEmpty, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { useKibanaServices } from '../../hooks/use_kibana'; +import { useAssetBasePath } from '../../hooks/use_asset_base_path'; + +import { BACK_LABEL } from '../../../../common/i18n_string'; +import { DecorativeHorizontalStepper } from '../common/decorative_horizontal_stepper'; + +export const ElasticManagedWebCrawlersCommingSoon: React.FC = () => { + const { + application: { navigateToUrl }, + } = useKibanaServices(); + + const assetBasePath = useAssetBasePath(); + const webCrawlerIcon = assetBasePath + '/web_crawlers.svg'; + + return ( + + + + + + navigateToUrl(`./`)} + > + {BACK_LABEL} + + + + + + + +

+ {i18n.translate('xpack.serverlessSearch.elasticManagedWebCrawlerEmpty.title', { + defaultMessage: 'Elastic managed web crawlers', + })} +

+
+
+ + Coming soon + + + +

+ {i18n.translate( + 'xpack.serverlessSearch.elasticManagedWebCrawlerEmpty.description', + { + defaultMessage: + "We're actively developing Elastic managed web crawlers, that won't require any self-managed infrastructure. You'll be able to handle all configuration in the UI. This will simplify syncing your data into a serverless Elasticsearch project. This new workflow will have two steps:", + } + )} +

+
+
+ + + + + + + + + + + + + + + + + + +

+ {i18n.translate( + 'xpack.serverlessSearch.elasticManagedWebCrawlerEmpty.guideOneDescription', + { + defaultMessage: 'Set one or more domain URLs you want to crawl', + } + )} +

+
+
+
+
+ + + + + + + + + + + + + + +

+ {i18n.translate( + 'xpack.serverlessSearch.elasticManagedWebCrawlerEmpty.guideThreeDescription', + { + defaultMessage: + 'Configure all the web crawler process using Kibana', + } + )} +

+
+
+
+
+
+
+
+
+
+
+
+
+ ); +}; diff --git a/x-pack/plugins/serverless_search/public/application/components/web_crawlers/empty_web_crawlers_prompt.tsx b/x-pack/plugins/serverless_search/public/application/components/web_crawlers/empty_web_crawlers_prompt.tsx new file mode 100644 index 0000000000000..8170ed6da3134 --- /dev/null +++ b/x-pack/plugins/serverless_search/public/application/components/web_crawlers/empty_web_crawlers_prompt.tsx @@ -0,0 +1,269 @@ +/* + * 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 { + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiIcon, + EuiTitle, + EuiText, + EuiLink, + EuiButton, + EuiBadge, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { useKibanaServices } from '../../hooks/use_kibana'; +import { useAssetBasePath } from '../../hooks/use_asset_base_path'; + +import { ELASTIC_MANAGED_WEB_CRAWLERS_PATH, BASE_WEB_CRAWLERS_PATH } from '../../constants'; +import { DecorativeHorizontalStepper } from '../common/decorative_horizontal_stepper'; + +export const EmptyWebCrawlersPrompt: React.FC = () => { + const { + application: { navigateToUrl }, + } = useKibanaServices(); + + const assetBasePath = useAssetBasePath(); + const webCrawlersIcon = assetBasePath + '/web_crawlers.svg'; + const githubIcon = assetBasePath + '/github_white.svg'; + + return ( + + + + + + + + + +

+ {i18n.translate('xpack.serverlessSearch.webCrawlersEmpty.title', { + defaultMessage: 'Set up a web crawler', + })} +

+
+
+ + +

+ {i18n.translate('xpack.serverlessSearch.webCrawlersEmpty.description', { + defaultMessage: + "To set up and deploy a web crawler you'll be working between data source, your terminal, and the Kibana UI. The high level process looks like this:", + })} +

+
+
+ + + + + + + + + + + + + + + + + + + + + + +

+ + {i18n.translate( + 'xpack.serverlessSearch.webCrawlersEmpty.sourceLabel', + { defaultMessage: 'source' } + )} + + ), + docker: ( + + {i18n.translate( + 'xpack.serverlessSearch.webCrawlersEmpty.dockerLabel', + { defaultMessage: 'Docker' } + )} + + ), + }} + /> +

+
+
+
+
+ + + + + + + + + + + +

+ {i18n.translate( + 'xpack.serverlessSearch.webCrawlersEmpty.guideOneDescription', + { + defaultMessage: 'Set one or more domain URLs you want to crawl', + } + )} +

+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +

+ {i18n.translate( + 'xpack.serverlessSearch.webCrawlersEmpty.guideThreeDescription', + { + defaultMessage: + 'Configure your web crawler and connect it to Elasticsearch', + } + )} +

+
+
+
+
+
+
+
+
+ + + + {i18n.translate('xpack.serverlessSearch.webCrawlersEmpty.selfManagedButton', { + defaultMessage: 'Self-managed web crawler', + })} + + + + + + + navigateToUrl( + `${BASE_WEB_CRAWLERS_PATH}/${ELASTIC_MANAGED_WEB_CRAWLERS_PATH}` + ) + } + > + {i18n.translate( + 'xpack.serverlessSearch.webCrawlersEmpty.elasticManagedButton', + { + defaultMessage: 'Elastic managed web crawler', + } + )} + + + + Coming soon + + + + +
+
+
+
+ ); +}; diff --git a/x-pack/plugins/serverless_search/public/application/components/web_crawlers_elastic_managed.tsx b/x-pack/plugins/serverless_search/public/application/components/web_crawlers_elastic_managed.tsx new file mode 100644 index 0000000000000..8ac5a0c59dd14 --- /dev/null +++ b/x-pack/plugins/serverless_search/public/application/components/web_crawlers_elastic_managed.tsx @@ -0,0 +1,62 @@ +/* + * 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 { EuiLink, EuiPageTemplate, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React, { useMemo } from 'react'; + +import { LEARN_MORE_LABEL } from '../../../common/i18n_string'; + +import { useKibanaServices } from '../hooks/use_kibana'; +import { ElasticManagedWebCrawlersCommingSoon } from './web_crawlers/elastic_managed_web_crawler_coming_soon'; + +export const WebCrawlersElasticManaged = () => { + const { console: consolePlugin } = useKibanaServices(); + + const embeddableConsole = useMemo( + () => (consolePlugin?.EmbeddableConsole ? : null), + [consolePlugin] + ); + + return ( + + + +

+ + {LEARN_MORE_LABEL} + + ), + }} + /> +

+
+
+ + + + {embeddableConsole} +
+ ); +}; diff --git a/x-pack/plugins/serverless_search/public/application/components/web_crawlers_overview.tsx b/x-pack/plugins/serverless_search/public/application/components/web_crawlers_overview.tsx new file mode 100644 index 0000000000000..b7e3763539536 --- /dev/null +++ b/x-pack/plugins/serverless_search/public/application/components/web_crawlers_overview.tsx @@ -0,0 +1,62 @@ +/* + * 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 { EuiLink, EuiPageTemplate, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React, { useMemo } from 'react'; + +import { LEARN_MORE_LABEL } from '../../../common/i18n_string'; + +import { useKibanaServices } from '../hooks/use_kibana'; +import { EmptyWebCrawlersPrompt } from './web_crawlers/empty_web_crawlers_prompt'; + +export const WebCrawlersOverview = () => { + const { console: consolePlugin } = useKibanaServices(); + + const embeddableConsole = useMemo( + () => (consolePlugin?.EmbeddableConsole ? : null), + [consolePlugin] + ); + + return ( + + + +

+ + {LEARN_MORE_LABEL} + + ), + }} + /> +

+
+
+ + + + {embeddableConsole} +
+ ); +}; diff --git a/x-pack/plugins/serverless_search/public/application/components/web_crawlers_router.tsx b/x-pack/plugins/serverless_search/public/application/components/web_crawlers_router.tsx new file mode 100644 index 0000000000000..7e4ae7a5d2657 --- /dev/null +++ b/x-pack/plugins/serverless_search/public/application/components/web_crawlers_router.tsx @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Route, Routes } from '@kbn/shared-ux-router'; +import React from 'react'; +import { WebCrawlersOverview } from './web_crawlers_overview'; +import { WebCrawlersElasticManaged } from './web_crawlers_elastic_managed'; + +export const WebCrawlersRouter: React.FC = () => { + return ( + + + + + + + + + ); +}; diff --git a/x-pack/plugins/serverless_search/public/application/constants.ts b/x-pack/plugins/serverless_search/public/application/constants.ts index 8e8c15638a860..e5dce2a328d05 100644 --- a/x-pack/plugins/serverless_search/public/application/constants.ts +++ b/x-pack/plugins/serverless_search/public/application/constants.ts @@ -12,5 +12,8 @@ export const INDEX_NAME_PLACEHOLDER = 'index_name'; // Paths export const BASE_CONNECTORS_PATH = 'connectors'; +export const BASE_WEB_CRAWLERS_PATH = 'web_crawlers'; export const EDIT_CONNECTOR_PATH = `${BASE_CONNECTORS_PATH}/:id`; +export const ELASTIC_MANAGED_CONNECTOR_PATH = '/elastic_managed'; +export const ELASTIC_MANAGED_WEB_CRAWLERS_PATH = '/elastic_managed'; export const FILE_UPLOAD_PATH = '/app/ml/filedatavisualizer'; diff --git a/x-pack/plugins/serverless_search/public/application/web_crawlers.tsx b/x-pack/plugins/serverless_search/public/application/web_crawlers.tsx new file mode 100644 index 0000000000000..e9a590c6dee57 --- /dev/null +++ b/x-pack/plugins/serverless_search/public/application/web_crawlers.tsx @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { CoreStart } from '@kbn/core-lifecycle-browser'; + +import { I18nProvider } from '@kbn/i18n-react'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; + +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; + +import ReactDOM from 'react-dom'; +import React from 'react'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import { Router } from '@kbn/shared-ux-router'; +import { ServerlessSearchContext } from './hooks/use_kibana'; + +export async function renderApp( + element: HTMLElement, + core: CoreStart, + services: ServerlessSearchContext, + queryClient: QueryClient +) { + const { WebCrawlersRouter } = await import('./components/web_crawlers_router'); + + ReactDOM.render( + + + + + + + + + + + + , + element + ); + return () => ReactDOM.unmountComponentAtNode(element); +} diff --git a/x-pack/plugins/serverless_search/public/assets/connectors.svg b/x-pack/plugins/serverless_search/public/assets/connectors.svg index 659e9e5494352..53624f84a8a00 100644 --- a/x-pack/plugins/serverless_search/public/assets/connectors.svg +++ b/x-pack/plugins/serverless_search/public/assets/connectors.svg @@ -1 +1,11 @@ - \ No newline at end of file + + + + + + + + + + + diff --git a/x-pack/plugins/serverless_search/public/assets/web_crawlers.svg b/x-pack/plugins/serverless_search/public/assets/web_crawlers.svg new file mode 100644 index 0000000000000..d6e2464c0f003 --- /dev/null +++ b/x-pack/plugins/serverless_search/public/assets/web_crawlers.svg @@ -0,0 +1,4 @@ + + + + diff --git a/x-pack/plugins/serverless_search/public/navigation_tree.ts b/x-pack/plugins/serverless_search/public/navigation_tree.ts index ae8f41b8b17f8..524888d0d33e6 100644 --- a/x-pack/plugins/serverless_search/public/navigation_tree.ts +++ b/x-pack/plugins/serverless_search/public/navigation_tree.ts @@ -8,7 +8,7 @@ import type { AppDeepLinkId, NavigationTreeDefinition } from '@kbn/core-chrome-browser'; import type { ApplicationStart } from '@kbn/core-application-browser'; import { i18n } from '@kbn/i18n'; -import { CONNECTORS_LABEL } from '../common/i18n_string'; +import { CONNECTORS_LABEL, WEB_CRAWLERS_LABEL } from '../common/i18n_string'; export const navigationTree = ({ isAppRegistered }: ApplicationStart): NavigationTreeDefinition => { function isAvailable(appId: string, content: T): T[] { @@ -54,6 +54,10 @@ export const navigationTree = ({ isAppRegistered }: ApplicationStart): Navigatio title: CONNECTORS_LABEL, link: 'serverlessConnectors', }, + { + title: WEB_CRAWLERS_LABEL, + link: 'serverlessWebCrawlers', + }, ], }, { diff --git a/x-pack/plugins/serverless_search/public/plugin.ts b/x-pack/plugins/serverless_search/public/plugin.ts index d097cd1eb3ad4..3c24211d2a520 100644 --- a/x-pack/plugins/serverless_search/public/plugin.ts +++ b/x-pack/plugins/serverless_search/public/plugin.ts @@ -120,6 +120,27 @@ export class ServerlessSearchPlugin }, }); + const webCrawlersTitle = i18n.translate('xpack.serverlessSearch.app.webCrawlers.title', { + defaultMessage: 'Web Crawlers', + }); + + core.application.register({ + id: 'serverlessWebCrawlers', + title: webCrawlersTitle, + appRoute: '/app/web_crawlers', + euiIconType: 'logoElastic', + category: DEFAULT_APP_CATEGORIES.enterpriseSearch, + visibleIn: [], + async mount({ element, history }: AppMountParameters) { + const { renderApp } = await import('./application/web_crawlers'); + const [coreStart, services] = await core.getStartServices(); + coreStart.chrome.docTitle.change(webCrawlersTitle); + docLinks.setDocLinks(coreStart.docLinks.links); + + return await renderApp(element, coreStart, { history, ...services }, queryClient); + }, + }); + const { searchIndices } = setupDeps; core.application.register({ id: 'serverlessHomeRedirect', diff --git a/x-pack/plugins/serverless_search/tsconfig.json b/x-pack/plugins/serverless_search/tsconfig.json index 794f146299a0f..854a90fdb5fb5 100644 --- a/x-pack/plugins/serverless_search/tsconfig.json +++ b/x-pack/plugins/serverless_search/tsconfig.json @@ -54,6 +54,7 @@ "@kbn/core-http-server", "@kbn/logging", "@kbn/security-plugin-types-public", + "@kbn/deeplinks-search", "@kbn/core-application-browser", ] } diff --git a/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/steps/update.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/steps/update.tsx index e7a37d415f4af..dba4f13ec9c86 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/steps/update.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/steps/update.tsx @@ -5,13 +5,22 @@ * 2.0. */ -import React, { FunctionComponent } from 'react'; +import React, { FunctionComponent, useState, useMemo } from 'react'; import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers'; -import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui'; -import { FIELD_TYPES, UseField } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText, EuiSwitch } from '@elastic/eui'; +import { + FIELD_TYPES, + UseField, + useFormContext, +} from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { Field } from '@kbn/es-ui-shared-plugin/static/forms/components'; import { JsonFieldWrapper, MustacheTextFieldWrapper } from '@kbn/triggers-actions-ui-plugin/public'; -import { containsCommentsOrEmpty, containsTitleAndDesc, isUrlButCanBeEmpty } from '../validator'; +import { + containsCommentsOrEmpty, + containsTitleAndDesc, + isUrlButCanBeEmpty, + validateCreateComment, +} from '../validator'; import { casesVars, commentVars, urlVars } from '../action_variables'; import { HTTP_VERBS } from '../webhook_connectors'; import { styles } from './update.styles'; @@ -23,185 +32,238 @@ interface Props { readOnly: boolean; } -export const UpdateStep: FunctionComponent = ({ display, readOnly }) => ( - - -

{i18n.STEP_4A}

- -

{i18n.STEP_4A_DESCRIPTION}

-
-
- - - - ({ text: verb.toUpperCase(), value: verb })), - readOnly, - }, - }} - /> - - - - - - - - - - - - -

{i18n.STEP_4B}

- -

{i18n.STEP_4B_DESCRIPTION}

-
-
- - - - ({ text: verb.toUpperCase(), value: verb })), - readOnly, - }, - }} - /> - - - - - - - - = ({ display, readOnly }) => { + const { getFieldDefaultValue } = useFormContext(); + + const hasCommentDefaultValue = + !!getFieldDefaultValue('config.createCommentUrl') || + !!getFieldDefaultValue('config.createCommentJson'); + + const [isAddCommentToggled, setIsAddCommentToggled] = useState(Boolean(hasCommentDefaultValue)); + + const onAddCommentToggle = () => { + setIsAddCommentToggled((prev) => !prev); + }; + + const updateIncidentMethodConfig = useMemo( + () => ({ + label: i18n.UPDATE_INCIDENT_METHOD, + defaultValue: 'put', + type: FIELD_TYPES.SELECT, + validations: [{ validator: emptyField(i18n.UPDATE_METHOD_REQUIRED) }], + }), + [] + ); + + const updateIncidentUrlConfig = useMemo( + () => ({ + label: i18n.UPDATE_INCIDENT_URL, + validations: [{ validator: urlField(i18n.UPDATE_URL_REQUIRED) }], + helpText: i18n.UPDATE_INCIDENT_URL_HELP, + }), + [] + ); + + const updateIncidentJsonConfig = useMemo( + () => ({ + label: i18n.UPDATE_INCIDENT_JSON, + helpText: i18n.UPDATE_INCIDENT_JSON_HELP, + validations: [ + { validator: emptyField(i18n.UPDATE_INCIDENT_REQUIRED) }, + { validator: containsTitleAndDesc() }, + ], + }), + [] + ); + + const createCommentMethodConfig = useMemo( + () => ({ + label: i18n.CREATE_COMMENT_METHOD, + defaultValue: 'put', + type: FIELD_TYPES.SELECT, + validations: [{ validator: emptyField(i18n.CREATE_COMMENT_METHOD_REQUIRED) }], + }), + [] + ); + + const createCommentUrlConfig = useMemo( + () => ({ + label: i18n.CREATE_COMMENT_URL, + fieldsToValidateOnChange: ['config.createCommentUrl', 'config.createCommentJson'], + validations: [ + { validator: isUrlButCanBeEmpty(i18n.CREATE_COMMENT_URL_FORMAT_REQUIRED) }, + { + validator: validateCreateComment( + i18n.CREATE_COMMENT_URL_MISSING, + 'config.createCommentJson' + ), + }, + ], + helpText: i18n.CREATE_COMMENT_URL_HELP, + }), + [] + ); + + const createCommentJsonConfig = useMemo( + () => ({ + label: i18n.CREATE_COMMENT_JSON, + helpText: i18n.CREATE_COMMENT_JSON_HELP, + fieldsToValidateOnChange: ['config.createCommentJson', 'config.createCommentUrl'], + validations: [ + { validator: containsCommentsOrEmpty(i18n.CREATE_COMMENT_FORMAT_MESSAGE) }, + { + validator: validateCreateComment( + i18n.CREATE_COMMENT_JSON_MISSING, + 'config.createCommentUrl' + ), + }, + ], + }), + [] + ); + + return ( + <> + + +

{i18n.STEP_4A}

+ +

{i18n.STEP_4A_DESCRIPTION}

+
+
+ + + + ({ text: verb.toUpperCase(), value: verb })), + readOnly, + }, + }} + /> + + + + + + + + + + + + -
-
-
-); + {isAddCommentToggled && ( + <> + + + +

{i18n.STEP_4B_DESCRIPTION}

+
+
+ + + ({ + text: verb.toUpperCase(), + value: verb, + })), + readOnly, + }, + }} + /> + + + + + + + + + + + + )} + + + ); +}; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/translations.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/translations.ts index 8c44b6197ef9c..5653fe4adc851 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/translations.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/translations.ts @@ -54,13 +54,28 @@ export const UPDATE_METHOD_REQUIRED = i18n.translate( } ); -export const CREATE_COMMENT_URL_REQUIRED = i18n.translate( +export const CREATE_COMMENT_URL_FORMAT_REQUIRED = i18n.translate( 'xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentUrlText', { defaultMessage: 'Create comment URL must be URL format.', } ); -export const CREATE_COMMENT_MESSAGE = i18n.translate( + +export const CREATE_COMMENT_URL_MISSING = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentUrlMissing', + { + defaultMessage: 'Create comment URL is required.', + } +); + +export const CREATE_COMMENT_JSON_MISSING = i18n.translate( + 'xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentJsonMissing', + { + defaultMessage: 'Create comment Json is required.', + } +); + +export const CREATE_COMMENT_FORMAT_MESSAGE = i18n.translate( 'xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentIncidentText', { defaultMessage: 'Create comment object must be valid JSON.', @@ -373,7 +388,7 @@ export const STEP_4A_DESCRIPTION = i18n.translate( ); export const STEP_4B = i18n.translate('xpack.stackConnectors.components.casesWebhook.step4b', { - defaultMessage: 'Add comment in case (optional)', + defaultMessage: 'Add comment in case', }); export const STEP_4B_DESCRIPTION = i18n.translate( diff --git a/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/validator.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/validator.ts index d972c9bbd1f86..8c64042801635 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/validator.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/validator.ts @@ -100,6 +100,11 @@ export const containsCommentsOrEmpty = (message: string) => (...args: Parameters): ReturnType> => { const [{ value, path }] = args; + + if (value === null || value === undefined || value === '') { + return undefined; + } + if (typeof value !== 'string') { return { code: 'ERR_FIELD_FORMAT', @@ -107,9 +112,6 @@ export const containsCommentsOrEmpty = message, }; } - if (value.length === 0) { - return undefined; - } const comment = templateActionVariable( commentVars.find((actionVariable) => actionVariable.name === 'case.comment')! @@ -128,16 +130,30 @@ export const isUrlButCanBeEmpty = (message: string) => (...args: Parameters) => { const [{ value }] = args; + const error: ValidationError = { code: 'ERR_FIELD_FORMAT', formatType: 'URL', message, }; - if (typeof value !== 'string') { - return error; - } - if (value.length === 0) { + + if (value === null || value === undefined || value === '') { return undefined; } - return isUrl(value) ? undefined : error; + return typeof value === 'string' && isUrl(value) ? undefined : error; + }; + +export const validateCreateComment = + (message: string, fieldName: string) => + (...args: Parameters) => { + const [{ value, formData }] = args; + const otherFielValue = formData[fieldName]; + + const error: ValidationError = { + code: 'ERR_FIELD_FORMAT', + formatType: 'STRING', + message, + }; + + return !value && otherFielValue ? error : undefined; }; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/webhook_connectors.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/webhook_connectors.test.tsx index 713f2bd9e6f83..911875f31eb26 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/webhook_connectors.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/webhook_connectors.test.tsx @@ -97,6 +97,49 @@ describe('CasesWebhookActionConnectorFields renders', () => { expect(await screen.findByTestId('webhookCreateCommentJson')).toBeInTheDocument(); }); + it('Add comment to case section is rendered only when the toggle button is on', async () => { + const incompleteActionConnector = { + ...actionConnector, + config: { + ...actionConnector.config, + createCommentUrl: undefined, + createCommentJson: undefined, + }, + }; + render( + + {}} + /> + + ); + + await userEvent.click(await screen.findByTestId('webhookAddCommentToggle')); + + expect(await screen.findByTestId('webhookCreateCommentMethodSelect')).toBeInTheDocument(); + expect(await screen.findByTestId('createCommentUrlInput')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookCreateCommentJson')).toBeInTheDocument(); + }); + + it('Toggle button is active when create comment section fields are populated', async () => { + render( + + {}} + /> + + ); + + expect(await screen.findByTestId('webhookAddCommentToggle')).toHaveAttribute( + 'aria-checked', + 'true' + ); + }); + it('connector auth toggles work as expected', async () => { render( diff --git a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/service.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/service.test.ts index aa8d248566d9a..5f2f5ee019a5f 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/service.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/service.test.ts @@ -1232,6 +1232,23 @@ describe('ServiceNow service', () => { `); }); + test('it should return null if no incident found, when incident to be closed is null', async () => { + requestMock.mockImplementationOnce(() => ({ + data: { + result: [], + }, + })); + + const res = await service.closeIncident({ incidentId: '2', correlationId: null }); + expect(logger.warn.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "[ServiceNow][CloseIncident] No incident found with correlation_id: null or incidentId: 2.", + ] + `); + + expect(res).toBeNull(); + }); + test('it should return null if found incident with correlation id is null', async () => { requestMock.mockImplementationOnce(() => ({ data: { diff --git a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/service.ts b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/service.ts index 84a8592aaa832..4cfe1ad56cfa7 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/service.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/service.ts @@ -8,6 +8,7 @@ import { AxiosResponse } from 'axios'; import { request } from '@kbn/actions-plugin/server/lib/axios_utils'; +import { isEmpty } from 'lodash'; import { ExternalService, ExternalServiceParamsCreate, @@ -306,7 +307,7 @@ export const createExternalService: ServiceFactory = ({ incidentToBeClosed = await getIncidentByCorrelationId(correlationId); } - if (incidentToBeClosed === null) { + if (incidentToBeClosed === null || isEmpty(incidentToBeClosed)) { logger.warn( `[ServiceNow][CloseIncident] No incident found with correlation_id: ${correlationId} or incidentId: ${incidentId}.` ); diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index e977ae228fdfc..cb51a020eeaac 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -6689,12 +6689,6 @@ "reporting.share.screenCapturePanelContent.optimizeForPrintingLabel": "Optimiser pour l'impression", "reporting.shareContextMenu.ExportsButtonLabel": "PDF", "reporting.shareContextMenu.ExportsButtonLabelPNG": "Export PNG", - "savedObjects.confirmModal.cancelButtonLabel": "Annuler", - "savedObjects.confirmModal.overwriteButtonLabel": "Écraser", - "savedObjects.confirmModal.overwriteConfirmationMessage": "Êtes-vous sûr de vouloir écraser {title} ?", - "savedObjects.confirmModal.overwriteTitle": "Écraser {name} ?", - "savedObjects.confirmModal.saveDuplicateButtonLabel": "Enregistrer {name}", - "savedObjects.confirmModal.saveDuplicateConfirmationMessage": "Il y a déjà une occurrence de {name} avec le titre \"{title}\". Voulez-vous tout de même enregistrer ?", "savedObjects.overwriteRejectedDescription": "La confirmation d'écrasement a été rejetée.", "savedObjects.saveDuplicateRejectedDescription": "La confirmation d'enregistrement avec un doublon de titre a été rejetée.", "savedObjects.saveModal.cancelButtonLabel": "Annuler", @@ -26193,7 +26187,6 @@ "xpack.inventory.badgeFilterWithPopover.openPopoverBadgeLabel": "Ouvrir la fenêtre contextuelle", "xpack.inventory.data_view.creation_failed": "Une erreur s'est produite lors de la création de la vue de données", "xpack.inventory.eemEnablement.errorTitle": "Erreur lors de l'activation du nouveau modèle d'entité", - "xpack.inventory.entityActions.discoverLink": "Ouvrir dans Discover", "xpack.inventory.entitiesGrid.euiDataGrid.alertsLabel": "Alertes", "xpack.inventory.entitiesGrid.euiDataGrid.alertsTooltip": "Le nombre d'alertes actives", "xpack.inventory.entitiesGrid.euiDataGrid.entityNameLabel": "Nom de l'entité", @@ -26206,6 +26199,7 @@ "xpack.inventory.entitiesGrid.euiDataGrid.lastSeenTooltip": "Horodatage des dernières données reçues pour l'entité (entity.lastSeenTimestamp)", "xpack.inventory.entitiesGrid.euiDataGrid.typeLabel": "Type", "xpack.inventory.entitiesGrid.euiDataGrid.typeTooltip": "Type d'entité (entity.type)", + "xpack.inventory.entityActions.discoverLink": "Ouvrir dans Discover", "xpack.inventory.featureRegistry.inventoryFeatureName": "Inventory", "xpack.inventory.home.serviceAlertsTable.tooltip.activeAlertsExplanation": "Alertes actives", "xpack.inventory.inventoryLinkTitle": "Inventory", @@ -43121,8 +43115,6 @@ "xpack.serverlessSearch.connectors.typeLabel": "Type", "xpack.serverlessSearch.connectors.variablesTitle": "Variable pour votre {url}", "xpack.serverlessSearch.connectors.waitingForConnection": "En attente de connexion", - "xpack.serverlessSearch.connectorsEmpty.availableConnectors": "Connecteurs disponibles", - "xpack.serverlessSearch.connectorsEmpty.createConnector": "Créer un connecteur", "xpack.serverlessSearch.connectorsEmpty.description": "La configuration et le déploiement d'un connecteur se passe entre la source de données tierce, votre terminal et l'UI sans serveur d'Elasticsearch. Le processus à haut niveau ressemble à ça :", "xpack.serverlessSearch.connectorsEmpty.dockerLabel": "Docker", "xpack.serverlessSearch.connectorsEmpty.guideOneDescription": "Choisissez une source de données à synchroniser", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 05416471642cb..b5a0c4a1481df 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -6682,12 +6682,6 @@ "reporting.share.screenCapturePanelContent.optimizeForPrintingLabel": "印刷用に最適化", "reporting.shareContextMenu.ExportsButtonLabel": "PDF", "reporting.shareContextMenu.ExportsButtonLabelPNG": "PNGエクスポート", - "savedObjects.confirmModal.cancelButtonLabel": "キャンセル", - "savedObjects.confirmModal.overwriteButtonLabel": "上書き", - "savedObjects.confirmModal.overwriteConfirmationMessage": "{title}を上書きしてよろしいですか?", - "savedObjects.confirmModal.overwriteTitle": "{name} を上書きしますか?", - "savedObjects.confirmModal.saveDuplicateButtonLabel": "{name} を保存", - "savedObjects.confirmModal.saveDuplicateConfirmationMessage": "''{title}''というタイトルの {name} がすでに存在します。保存しますか?", "savedObjects.overwriteRejectedDescription": "上書き確認が拒否されました", "savedObjects.saveDuplicateRejectedDescription": "重複ファイルの保存確認が拒否されました", "savedObjects.saveModal.cancelButtonLabel": "キャンセル", @@ -26165,7 +26159,6 @@ "xpack.inventory.badgeFilterWithPopover.openPopoverBadgeLabel": "ポップオーバーを開く", "xpack.inventory.data_view.creation_failed": "データビューの作成中にエラーが発生しました", "xpack.inventory.eemEnablement.errorTitle": "新しいエンティティモデルの有効化エラー", - "xpack.inventory.entityActions.discoverLink": "Discoverで開く", "xpack.inventory.entitiesGrid.euiDataGrid.alertsLabel": "アラート", "xpack.inventory.entitiesGrid.euiDataGrid.alertsTooltip": "アクティブなアラートの件数", "xpack.inventory.entitiesGrid.euiDataGrid.entityNameLabel": "エンティティ名", @@ -26178,6 +26171,7 @@ "xpack.inventory.entitiesGrid.euiDataGrid.lastSeenTooltip": "エンティティで最後に受信したデータのタイムスタンプ(entity.lastSeenTimestamp)", "xpack.inventory.entitiesGrid.euiDataGrid.typeLabel": "型", "xpack.inventory.entitiesGrid.euiDataGrid.typeTooltip": "エンティティのタイプ(entity.type)", + "xpack.inventory.entityActions.discoverLink": "Discoverで開く", "xpack.inventory.featureRegistry.inventoryFeatureName": "インベントリ", "xpack.inventory.home.serviceAlertsTable.tooltip.activeAlertsExplanation": "アクティブアラート", "xpack.inventory.inventoryLinkTitle": "インベントリ", @@ -43087,8 +43081,6 @@ "xpack.serverlessSearch.connectors.typeLabel": "型", "xpack.serverlessSearch.connectors.variablesTitle": "{url}の変数", "xpack.serverlessSearch.connectors.waitingForConnection": "接続を待機中", - "xpack.serverlessSearch.connectorsEmpty.availableConnectors": "使用可能なコネクター", - "xpack.serverlessSearch.connectorsEmpty.createConnector": "コネクターを作成", "xpack.serverlessSearch.connectorsEmpty.description": "コネクターを設定およびデプロイするには、サードパーティのデータソース、端末、ElasticsearchサーバーレスUI の間で作業することになります。プロセスの概要は次のとおりです。", "xpack.serverlessSearch.connectorsEmpty.dockerLabel": "Docker", "xpack.serverlessSearch.connectorsEmpty.guideOneDescription": "同期したいデータソースを選択します。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 0e0b68b1a3ef9..8c21f00ca8228 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -6601,8 +6601,6 @@ "reporting.share.screenCapturePanelContent.optimizeForPrintingLabel": "打印优化", "reporting.shareContextMenu.ExportsButtonLabel": "PDF", "reporting.shareContextMenu.ExportsButtonLabelPNG": "PNG 导出", - "savedObjects.confirmModal.cancelButtonLabel": "取消", - "savedObjects.confirmModal.overwriteButtonLabel": "覆盖", "savedObjects.overwriteRejectedDescription": "已拒绝覆盖确认", "savedObjects.saveDuplicateRejectedDescription": "已拒绝使用重复标题保存确认", "savedObjects.saveModal.cancelButtonLabel": "取消", @@ -25691,7 +25689,6 @@ "xpack.inventory.badgeFilterWithPopover.openPopoverBadgeLabel": "打开弹出框", "xpack.inventory.data_view.creation_failed": "创建数据视图时出错", "xpack.inventory.eemEnablement.errorTitle": "启用新实体模型时出错", - "xpack.inventory.entityActions.discoverLink": "在 Discover 中打开", "xpack.inventory.entitiesGrid.euiDataGrid.alertsLabel": "告警", "xpack.inventory.entitiesGrid.euiDataGrid.alertsTooltip": "活动告警计数", "xpack.inventory.entitiesGrid.euiDataGrid.entityNameLabel": "实体名称", @@ -25704,6 +25701,7 @@ "xpack.inventory.entitiesGrid.euiDataGrid.lastSeenTooltip": "上次接收的实体数据的时间戳 (entity.lastSeenTimestamp)", "xpack.inventory.entitiesGrid.euiDataGrid.typeLabel": "类型", "xpack.inventory.entitiesGrid.euiDataGrid.typeTooltip": "实体的类型 (entity.type)", + "xpack.inventory.entityActions.discoverLink": "在 Discover 中打开", "xpack.inventory.featureRegistry.inventoryFeatureName": "库存", "xpack.inventory.home.serviceAlertsTable.tooltip.activeAlertsExplanation": "活动告警", "xpack.inventory.inventoryLinkTitle": "库存", @@ -42431,8 +42429,6 @@ "xpack.serverlessSearch.connectors.typeLabel": "类型", "xpack.serverlessSearch.connectors.variablesTitle": "您的 {url} 的变量", "xpack.serverlessSearch.connectors.waitingForConnection": "等待连接", - "xpack.serverlessSearch.connectorsEmpty.availableConnectors": "可用连接器", - "xpack.serverlessSearch.connectorsEmpty.createConnector": "创建连接器", "xpack.serverlessSearch.connectorsEmpty.description": "要设置并部署连接器,您需要在第三方数据源、终端与 Elasticsearch 无服务器 UI 之间开展工作。高级流程类似于这样:", "xpack.serverlessSearch.connectorsEmpty.dockerLabel": "Docker", "xpack.serverlessSearch.connectorsEmpty.guideOneDescription": "选择要同步的数据源", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/index.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/index.test.tsx index 1ff0d9f679a05..6ad74732844c0 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/index.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/index.test.tsx @@ -143,7 +143,7 @@ describe('EditConnectorFlyout', () => { }); await waitFor(() => { - expect(queryByText('This connector is readonly.')).not.toBeInTheDocument(); + expect(queryByText('This connector is read-only.')).not.toBeInTheDocument(); expect(getByTestId('nameInput')).toHaveValue('My test'); expect(getByTestId('test-connector-text-field')).toHaveValue('My text field'); }); @@ -176,7 +176,7 @@ describe('EditConnectorFlyout', () => { /> ); - expect(getByText('This connector is readonly.')).toBeInTheDocument(); + expect(getByText('This connector is read-only.')).toBeInTheDocument(); }); it('shows the buttons', async () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/read_only.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/read_only.test.tsx index baa8eed5265d5..194a3bf1f1524 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/read_only.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/read_only.test.tsx @@ -25,7 +25,7 @@ describe('ReadOnlyConnectorMessage', () => { { wrapper: I18nProvider } ); - expect(getByText('This connector is readonly.')).toBeInTheDocument(); + expect(getByText('This connector is read-only.')).toBeInTheDocument(); expect(getByTestId('read-only-link')).toHaveProperty('href', 'https://example.com/'); expect(queryByText('Extra Component')).toBeNull(); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/read_only.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/read_only.tsx index f32bc2a34bd6b..354f832090869 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/read_only.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/edit_connector_flyout/read_only.tsx @@ -22,7 +22,7 @@ export const ReadOnlyConnectorMessage: React.FC<{ <> {i18n.translate('xpack.triggersActionsUI.sections.editConnectorForm.descriptionText', { - defaultMessage: 'This connector is readonly.', + defaultMessage: 'This connector is read-only.', })} diff --git a/x-pack/test/accessibility/apps/group1/users.ts b/x-pack/test/accessibility/apps/group1/users.ts index e26e6a6f6a54f..138f0995cbaae 100644 --- a/x-pack/test/accessibility/apps/group1/users.ts +++ b/x-pack/test/accessibility/apps/group1/users.ts @@ -62,7 +62,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { confirm_password: 'password', full_name: 'a11y user', email: 'example@example.com', - roles: ['apm_user'], + roles: ['editor'], }); await testSubjects.click('rolesDropdown'); await a11y.testAppSnapshot(); @@ -75,7 +75,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { confirm_password: 'password', full_name: 'DeleteA11y user', email: 'example@example.com', - roles: ['apm_user'], + roles: ['editor'], }); await testSubjects.click('checkboxSelectRow-deleteA11y'); await a11y.testAppSnapshot(); diff --git a/x-pack/test/api_integration/services/security_solution_api.gen.ts b/x-pack/test/api_integration/services/security_solution_api.gen.ts index 3cffbef413fa3..6ba76b071d860 100644 --- a/x-pack/test/api_integration/services/security_solution_api.gen.ts +++ b/x-pack/test/api_integration/services/security_solution_api.gen.ts @@ -106,6 +106,7 @@ import { InitEntityEngineRequestParamsInput, InitEntityEngineRequestBodyInput, } from '@kbn/security-solution-plugin/common/api/entity_analytics/entity_store/engine/init.gen'; +import { InitEntityStoreRequestBodyInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/entity_store/enablement.gen'; import { InstallPrepackedTimelinesRequestBodyInput } from '@kbn/security-solution-plugin/common/api/timeline/install_prepackaged_timelines/install_prepackaged_timelines_route.gen'; import { ListEntitiesRequestQueryInput } from '@kbn/security-solution-plugin/common/api/entity_analytics/entity_store/entities/list_entities.gen'; import { PatchRuleRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/crud/patch_rule/patch_rule_route.gen'; @@ -842,6 +843,13 @@ finalize it. .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); }, + getEntityStoreStatus(kibanaSpace: string = 'default') { + return supertest + .get(routeWithNamespace('/api/entity_store/status', kibanaSpace)) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); + }, /** * Get all notes for a given document. */ @@ -1030,6 +1038,14 @@ finalize it. .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send(props.body as object); }, + initEntityStore(props: InitEntityStoreProps, kibanaSpace: string = 'default') { + return supertest + .post(routeWithNamespace('/api/entity_store/enable', kibanaSpace)) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, /** * Initializes the Risk Engine by creating the necessary indices and mappings, removing old transforms, and starting the new risk engine */ @@ -1633,6 +1649,9 @@ export interface InitEntityEngineProps { params: InitEntityEngineRequestParamsInput; body: InitEntityEngineRequestBodyInput; } +export interface InitEntityStoreProps { + body: InitEntityStoreRequestBodyInput; +} export interface InstallPrepackedTimelinesProps { body: InstallPrepackedTimelinesRequestBodyInput; } diff --git a/x-pack/test/functional/apps/index_lifecycle_management/read_only_view.ts b/x-pack/test/functional/apps/index_lifecycle_management/read_only_view.ts index 030074a97b4bd..b30ee9ecee763 100644 --- a/x-pack/test/functional/apps/index_lifecycle_management/read_only_view.ts +++ b/x-pack/test/functional/apps/index_lifecycle_management/read_only_view.ts @@ -15,6 +15,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const security = getService('security'); describe('Read only view', function () { + this.tags('skipFIPS'); before(async () => { await security.testUser.setRoles(['read_ilm']); diff --git a/x-pack/test/functional/apps/infra/home_page.ts b/x-pack/test/functional/apps/infra/home_page.ts index f36b3394e2a89..fc937afc3f3c9 100644 --- a/x-pack/test/functional/apps/infra/home_page.ts +++ b/x-pack/test/functional/apps/infra/home_page.ts @@ -426,8 +426,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(nodesWithValue).to.eql([ { name: 'host-5', value: 10, color: '#6092c0' }, { name: 'host-4', value: 30, color: '#9ab6d5' }, - { name: 'host-1', value: 50, color: '#f1d9b9' }, - { name: 'host-2', value: 70, color: '#eba47a' }, + { name: 'host-1', value: 50, color: '#f6e0b9' }, + { name: 'host-2', value: 70, color: '#eda77a' }, { name: 'host-3', value: 90, color: '#e7664c' }, ]); }); diff --git a/x-pack/test/functional/apps/lens/group4/chart_data.ts b/x-pack/test/functional/apps/lens/group4/chart_data.ts index 3b3a51c289473..fc922f8d2df17 100644 --- a/x-pack/test/functional/apps/lens/group4/chart_data.ts +++ b/x-pack/test/functional/apps/lens/group4/chart_data.ts @@ -117,7 +117,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { { key: '5,722.775 - 8,529.22', name: '5,722.775 - 8,529.22', color: '#6092c0' }, { key: '8,529.22 - 11,335.665', name: '8,529.22 - 11,335.665', color: '#a8bfda' }, { key: '11,335.665 - 14,142.11', name: '11,335.665 - 14,142.11', color: '#ebeff5' }, - { key: '14,142.11 - 16,948.555', name: '14,142.11 - 16,948.555', color: '#ecb385' }, + { key: '14,142.11 - 16,948.555', name: '14,142.11 - 16,948.555', color: '#efb785' }, { key: '≥ 16,948.555', name: '≥ 16,948.555', color: '#e7664c' }, ]); }); diff --git a/x-pack/test/functional/apps/lens/group5/heatmap.ts b/x-pack/test/functional/apps/lens/group5/heatmap.ts index 7abcba0cb0780..a61afa2d24d8a 100644 --- a/x-pack/test/functional/apps/lens/group5/heatmap.ts +++ b/x-pack/test/functional/apps/lens/group5/heatmap.ts @@ -58,7 +58,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { { key: '5,722.775 - 8,529.22', name: '5,722.775 - 8,529.22', color: '#6092c0' }, { key: '8,529.22 - 11,335.665', name: '8,529.22 - 11,335.665', color: '#a8bfda' }, { key: '11,335.665 - 14,142.11', name: '11,335.665 - 14,142.11', color: '#ebeff5' }, - { key: '14,142.11 - 16,948.555', name: '14,142.11 - 16,948.555', color: '#ecb385' }, + { key: '14,142.11 - 16,948.555', name: '14,142.11 - 16,948.555', color: '#efb785' }, { key: '≥ 16,948.555', name: '≥ 16,948.555', color: '#e7664c' }, ]); }); @@ -80,7 +80,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { { key: '7,125.997 - 8,529.22', name: '7,125.997 - 8,529.22', color: '#6092c0' }, { key: '8,529.22 - 11,335.665', name: '8,529.22 - 11,335.665', color: '#a8bfda' }, { key: '11,335.665 - 14,142.11', name: '11,335.665 - 14,142.11', color: '#ebeff5' }, - { key: '14,142.11 - 16,948.555', name: '14,142.11 - 16,948.555', color: '#ecb385' }, + { key: '14,142.11 - 16,948.555', name: '14,142.11 - 16,948.555', color: '#efb785' }, { key: '≥ 16,948.555', name: '≥ 16,948.555', color: '#e7664c' }, ]); }); @@ -94,7 +94,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { { key: '7,125.99 - 8,529.2', name: '7,125.99 - 8,529.2', color: '#6092c0' }, { key: '8,529.2 - 11,335.66', name: '8,529.2 - 11,335.66', color: '#a8bfda' }, { key: '11,335.66 - 14,142.1', name: '11,335.66 - 14,142.1', color: '#ebeff5' }, - { key: '14,142.1 - 16,948.55', name: '14,142.1 - 16,948.55', color: '#ecb385' }, + { key: '14,142.1 - 16,948.55', name: '14,142.1 - 16,948.55', color: '#efb785' }, { color: '#e7664c', key: '≥ 16,948.55', @@ -115,7 +115,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { { key: '0 - 8,529.2', name: '0 - 8,529.2', color: '#6092c0' }, { key: '8,529.2 - 11,335.66', name: '8,529.2 - 11,335.66', color: '#a8bfda' }, { key: '11,335.66 - 14,142.1', name: '11,335.66 - 14,142.1', color: '#ebeff5' }, - { key: '14,142.1 - 16,948.55', name: '14,142.1 - 16,948.55', color: '#ecb385' }, + { key: '14,142.1 - 16,948.55', name: '14,142.1 - 16,948.55', color: '#efb785' }, { key: '≥ 16,948.55', name: '≥ 16,948.55', color: '#e7664c' }, ]); }); @@ -133,7 +133,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { { key: '5,722.775 - 8,529.2', name: '5,722.775 - 8,529.2', color: '#6092c0' }, { key: '8,529.2 - 11,335.66', name: '8,529.2 - 11,335.66', color: '#a8bfda' }, { key: '11,335.66 - 14,142.1', name: '11,335.66 - 14,142.1', color: '#ebeff5' }, - { key: '14,142.1 - 16,948.55', name: '14,142.1 - 16,948.55', color: '#ecb385' }, + { key: '14,142.1 - 16,948.55', name: '14,142.1 - 16,948.55', color: '#efb785' }, { key: '≥ 16,948.55', name: '≥ 16,948.55', color: '#e7664c' }, ]); // assert the cell has the correct coloring despite the legend rounding diff --git a/x-pack/test/functional/apps/security/users.ts b/x-pack/test/functional/apps/security/users.ts index e9711dc29c46b..a8886045b70a4 100644 --- a/x-pack/test/functional/apps/security/users.ts +++ b/x-pack/test/functional/apps/security/users.ts @@ -111,9 +111,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(roles.apm_system.reserved).to.be(true); expect(roles.apm_system.deprecated).to.be(false); - expect(roles.apm_user.reserved).to.be(true); - expect(roles.apm_user.deprecated).to.be(true); - expect(roles.beats_admin.reserved).to.be(true); expect(roles.beats_admin.deprecated).to.be(false); diff --git a/x-pack/test/functional/page_objects/log_wrapper.ts b/x-pack/test/functional/page_objects/log_wrapper.ts index 97f5a7a89369f..afcead60b2905 100644 --- a/x-pack/test/functional/page_objects/log_wrapper.ts +++ b/x-pack/test/functional/page_objects/log_wrapper.ts @@ -7,6 +7,10 @@ import { ToolingLog } from '@kbn/tooling-log'; +function isPromise(value: unknown): value is Promise { + return value instanceof Promise; +} + /** * Wraps the specified object instance with debug log statements of all method calls. * @@ -19,17 +23,45 @@ export function logWrapper>( log: ToolingLog, instance: T ): T { + const logger = prepareLogger(log, prefix); return Object.keys(instance).reduce((acc, prop) => { const baseFn = acc[prop]; (acc as Record)[prop] = (...args: unknown[]) => { - logMethodCall(log, prefix, prop, args); - return baseFn.apply(instance, args); + logger.start(prop, args); + const result = baseFn.apply(instance, args); + if (isPromise(result)) { + result.then(logger.end, logger.end); + } else { + logger.end(); + } + return result; }; return acc; }, instance); } -function logMethodCall(log: ToolingLog, prefix: string, prop: string, args: unknown[]) { - const argsStr = args.map((arg) => (typeof arg === 'string' ? `'${arg}'` : arg)).join(', '); - log.debug(`${prefix}.${prop}(${argsStr})`); +function prepareLogger(log: ToolingLog, prefix: string) { + let now = Date.now(); + let currentContext = ''; + + return { + start: (prop: string, args: unknown[]) => { + if (prop === '') { + return; + } + currentContext = `${prop}(${args + .map((arg) => (typeof arg === 'string' ? `'${arg}'` : JSON.stringify(arg))) + .join(', ')})`; + log.debug(`${prefix}.${currentContext}`); + now = Date.now(); + }, + end: () => { + if (currentContext === '') { + return; + } + log.debug(`${prefix}.${currentContext} - (Took ${Date.now() - now} ms)`); + now = Date.now(); + currentContext = ''; + }, + }; } diff --git a/x-pack/test/observability_ai_assistant_functional/tests/conversations/index.spec.ts b/x-pack/test/observability_ai_assistant_functional/tests/conversations/index.spec.ts index de780d2f46b0e..6d509a77b42f7 100644 --- a/x-pack/test/observability_ai_assistant_functional/tests/conversations/index.spec.ts +++ b/x-pack/test/observability_ai_assistant_functional/tests/conversations/index.spec.ts @@ -9,6 +9,8 @@ import expect from '@kbn/expect'; import { MessageRole } from '@kbn/observability-ai-assistant-plugin/common'; import { ChatFeedback } from '@kbn/observability-ai-assistant-plugin/public/analytics/schemas/chat_feedback'; import { pick } from 'lodash'; +import { parse as parseCookie } from 'tough-cookie'; +import { kbnTestConfig } from '@kbn/test'; import { createLlmProxy, isFunctionTitleRequest, @@ -17,12 +19,15 @@ import { import { interceptRequest } from '../../common/intercept_request'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { editor } from '../../../observability_ai_assistant_api_integration/common/users/users'; + export default function ApiTest({ getService, getPageObjects }: FtrProviderContext) { const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); const ui = getService('observabilityAIAssistantUI'); const testSubjects = getService('testSubjects'); const browser = getService('browser'); const supertest = getService('supertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); const retry = getService('retry'); const log = getService('log'); const telemetry = getService('kibana_ebt_ui'); @@ -35,6 +40,20 @@ export default function ApiTest({ getService, getPageObjects }: FtrProviderConte const flyoutService = getService('flyout'); + async function login(username: string, password: string | undefined) { + const response = await supertestWithoutAuth + .post('/internal/security/login') + .set('kbn-xsrf', 'xxx') + .send({ + providerType: 'basic', + providerName: 'basic', + currentURL: '/', + params: { username, password }, + }) + .expect(200); + return parseCookie(response.headers['set-cookie'][0])!; + } + async function deleteConversations() { const response = await observabilityAIAssistantAPIClient.editor({ endpoint: 'POST /internal/observability_ai_assistant/conversations', @@ -66,78 +85,84 @@ export default function ApiTest({ getService, getPageObjects }: FtrProviderConte } async function createOldConversation() { - await observabilityAIAssistantAPIClient.editor({ - endpoint: 'POST /internal/observability_ai_assistant/conversation', - params: { - body: { - conversation: { - messages: [ - { - '@timestamp': '2024-04-18T14:28:50.118Z', - message: { - role: MessageRole.System, - content: - 'You are a helpful assistant for Elastic Observability. Your goal is to help the Elastic Observability users to quickly assess what is happening in their observed systems. You can help them visualise and analyze data, investigate their systems, perform root cause analysis or identify optimisation opportunities.\n\nIt\'s very important to not assume what the user is meaning. Ask them for clarification if needed.\n\nIf you are unsure about which function should be used and with what arguments, ask the user for clarification or confirmation.\n\nIn KQL ("kqlFilter")) escaping happens with double quotes, not single quotes. Some characters that need escaping are: \':()\\ /". Always put a field value in double quotes. Best: service.name:"opbeans-go". Wrong: service.name:opbeans-go. This is very important!\n\nYou can use Github-flavored Markdown in your responses. If a function returns an array, consider using a Markdown table to format the response.\n\nNote that ES|QL (the Elasticsearch Query Language which is a new piped language) is the preferred query language.\n\nYou MUST use the "query" function when the user wants to:\n- visualize data\n- run any arbitrary query\n- breakdown or filter ES|QL queries that are displayed on the current page\n- convert queries from another language to ES|QL\n- asks general questions about ES|QL\n\nDO NOT UNDER ANY CIRCUMSTANCES generate ES|QL queries or explain anything about the ES|QL query language yourself.\nDO NOT UNDER ANY CIRCUMSTANCES try to correct an ES|QL query yourself - always use the "query" function for this.\n\nDO NOT UNDER ANY CIRCUMSTANCES USE ES|QL syntax (`service.name == "foo"`) with "kqlFilter" (`service.name:"foo"`).\n\nEven if the "context" function was used before that, follow it up with the "query" function. If a query fails, do not attempt to correct it yourself. Again you should call the "query" function,\neven if it has been called before.\n\nWhen the "visualize_query" function has been called, a visualization has been displayed to the user. DO NOT UNDER ANY CIRCUMSTANCES follow up a "visualize_query" function call with your own visualization attempt.\nIf the "execute_query" function has been called, summarize these results for the user. The user does not see a visualization in this case.\n\nYou MUST use the get_dataset_info function function before calling the "query" or "changes" function.\n\nIf a function requires an index, you MUST use the results from the dataset info functions.\n\n\n\nThe user is able to change the language which they want you to reply in on the settings page of the AI Assistant for Observability, which can be found in the Stack Management app under the option AI Assistants.\nIf the user asks how to change the language, reply in the same language the user asked in.You do not have a working memory. If the user expects you to remember the previous conversations, tell them they can set up the knowledge base.', - }, + const { password } = kbnTestConfig.getUrlParts(); + const sessionCookie = await login(editor.username, password); + const endpoint = '/internal/observability_ai_assistant/conversation'; + const cookie = sessionCookie.cookieString(); + const params = { + body: { + conversation: { + messages: [ + { + '@timestamp': '2024-04-18T14:28:50.118Z', + message: { + role: MessageRole.System, + content: + 'You are a helpful assistant for Elastic Observability. Your goal is to help the Elastic Observability users to quickly assess what is happening in their observed systems. You can help them visualise and analyze data, investigate their systems, perform root cause analysis or identify optimisation opportunities.\n\nIt\'s very important to not assume what the user is meaning. Ask them for clarification if needed.\n\nIf you are unsure about which function should be used and with what arguments, ask the user for clarification or confirmation.\n\nIn KQL ("kqlFilter")) escaping happens with double quotes, not single quotes. Some characters that need escaping are: \':()\\ /". Always put a field value in double quotes. Best: service.name:"opbeans-go". Wrong: service.name:opbeans-go. This is very important!\n\nYou can use Github-flavored Markdown in your responses. If a function returns an array, consider using a Markdown table to format the response.\n\nNote that ES|QL (the Elasticsearch Query Language which is a new piped language) is the preferred query language.\n\nYou MUST use the "query" function when the user wants to:\n- visualize data\n- run any arbitrary query\n- breakdown or filter ES|QL queries that are displayed on the current page\n- convert queries from another language to ES|QL\n- asks general questions about ES|QL\n\nDO NOT UNDER ANY CIRCUMSTANCES generate ES|QL queries or explain anything about the ES|QL query language yourself.\nDO NOT UNDER ANY CIRCUMSTANCES try to correct an ES|QL query yourself - always use the "query" function for this.\n\nDO NOT UNDER ANY CIRCUMSTANCES USE ES|QL syntax (`service.name == "foo"`) with "kqlFilter" (`service.name:"foo"`).\n\nEven if the "context" function was used before that, follow it up with the "query" function. If a query fails, do not attempt to correct it yourself. Again you should call the "query" function,\neven if it has been called before.\n\nWhen the "visualize_query" function has been called, a visualization has been displayed to the user. DO NOT UNDER ANY CIRCUMSTANCES follow up a "visualize_query" function call with your own visualization attempt.\nIf the "execute_query" function has been called, summarize these results for the user. The user does not see a visualization in this case.\n\nYou MUST use the get_dataset_info function function before calling the "query" or "changes" function.\n\nIf a function requires an index, you MUST use the results from the dataset info functions.\n\n\n\nThe user is able to change the language which they want you to reply in on the settings page of the AI Assistant for Observability, which can be found in the Stack Management app under the option AI Assistants.\nIf the user asks how to change the language, reply in the same language the user asked in.You do not have a working memory. If the user expects you to remember the previous conversations, tell them they can set up the knowledge base.', }, - { - '@timestamp': '2024-04-18T14:29:01.615Z', - message: { - content: 'What are SLOs?', - role: MessageRole.User, - }, - }, - { - '@timestamp': '2024-04-18T14:29:01.876Z', - message: { - role: MessageRole.Assistant, - content: '', - function_call: { - name: 'context', - arguments: '{}', - trigger: MessageRole.Assistant, - }, - }, + }, + { + '@timestamp': '2024-04-18T14:29:01.615Z', + message: { + content: 'What are SLOs?', + role: MessageRole.User, }, - { - '@timestamp': '2024-04-18T14:29:01.876Z', - message: { - content: - '{"screen_description":"The user is looking at http://localhost:5601/ftw/app/observabilityAIAssistant/conversations/new. The current time range is 2024-04-18T14:13:49.815Z - 2024-04-18T14:28:49.815Z.","learnings":[]}', + }, + { + '@timestamp': '2024-04-18T14:29:01.876Z', + message: { + role: MessageRole.Assistant, + content: '', + function_call: { name: 'context', - role: MessageRole.User, + arguments: '{}', + trigger: MessageRole.Assistant, }, }, - { - '@timestamp': '2024-04-18T14:29:22.945Z', - message: { - content: - "SLOs, or Service Level Objectives, are a key part of the Site Reliability Engineering (SRE) methodology. They are a target value or range of values for a service level that is measured by an SLI (Service Level Indicator). \n\nAn SLO is a goal for how often and how much you want your service to meet a particular SLI. For example, you might have an SLO that your service should be up and running 99.9% of the time. \n\nSLOs are important because they set clear expectations for your team and your users about the level of service you aim to provide. They also help you make decisions about where to focus your efforts: if you're meeting your SLOs, you can focus on building new features; if you're not meeting your SLOs, you need to focus on improving reliability. \n\nIn Elastic Observability, you can define and monitor your SLOs to ensure your services are meeting their targets.", - function_call: { - name: '', - arguments: '', - trigger: MessageRole.Assistant, - }, - role: MessageRole.Assistant, - }, + }, + { + '@timestamp': '2024-04-18T14:29:01.876Z', + message: { + content: + '{"screen_description":"The user is looking at http://localhost:5601/ftw/app/observabilityAIAssistant/conversations/new. The current time range is 2024-04-18T14:13:49.815Z - 2024-04-18T14:28:49.815Z.","learnings":[]}', + name: 'context', + role: MessageRole.User, }, - ], - conversation: { - title: 'My old conversation', - token_count: { - completion: 1, - prompt: 1, - total: 2, + }, + { + '@timestamp': '2024-04-18T14:29:22.945Z', + message: { + content: + "SLOs, or Service Level Objectives, are a key part of the Site Reliability Engineering (SRE) methodology. They are a target value or range of values for a service level that is measured by an SLI (Service Level Indicator). \n\nAn SLO is a goal for how often and how much you want your service to meet a particular SLI. For example, you might have an SLO that your service should be up and running 99.9% of the time. \n\nSLOs are important because they set clear expectations for your team and your users about the level of service you aim to provide. They also help you make decisions about where to focus your efforts: if you're meeting your SLOs, you can focus on building new features; if you're not meeting your SLOs, you need to focus on improving reliability. \n\nIn Elastic Observability, you can define and monitor your SLOs to ensure your services are meeting their targets.", + function_call: { + name: '', + arguments: '', + trigger: MessageRole.Assistant, + }, + role: MessageRole.Assistant, }, }, - '@timestamp': '2024-04-18T14:29:22.948', - public: false, - numeric_labels: {}, - labels: {}, + ], + conversation: { + title: 'My old conversation', + token_count: { + completion: 1, + prompt: 1, + total: 2, + }, }, + '@timestamp': '2024-04-18T14:29:22.948', + public: false, + numeric_labels: {}, + labels: {}, }, }, - }); + }; + await supertestWithoutAuth + .post(endpoint) + .set('kbn-xsrf', 'xxx') + .set('Cookie', cookie) + .send(params.body); } describe('Conversations', () => { diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts index 88ef256b353e6..a6bf7e7e9d5f2 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts @@ -49,6 +49,9 @@ export default function ({ getService }: FtrProviderContext) { 'Fleet-Usage-Logger', 'Fleet-Usage-Sender', 'ML:saved-objects-sync', + 'ProductDocBase:EnsureUpToDate', + 'ProductDocBase:InstallAll', + 'ProductDocBase:UninstallAll', 'SLO:ORPHAN_SUMMARIES-CLEANUP-TASK', 'Synthetics:Clean-Up-Package-Policies', 'UPTIME:SyntheticsService:Sync-Saved-Monitor-Objects', diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/engine.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/entity_store.ts similarity index 78% rename from x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/engine.ts rename to x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/entity_store.ts index f51fbd15ceead..1fbaaa9b3fc71 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/engine.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/entity_store.ts @@ -14,7 +14,7 @@ export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const utils = EntityStoreUtils(getService); - describe('@ess @skipInServerlessMKI Entity Store Engine APIs', () => { + describe('@ess @skipInServerlessMKI Entity Store APIs', () => { const dataView = dataViewRouteHelpersFactory(supertest); before(async () => { @@ -42,6 +42,18 @@ export default ({ getService }: FtrProviderContext) => { }); }); + describe('enablement', () => { + afterEach(async () => { + await utils.cleanEngines(); + }); + + it('should enable the entity store, creating both user and host engines', async () => { + await utils.enableEntityStore(); + await utils.expectEngineAssetsExist('user'); + await utils.expectEngineAssetsExist('host'); + }); + }); + describe('get and list', () => { before(async () => { await utils.initEntityEngineForEntityTypesAndWait(['host', 'user']); @@ -182,6 +194,42 @@ export default ({ getService }: FtrProviderContext) => { }); }); + describe('status', () => { + afterEach(async () => { + await utils.cleanEngines(); + }); + it('should return "not_installed" when no engines have been initialized', async () => { + const { body } = await api.getEntityStoreStatus().expect(200); + + expect(body).to.eql({ + engines: [], + status: 'not_installed', + }); + }); + + it('should return "installing" when at least one engine is being initialized', async () => { + await utils.enableEntityStore(); + + const { body } = await api.getEntityStoreStatus().expect(200); + + expect(body.status).to.eql('installing'); + expect(body.engines.length).to.eql(2); + expect(body.engines[0].status).to.eql('installing'); + expect(body.engines[1].status).to.eql('installing'); + }); + + it('should return "started" when all engines are started', async () => { + await utils.initEntityEngineForEntityTypesAndWait(['host', 'user']); + + const { body } = await api.getEntityStoreStatus().expect(200); + + expect(body.status).to.eql('running'); + expect(body.engines.length).to.eql(2); + expect(body.engines[0].status).to.eql('started'); + expect(body.engines[1].status).to.eql('started'); + }); + }); + describe('apply_dataview_indices', () => { before(async () => { await utils.initEntityEngineForEntityTypesAndWait(['host']); diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/engine_nondefault_spaces.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/entity_store_nondefault_spaces.ts similarity index 100% rename from x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/engine_nondefault_spaces.ts rename to x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/entity_store_nondefault_spaces.ts diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/index.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/index.ts index 5f2d15db240c6..899dbc68102f3 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/index.ts @@ -10,8 +10,8 @@ import { FtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('Entity Analytics - Entity Store', function () { loadTestFile(require.resolve('./entities_list')); - loadTestFile(require.resolve('./engine')); + loadTestFile(require.resolve('./entity_store')); loadTestFile(require.resolve('./field_retention_operators')); - loadTestFile(require.resolve('./engine_nondefault_spaces')); + loadTestFile(require.resolve('./entity_store_nondefault_spaces')); }); } diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/entity_store.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/entity_store.ts index 7ee32e20640d6..fff1040b81f29 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/entity_store.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/entity_store.ts @@ -90,6 +90,16 @@ export const EntityStoreUtils = ( ); }; + const enableEntityStore = async () => { + const res = await api.initEntityStore({ body: {} }, namespace); + if (res.status !== 200) { + log.error(`Failed to enable entity store`); + log.error(JSON.stringify(res.body)); + } + expect(res.status).to.eql(200); + return res; + }; + const expectTransformStatus = async ( transformId: string, exists: boolean, @@ -144,5 +154,6 @@ export const EntityStoreUtils = ( expectTransformStatus, expectEngineAssetsExist, expectEngineAssetsDoNotExist, + enableEntityStore, }; }; diff --git a/x-pack/test/spaces_api_integration/common/lib/authentication.ts b/x-pack/test/spaces_api_integration/common/lib/authentication.ts index 27f644c3f5cd5..cbd261008dacb 100644 --- a/x-pack/test/spaces_api_integration/common/lib/authentication.ts +++ b/x-pack/test/spaces_api_integration/common/lib/authentication.ts @@ -90,10 +90,6 @@ export const AUTHENTICATION = { username: 'a_kibana_rbac_space_1_saved_objects_read_user', password: 'password', }, - APM_USER: { - username: 'a_apm_user', - password: 'password', - }, MACHINE_LEARING_ADMIN: { username: 'a_machine_learning_admin', password: 'password', diff --git a/x-pack/test/spaces_api_integration/common/lib/create_users_and_roles.ts b/x-pack/test/spaces_api_integration/common/lib/create_users_and_roles.ts index 2f93cc09fd032..f917a6efed15f 100644 --- a/x-pack/test/spaces_api_integration/common/lib/create_users_and_roles.ts +++ b/x-pack/test/spaces_api_integration/common/lib/create_users_and_roles.ts @@ -469,16 +469,6 @@ export const createUsersAndRoles = async (es: Client, supertest: SuperTestAgent) }, }); - await es.security.putUser({ - username: AUTHENTICATION.APM_USER.username, - body: { - password: AUTHENTICATION.APM_USER.password, - roles: ['apm_user'], - full_name: 'a apm user', - email: 'a_apm_user@elastic.co', - }, - }); - await es.security.putUser({ username: AUTHENTICATION.MACHINE_LEARING_ADMIN.username, body: { diff --git a/x-pack/test/spaces_api_integration/security_and_spaces/apis/get_all.ts b/x-pack/test/spaces_api_integration/security_and_spaces/apis/get_all.ts index 1c2db5f6bcd7c..d40413f9457e3 100644 --- a/x-pack/test/spaces_api_integration/security_and_spaces/apis/get_all.ts +++ b/x-pack/test/spaces_api_integration/security_and_spaces/apis/get_all.ts @@ -57,7 +57,6 @@ export default function getAllSpacesTestSuite({ getService }: FtrProviderContext legacyAll: AUTHENTICATION.KIBANA_LEGACY_USER, dualAll: AUTHENTICATION.KIBANA_DUAL_PRIVILEGES_USER, dualRead: AUTHENTICATION.KIBANA_DUAL_PRIVILEGES_DASHBOARD_ONLY_USER, - apmUser: AUTHENTICATION.APM_USER, machineLearningAdmin: AUTHENTICATION.MACHINE_LEARING_ADMIN, machineLearningUser: AUTHENTICATION.MACHINE_LEARNING_USER, monitoringUser: AUTHENTICATION.MONITORING_USER, @@ -83,7 +82,6 @@ export default function getAllSpacesTestSuite({ getService }: FtrProviderContext legacyAll: AUTHENTICATION.KIBANA_LEGACY_USER, dualAll: AUTHENTICATION.KIBANA_DUAL_PRIVILEGES_USER, dualRead: AUTHENTICATION.KIBANA_DUAL_PRIVILEGES_DASHBOARD_ONLY_USER, - apmUser: AUTHENTICATION.APM_USER, machineLearningAdmin: AUTHENTICATION.MACHINE_LEARING_ADMIN, machineLearningUser: AUTHENTICATION.MACHINE_LEARNING_USER, monitoringUser: AUTHENTICATION.MONITORING_USER, @@ -484,29 +482,6 @@ export default function getAllSpacesTestSuite({ getService }: FtrProviderContext } ); - getAllTest(`apm_user can't access any spaces from ${scenario.spaceId}`, { - spaceId: scenario.spaceId, - user: scenario.users.apmUser, - tests: { - exists: { - statusCode: 403, - response: expectRbacForbidden, - }, - copySavedObjectsPurpose: { - statusCode: 403, - response: expectRbacForbidden, - }, - shareSavedObjectsPurpose: { - statusCode: 403, - response: expectRbacForbidden, - }, - includeAuthorizedPurposes: { - statusCode: 403, - response: expectRbacForbidden, - }, - }, - }); - getAllTest(`machine_learning_admin can't access any spaces from ${scenario.spaceId}`, { spaceId: scenario.spaceId, user: scenario.users.machineLearningAdmin, diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/apm_api_integration/common/apm_api_supertest.ts b/x-pack/test_serverless/api_integration/test_suites/observability/apm_api_integration/common/apm_api_supertest.ts index 19f102335d99f..3b05b5d08d29d 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/apm_api_integration/common/apm_api_supertest.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/apm_api_integration/common/apm_api_supertest.ts @@ -46,7 +46,7 @@ export function createApmApiClient(st: supertest.Agent) { .set('Content-type', 'multipart/form-data'); for (const field of fields) { - await formDataRequest.field(field[0], field[1]); + void formDataRequest.field(field[0], field[1]); } res = await formDataRequest; diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/apm_api_integration/feature_flags.ts b/x-pack/test_serverless/api_integration/test_suites/observability/apm_api_integration/feature_flags.ts index 88096d6258e27..15af0d68d8db7 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/apm_api_integration/feature_flags.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/apm_api_integration/feature_flags.ts @@ -77,9 +77,7 @@ export default function ({ getService }: APMFtrContextProvider) { const svlUserManager = getService('svlUserManager'); const svlCommonApi = getService('svlCommonApi'); - // https://github.com/elastic/kibana/pull/190690 - // skipping since "rejects requests to list source maps" fails with 400 - describe.skip('apm feature flags', () => { + describe('apm feature flags', () => { let roleAuthc: RoleCredentials; let internalReqHeader: InternalRequestHeader; diff --git a/x-pack/test_serverless/functional/page_objects/svl_search_connectors_page.ts b/x-pack/test_serverless/functional/page_objects/svl_search_connectors_page.ts index 615e3397a45ce..78554ea05beda 100644 --- a/x-pack/test_serverless/functional/page_objects/svl_search_connectors_page.ts +++ b/x-pack/test_serverless/functional/page_objects/svl_search_connectors_page.ts @@ -14,7 +14,7 @@ export function SvlSearchConnectorsPageProvider({ getService }: FtrProviderConte return { connectorConfigurationPage: { async createConnector() { - await testSubjects.click('serverlessSearchConnectorsOverviewCreateConnectorButton'); + await testSubjects.click('serverlessSearchEmptyConnectorsPromptCreateConnectorButton'); await testSubjects.existOrFail('serverlessSearchEditConnectorButton'); await testSubjects.exists('serverlessSearchConnectorLinkElasticsearchRunWithDockerButton'); await testSubjects.exists('serverlessSearchConnectorLinkElasticsearchRunFromSourceButton'); @@ -90,9 +90,9 @@ export function SvlSearchConnectorsPageProvider({ getService }: FtrProviderConte }, async expectConnectorOverviewPageComponentsToExist() { await testSubjects.existOrFail('serverlessSearchConnectorsTitle'); - await testSubjects.existOrFail('serverlessSearchConnectorsOverviewElasticConnectorsLink'); + // await testSubjects.existOrFail('serverlessSearchConnectorsOverviewElasticConnectorsLink'); await testSubjects.exists('serverlessSearchEmptyConnectorsPromptCreateConnectorButton'); - await testSubjects.existOrFail('serverlessSearchConnectorsOverviewCreateConnectorButton'); + // await testSubjects.existOrFail('serverlessSearchConnectorsOverviewCreateConnectorButton'); }, async expectConnectorTableToExist() { await testSubjects.existOrFail('serverlessSearchConnectorTable'); diff --git a/x-pack/test_serverless/functional/test_suites/search/navigation.ts b/x-pack/test_serverless/functional/test_suites/search/navigation.ts index 97952d68f8fd1..24009866b2b15 100644 --- a/x-pack/test_serverless/functional/test_suites/search/navigation.ts +++ b/x-pack/test_serverless/functional/test_suites/search/navigation.ts @@ -241,6 +241,7 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { await solutionNavigation.sidenav.expectLinkExists({ text: 'Data' }); await solutionNavigation.sidenav.expectLinkExists({ text: 'Index Management' }); await solutionNavigation.sidenav.expectLinkExists({ text: 'Connectors' }); + await solutionNavigation.sidenav.expectLinkExists({ text: 'Web crawlers' }); await solutionNavigation.sidenav.expectLinkExists({ text: 'Build' }); await solutionNavigation.sidenav.expectLinkExists({ text: 'Dev Tools' }); await solutionNavigation.sidenav.expectLinkExists({ text: 'Playground' }); @@ -265,6 +266,7 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { 'data', 'management:index_management', 'serverlessConnectors', + 'serverlessWebCrawlers', 'build', 'dev_tools', 'searchPlayground', diff --git a/yarn.lock b/yarn.lock index 719e45bb7cab7..5b3b6f246ff42 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1748,11 +1748,25 @@ resolved "https://registry.yarnpkg.com/@elastic/eslint-plugin-eui/-/eslint-plugin-eui-0.0.2.tgz#56b9ef03984a05cc213772ae3713ea8ef47b0314" integrity sha512-IoxURM5zraoQ7C8f+mJb9HYSENiZGgRVcG4tLQxE61yHNNRDXtGDWTZh8N1KIHcsqN1CEPETjuzBXkJYF/fDiQ== -"@elastic/eui@97.3.1": - version "97.3.1" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-97.3.1.tgz#b0f07c603042bd359544b41829507e65f4fa3cd2" - integrity sha512-zJs3aaO6qjTdxJM2mPahcqaC6FfaC34yTc3qpQq7+Cbhw2xGrwx8bAfIzhttLU87mwgr59Sqv9Ojvwk8c3js7A== +"@elastic/eui-theme-borealis@0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@elastic/eui-theme-borealis/-/eui-theme-borealis-0.0.2.tgz#4b65f13073b1887a12641063ace96539fa923674" + integrity sha512-ekePJ+V9UMCUDqjNLECjM+Vi/qHkJcu6lhm1GenUFs3awPxaLhvasb3pN++qnWYkXWo90vmZER62MTHpxlQyQA== + +"@elastic/eui-theme-common@0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@elastic/eui-theme-common/-/eui-theme-common-0.0.2.tgz#3da6078a5d255c5740423d26409e5e06536a5db3" + integrity sha512-tIyXrylrLhmOWiRbxuJSiHHVJpt4fVd5frzhUGoSN2frobOT9RLh8Klzyd4kmHasZ7bB1vETPR5fytqgocRvdA== + dependencies: + "@types/lodash" "^4.14.202" + lodash "^4.17.21" + +"@elastic/eui@97.3.1-borealis.2": + version "97.3.1-borealis.2" + resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-97.3.1-borealis.2.tgz#32d9616ddbab11ef6e97739cf728a667220ca74c" + integrity sha512-j0WsE+WWtV3eEbRqyjr8hJ1swQIbCEGc9iViMtDK/XeVCVqs++dJE/+jPdjharMjXLrstOr0cx0uvtsH6OWTUw== dependencies: + "@elastic/eui-theme-common" "0.0.2" "@hello-pangea/dnd" "^16.6.0" "@types/lodash" "^4.14.202" "@types/numeral" "^2.0.5" @@ -5550,6 +5564,10 @@ version "0.0.0" uid "" +"@kbn/llm-tasks-plugin@link:x-pack/plugins/ai_infra/llm_tasks": + version "0.0.0" + uid "" + "@kbn/locator-examples-plugin@link:examples/locator_examples": version "0.0.0" uid "" @@ -6034,6 +6052,14 @@ version "0.0.0" uid "" +"@kbn/product-doc-base-plugin@link:x-pack/plugins/ai_infra/product_doc_base": + version "0.0.0" + uid "" + +"@kbn/product-doc-common@link:x-pack/packages/ai-infra/product-doc-common": + version "0.0.0" + uid "" + "@kbn/profiling-data-access-plugin@link:x-pack/plugins/observability_solution/profiling_data_access": version "0.0.0" uid "" @@ -12127,19 +12153,19 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== -"@xyflow/react@^12.3.4": - version "12.3.4" - resolved "https://registry.yarnpkg.com/@xyflow/react/-/react-12.3.4.tgz#cccc57f7a992faecc5ed1dda82838b31c1afa522" - integrity sha512-KjFkj84S+wK8aJF/PORxSkOAeotTTPz++hus+Y95NFMIJGVyl8jjVaaz5B1zyV0prk6ZkbMp6q0vqMjJdZT25Q== +"@xyflow/react@^12.3.5": + version "12.3.5" + resolved "https://registry.yarnpkg.com/@xyflow/react/-/react-12.3.5.tgz#88ca2efe2ddf1300bc171a2ef797f7cb41386ca4" + integrity sha512-wAYqpicdrVo1rxCu0X3M9s3YIF45Agqfabw0IBryTGqjWvr2NyfciI8gIP4MB+NKpWWN5kxZ9tiZ9u8lwC7iAg== dependencies: - "@xyflow/system" "0.0.45" + "@xyflow/system" "0.0.46" classcat "^5.0.3" zustand "^4.4.0" -"@xyflow/system@0.0.45": - version "0.0.45" - resolved "https://registry.yarnpkg.com/@xyflow/system/-/system-0.0.45.tgz#ca1f4d843d2925ce9c5763f16abf51a4c69953ef" - integrity sha512-szP1LjDD4jlRYYhxvgZqOCTMToUVNqjQkrlhb0fhv1sXomU1+yMDdhpQT+FjE4d+rKx08fS10sOuZUl2ycXaDw== +"@xyflow/system@0.0.46": + version "0.0.46" + resolved "https://registry.yarnpkg.com/@xyflow/system/-/system-0.0.46.tgz#b0a5915d59c0ea5ca6d24e1eb90c5a0d7eda7864" + integrity sha512-bmFXvboVdiydIFZmDCjrbBCYgB0d5pYdkcZPWbAxGmhMRUZ+kW3CksYgYxWabrw51rwpWitLEadvLrivG0mVfA== dependencies: "@types/d3-drag" "^3.0.7" "@types/d3-selection" "^3.0.10"