From d91f0e83db9a1e4483519d0b3d3e211243d55bc8 Mon Sep 17 00:00:00 2001 From: Wafaa Nasr Date: Wed, 25 Oct 2023 09:17:37 +0200 Subject: [PATCH 01/24] [Security Solution][Exceptions][API testing] Add skipInQA tag and applied for failing tests (#169646) ## Summary - In order to merge the MKI pipeline for FTR added in this PR https://github.com/elastic/kibana/pull/169422 we need to merge this one first - Introduce the "skipInQA" tag along with its respective commands, and apply it to the failing tests. - The skipped tests are failing because the `elastic` user on QA is missing some permissions on the `restricted_indices ` image - Once we fix this issue in MKI/QA env those tests will be unskipped again https://buildkite.com/elastic/kibana-serverless-security-solution-quality-gate-api-integration/builds/6#018b47bd-edfc-4608-bce1-5b435b1a01d4 --- .../package.json | 5 +++++ .../role_based_rule_exceptions_workflows.ts | 18 +++++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/x-pack/test/security_solution_api_integration/package.json b/x-pack/test/security_solution_api_integration/package.json index ed8252f86c2da..d362078fc03b1 100644 --- a/x-pack/test/security_solution_api_integration/package.json +++ b/x-pack/test/security_solution_api_integration/package.json @@ -7,22 +7,27 @@ "scripts": { "exception_workflows:server:serverless": "node ../../../scripts/functional_tests_server.js --config ./test_suites/detections_response/default_license/exceptions/workflows/configs/serverless.config.ts", "exception_workflows:runner:serverless": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/exceptions/workflows/configs/serverless.config.ts --grep @serverless --grep @brokenInServerless --invert", + "exception_workflows:qa:serverless": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/exceptions/workflows/configs/serverless.config.ts --grep @serverless --grep '@brokenInServerless|@skipInQA' --invert", "exception_workflows:server:ess": "node ../../../scripts/functional_tests_server.js --config ./test_suites/detections_response/default_license/exceptions/workflows/configs/ess.config.ts", "exception_workflows:runner:ess": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/exceptions/workflows/configs/ess.config.ts --grep @ess", "exception_operators_date_numeric_types:server:serverless": "node ../../../scripts/functional_tests_server.js --config ./test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/configs/serverless.config.ts", "exception_operators_date_numeric_types:runner:serverless": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/configs/serverless.config.ts --grep @serverless --grep @brokenInServerless --invert", + "exception_operators_date_numeric_types:qa:serverless": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/configs/serverless.config.ts --grep @serverless --grep '@brokenInServerless|@skipInQA' --invert", "exception_operators_date_numeric_types:server:ess": "node ../../../scripts/functional_tests_server.js --config ./test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/configs/ess.config.ts", "exception_operators_date_numeric_types:runner:ess": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/exceptions/operators_data_types/date_numeric_types/configs/ess.config.ts --grep @ess", "exception_operators_keyword_text_long:server:serverless": "node ../../../scripts/functional_tests_server.js --config ./test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/configs/serverless.config.ts", "exception_operators_keyword_text_long:runner:serverless": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/configs/serverless.config.ts --grep @serverless --grep @brokenInServerless --invert", + "exception_operators_keyword_text_long:qa:serverless": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/configs/serverless.config.ts --grep @serverless --grep '@brokenInServerless|@skipInQA' --invert", "exception_operators_keyword_text_long:server:ess": "node ../../../scripts/functional_tests_server.js --config ./test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/configs/ess.config.ts", "exception_operators_keyword_text_long:runner:ess": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/exceptions/operators_data_types/keyword_text_long/configs/ess.config.ts --grep @ess", "exception_operators_ips_text_array:server:serverless": "node ../../../scripts/functional_tests_server.js --config ./test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array/configs/serverless.config.ts", "exception_operators_ips_text_array:runner:serverless": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array/configs/serverless.config.ts --grep @serverless --grep @brokenInServerless --invert", + "exception_operators_ips_text_array:qa:serverless": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array/configs/serverless.config.ts --grep @serverless --grep '@brokenInServerless|@skipInQA' --invert", "exception_operators_ips_text_array:server:ess": "node ../../../scripts/functional_tests_server.js --config ./test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array/configs/ess.config.ts", "exception_operators_ips_text_array:runner:ess": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/exceptions/operators_data_types/ips_text_array/configs/ess.config.ts --grep @ess", "rule_creation:server:serverless": "node ../../../scripts/functional_tests_server.js --config ./test_suites/detections_response/default_license/rule_creation/configs/serverless.config.ts", "rule_creation:runner:serverless": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/rule_creation/configs/serverless.config.ts --grep @serverless --grep @brokenInServerless --invert", + "rule_creation:qa:serverless": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/rule_creation/configs/serverless.config.ts --grep @serverless --grep '@brokenInServerless|@skipInQA' --invert", "rule_creation:server:ess": "node ../../../scripts/functional_tests_server.js --config ./test_suites/detections_response/default_license/rule_creation/configs/ess.config.ts", "rule_creation:runner:ess": "node ../../../scripts/functional_test_runner --config=test_suites/detections_response/default_license/rule_creation/configs/ess.config.ts --grep @ess" } diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/role_based_rule_exceptions_workflows.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/role_based_rule_exceptions_workflows.ts index a83aa609949ca..fb5c385ab2c7b 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/role_based_rule_exceptions_workflows.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/exceptions/workflows/role_based_rule_exceptions_workflows.ts @@ -180,7 +180,7 @@ export default ({ getService }: FtrProviderContext) => { expect(bodyToCompare).toEqual(expected); }); - it('should allow removing an exception list from an immutable rule through patch', async () => { + it('@skipInQA should allow removing an exception list from an immutable rule through patch', async () => { await installMockPrebuiltRules(supertest, es); // This rule has an existing exceptions_list that we are going to use @@ -199,7 +199,7 @@ export default ({ getService }: FtrProviderContext) => { expect(immutableRuleSecondTime.exceptions_list.length).toEqual(0); }); - it('should allow adding a second exception list to an immutable rule through patch', async () => { + it('@skipInQA should allow adding a second exception list to an immutable rule through patch', async () => { await installMockPrebuiltRules(supertest, es); const { id, list_id, namespace_type, type } = await createExceptionList( @@ -236,7 +236,7 @@ export default ({ getService }: FtrProviderContext) => { expect(immutableRuleSecondTime.exceptions_list.length).toEqual(2); }); - it('should override any updates to pre-packaged rules if the user removes the exception list through the API but the new version of a rule has an exception list again', async () => { + it('@skipInQA should override any updates to pre-packaged rules if the user removes the exception list through the API but the new version of a rule has an exception list again', async () => { await installMockPrebuiltRules(supertest, es); // This rule has an existing exceptions_list that we are going to use @@ -259,7 +259,7 @@ export default ({ getService }: FtrProviderContext) => { expect(immutableRuleSecondTime.exceptions_list).toEqual(immutableRule.exceptions_list); }); - it('should merge back an exceptions_list if it was removed from the immutable rule through PATCH', async () => { + it('@skipInQA should merge back an exceptions_list if it was removed from the immutable rule through PATCH', async () => { await installMockPrebuiltRules(supertest, es); const { id, list_id, namespace_type, type } = await createExceptionList( @@ -305,7 +305,7 @@ export default ({ getService }: FtrProviderContext) => { ]); }); - it('should NOT add an extra exceptions_list that already exists on a rule during an upgrade', async () => { + it('@skipInQA should NOT add an extra exceptions_list that already exists on a rule during an upgrade', async () => { await installMockPrebuiltRules(supertest, es); // This rule has an existing exceptions_list that we are going to ensure does not stomp on our existing rule @@ -324,7 +324,7 @@ export default ({ getService }: FtrProviderContext) => { ]); }); - it('should NOT allow updates to pre-packaged rules to overwrite existing exception based rules when the user adds an additional exception list', async () => { + it('@skipInQA should NOT allow updates to pre-packaged rules to overwrite existing exception based rules when the user adds an additional exception list', async () => { await installMockPrebuiltRules(supertest, es); const { id, list_id, namespace_type, type } = await createExceptionList( @@ -371,7 +371,7 @@ export default ({ getService }: FtrProviderContext) => { ]); }); - it('should not remove any exceptions added to a pre-packaged/immutable rule during an update if that rule has no existing exception lists', async () => { + it('@skipInQA should not remove any exceptions added to a pre-packaged/immutable rule during an update if that rule has no existing exception lists', async () => { await installMockPrebuiltRules(supertest, es); // Create a new exception list @@ -425,7 +425,7 @@ export default ({ getService }: FtrProviderContext) => { ]); }); - it('should not change the immutable tags when adding a second exception list to an immutable rule through patch', async () => { + it('@skipInQA should not change the immutable tags when adding a second exception list to an immutable rule through patch', async () => { await installMockPrebuiltRules(supertest, es); const { id, list_id, namespace_type, type } = await createExceptionList( @@ -466,7 +466,7 @@ export default ({ getService }: FtrProviderContext) => { expect(bodyToCompare.version).toEqual(immutableRule.version); // The version should never update on a patch }); - it('should not change count of prepacked rules when adding a second exception list to an immutable rule through patch. If this fails, suspect the immutable tags are not staying on the rule correctly.', async () => { + it('@skipInQA should not change count of prepacked rules when adding a second exception list to an immutable rule through patch. If this fails, suspect the immutable tags are not staying on the rule correctly.', async () => { await installMockPrebuiltRules(supertest, es); const { id, list_id, namespace_type, type } = await createExceptionList( From 98161265887f6a72f2a147003c107fd6a4f42adf Mon Sep 17 00:00:00 2001 From: Janki Salvi <117571355+js-jankisalvi@users.noreply.github.com> Date: Wed, 25 Oct 2023 09:42:01 +0200 Subject: [PATCH 02/24] [Cases] remove grab icon from custom field list (#169676) ## Summary Removing drag icon as discussed in https://github.com/elastic/kibana/issues/169348 ![image](https://github.com/elastic/kibana/assets/117571355/a496473f-5969-4ea0-95fb-e37b055a8124) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../components/custom_fields/custom_fields_list/index.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/x-pack/plugins/cases/public/components/custom_fields/custom_fields_list/index.tsx b/x-pack/plugins/cases/public/components/custom_fields/custom_fields_list/index.tsx index 64420fc463649..649b0ec5d339f 100644 --- a/x-pack/plugins/cases/public/components/custom_fields/custom_fields_list/index.tsx +++ b/x-pack/plugins/cases/public/components/custom_fields/custom_fields_list/index.tsx @@ -12,7 +12,6 @@ import { EuiFlexItem, EuiSpacer, EuiText, - EuiIcon, EuiButtonIcon, } from '@elastic/eui'; @@ -63,9 +62,6 @@ const CustomFieldsListComponent: React.FC = (props) => { hasShadow={false} > - - - From c303263344e5b28ce8f3da4ecdcdb2f5d2fcad82 Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Wed, 25 Oct 2023 10:09:28 +0200 Subject: [PATCH 03/24] [CI] automatically commit SO def change snapshot (#169610) Fix https://github.com/elastic/kibana/issues/147935 Automatically run the SO def integration test, and commit the changes if any, as a new CI check script. As explained in the issue, this is just some DX improvement, avoiding for the developer the inconvenience of doing it manually every time they change or add a SO in any PR. --- .buildkite/scripts/steps/checks.sh | 1 + .../checks/saved_objects_definition_change.sh | 11 ++++++++++ .../ci_checks/jest.integration.config.js | 20 +++++++++++++++++++ .../check_registered_types.test.ts | 0 4 files changed, 32 insertions(+) create mode 100755 .buildkite/scripts/steps/checks/saved_objects_definition_change.sh create mode 100644 src/core/server/integration_tests/ci_checks/jest.integration.config.js rename src/core/server/integration_tests/{saved_objects/migrations/group2 => ci_checks/saved_objects}/check_registered_types.test.ts (100%) diff --git a/.buildkite/scripts/steps/checks.sh b/.buildkite/scripts/steps/checks.sh index 92ae1f5eadca3..639e3f02ef1df 100755 --- a/.buildkite/scripts/steps/checks.sh +++ b/.buildkite/scripts/steps/checks.sh @@ -22,5 +22,6 @@ export DISABLE_BOOTSTRAP_VALIDATION=false .buildkite/scripts/steps/checks/test_hardening.sh .buildkite/scripts/steps/checks/ftr_configs.sh .buildkite/scripts/steps/checks/saved_objects_compat_changes.sh +.buildkite/scripts/steps/checks/saved_objects_definition_change.sh .buildkite/scripts/steps/code_generation/security_solution_codegen.sh .buildkite/scripts/steps/checks/yarn_deduplicate.sh diff --git a/.buildkite/scripts/steps/checks/saved_objects_definition_change.sh b/.buildkite/scripts/steps/checks/saved_objects_definition_change.sh new file mode 100755 index 0000000000000..175128d536fd5 --- /dev/null +++ b/.buildkite/scripts/steps/checks/saved_objects_definition_change.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source .buildkite/scripts/common/util.sh + +echo --- Check SO definition changes +cmd="node scripts/jest_integration -u src/core/server/integration_tests/ci_checks" + +eval "$cmd" +check_for_changed_files "$cmd" true diff --git a/src/core/server/integration_tests/ci_checks/jest.integration.config.js b/src/core/server/integration_tests/ci_checks/jest.integration.config.js new file mode 100644 index 0000000000000..cca8ea704bb8d --- /dev/null +++ b/src/core/server/integration_tests/ci_checks/jest.integration.config.js @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + bail: true, // only report 1 issue + // TODO replace the line below with + // preset: '@kbn/test/jest_integration_node + // to do so, we must fix all integration tests first + // see https://github.com/elastic/kibana/pull/130255/ + preset: '@kbn/test/jest_integration', + rootDir: '../../../../..', + roots: ['/src/core/server/integration_tests/ci_checks'], + // must override to match all test given there is no `integration_tests` subfolder + testMatch: ['**/*.test.{js,mjs,ts,tsx}'], +}; diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts similarity index 100% rename from src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts rename to src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts From 906987c2860b53b91d449bc164957857adddc06a Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Wed, 25 Oct 2023 11:24:32 +0200 Subject: [PATCH 04/24] [Security Solituon] Unskip search bar Serverless Cypress tests (#169347) **Addreses:** https://github.com/elastic/kibana/issues/161540 ## Summary This PR unskips `search_bar.cy.ts` Serverless Cypress tests. ## Details Besides just unskipping `search_bar.cy.ts` this PR also makes sure the test isn't flaky by making `operator` required in `fillAddFilterForm()`. It turned out the test works only if the Cypress window is in focus when an operator isn't set. Such behavior can lead to test flakiness in CI. This way choosing an operator via keyboard is a safer option. ## Flaky test runner `search_bar.cy.ts` [150 runs](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3614) --- .../cypress/e2e/header/search_bar.cy.ts | 3 +-- .../alerts/detection_page_filters.cy.ts | 1 + .../cypress/objects/filter.ts | 3 ++- .../cypress/screens/search_bar.ts | 3 --- .../cypress/tasks/search_bar.ts | 13 +++++-------- 5 files changed, 9 insertions(+), 14 deletions(-) diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/header/search_bar.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/header/search_bar.cy.ts index 7600c8edd9bbd..203e966eef9ad 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/header/search_bar.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/header/search_bar.cy.ts @@ -23,8 +23,7 @@ import { getHostIpFilter } from '../../objects/filter'; import { hostsUrl } from '../../urls/navigation'; import { waitForAllHostsToBeLoaded } from '../../tasks/hosts/all_hosts'; -// FLAKY: https://github.com/elastic/kibana/issues/165637 -describe('SearchBar', { tags: ['@ess', '@serverless', '@brokenInServerless'] }, () => { +describe('SearchBar', { tags: ['@ess', '@serverless'] }, () => { beforeEach(() => { login(); visit(hostsUrl('allHosts')); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/detection_page_filters.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/detection_page_filters.cy.ts index 0b0169da945b9..9231b73843b1c 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/detection_page_filters.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/detection_page_filters.cy.ts @@ -361,6 +361,7 @@ describe.skip(`Detections : Page Filters`, { tags: ['@ess', '@brokenInServerless openAddFilterPopover(); fillAddFilterForm({ key: 'kibana.alert.workflow_status', + operator: 'is', value: 'invalid', }); waitForPageFilters(); diff --git a/x-pack/test/security_solution_cypress/cypress/objects/filter.ts b/x-pack/test/security_solution_cypress/cypress/objects/filter.ts index 2ab271b795ca4..22df1394ff35c 100644 --- a/x-pack/test/security_solution_cypress/cypress/objects/filter.ts +++ b/x-pack/test/security_solution_cypress/cypress/objects/filter.ts @@ -7,11 +7,12 @@ export interface SearchBarFilter { key: string; + operator: 'is' | 'is not' | 'is one of' | 'is not one of' | 'exists' | 'does not exist'; value?: string; - operator?: 'is' | 'is not' | 'is one of' | 'is not one of' | 'exists' | 'does not exist'; } export const getHostIpFilter = (): SearchBarFilter => ({ key: 'host.ip', + operator: 'is', value: '1.1.1.1', }); diff --git a/x-pack/test/security_solution_cypress/cypress/screens/search_bar.ts b/x-pack/test/security_solution_cypress/cypress/screens/search_bar.ts index 4517f1e3b2632..36b3accb3e5a6 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/search_bar.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/search_bar.ts @@ -26,9 +26,6 @@ export const ADD_FILTER_FORM_FIELD_OPTION = (value: string) => export const ADD_FILTER_FORM_OPERATOR_FIELD = '[data-test-subj="filterOperatorList"] input[data-test-subj="comboBoxSearchInput"]'; -export const ADD_FILTER_FORM_OPERATOR_OPTION_IS = - '[data-test-subj="comboBoxOptionsList filterOperatorList-optionsList"] button[title="is"]'; - export const ADD_FILTER_FORM_FILTER_VALUE_INPUT = '[data-test-subj="filterParams"] input'; export const ADD_FILTER_FORM_SAVE_BUTTON = '[data-test-subj="saveFilter"]'; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/search_bar.ts b/x-pack/test/security_solution_cypress/cypress/tasks/search_bar.ts index 2d33ff2244571..3a4355cad2e7e 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/search_bar.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/search_bar.ts @@ -13,7 +13,6 @@ import { GLOBAL_SEARCH_BAR_SUBMIT_BUTTON, ADD_FILTER_FORM_SAVE_BUTTON, ADD_FILTER_FORM_FIELD_INPUT, - ADD_FILTER_FORM_OPERATOR_OPTION_IS, ADD_FILTER_FORM_OPERATOR_FIELD, ADD_FILTER_FORM_FILTER_VALUE_INPUT, GLOBAL_KQL_INPUT, @@ -53,19 +52,17 @@ export const removeKqlFilter = () => { }); }; -export const fillAddFilterForm = ({ key, value, operator }: SearchBarFilter) => { +export const fillAddFilterForm = ({ key, operator, value }: SearchBarFilter) => { cy.get(ADD_FILTER_FORM_FIELD_INPUT).should('exist'); cy.get(ADD_FILTER_FORM_FIELD_INPUT).should('be.visible'); cy.get(ADD_FILTER_FORM_FIELD_INPUT).type(`${key}{downarrow}{enter}`); - if (!operator) { - cy.get(ADD_FILTER_FORM_OPERATOR_FIELD).click(); - cy.get(ADD_FILTER_FORM_OPERATOR_OPTION_IS).click(); - } else { - cy.get(ADD_FILTER_FORM_OPERATOR_FIELD).type(`${operator}{enter}`); - } + + cy.get(ADD_FILTER_FORM_OPERATOR_FIELD).type(`${operator}{downarrow}{enter}`); + if (value) { cy.get(ADD_FILTER_FORM_FILTER_VALUE_INPUT).type(value); } + cy.get(ADD_FILTER_FORM_SAVE_BUTTON).click(); cy.get(ADD_FILTER_FORM_SAVE_BUTTON).should('not.exist'); }; From 443cf434420016e617e1eda93318ea0fe7935df0 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Wed, 25 Oct 2023 11:36:26 +0200 Subject: [PATCH 05/24] [ML] Transforms: Serverless functional tests for transform list. (#169612) Adds basic functional tests for serverless for the transform list. Since transforms are available in all project types, this adds the tests to `common` so they are run in all three project types. - Navigates to the empty transform list and asserts the page. - Checks `transform` is available as a search feature. --- .../test_serverless/functional/config.base.ts | 3 + .../services/deployment_agnostic_services.ts | 1 + .../test_suites/common/management/index.ts | 2 + .../transforms/search_bar_features.ts | 64 +++++++++++++++++++ .../management/transforms/transform_list.ts | 48 ++++++++++++++ 5 files changed, 118 insertions(+) create mode 100644 x-pack/test_serverless/functional/test_suites/common/management/transforms/search_bar_features.ts create mode 100644 x-pack/test_serverless/functional/test_suites/common/management/transforms/transform_list.ts diff --git a/x-pack/test_serverless/functional/config.base.ts b/x-pack/test_serverless/functional/config.base.ts index c06607ecb1a05..1090f0a96aa64 100644 --- a/x-pack/test_serverless/functional/config.base.ts +++ b/x-pack/test_serverless/functional/config.base.ts @@ -69,6 +69,9 @@ export function createTestConfig(options: CreateTestConfigOptions) { indexManagement: { pathname: '/app/management/data/index_management', }, + transform: { + pathname: '/app/management/data/transform', + }, connectors: { pathname: '/app/management/insightsAndAlerting/triggersActionsConnectors/', }, diff --git a/x-pack/test_serverless/functional/services/deployment_agnostic_services.ts b/x-pack/test_serverless/functional/services/deployment_agnostic_services.ts index f27d3493e0951..4ba56987416d3 100644 --- a/x-pack/test_serverless/functional/services/deployment_agnostic_services.ts +++ b/x-pack/test_serverless/functional/services/deployment_agnostic_services.ts @@ -66,6 +66,7 @@ const deploymentAgnosticFunctionalServices = _.pick(functionalServices, [ 'snapshots', 'supertest', 'testSubjects', + 'transform', 'toasts', 'uptime', 'usageCollection', diff --git a/x-pack/test_serverless/functional/test_suites/common/management/index.ts b/x-pack/test_serverless/functional/test_suites/common/management/index.ts index 83487bc5a0556..f20efc97b091d 100644 --- a/x-pack/test_serverless/functional/test_suites/common/management/index.ts +++ b/x-pack/test_serverless/functional/test_suites/common/management/index.ts @@ -12,6 +12,8 @@ export default ({ loadTestFile }: FtrProviderContext) => { loadTestFile(require.resolve('./index_management/index_templates')); loadTestFile(require.resolve('./index_management/indices')); loadTestFile(require.resolve('./index_management/create_enrich_policy')); + loadTestFile(require.resolve('./transforms/search_bar_features')); + loadTestFile(require.resolve('./transforms/transform_list')); loadTestFile(require.resolve('./advanced_settings')); loadTestFile(require.resolve('./data_view_mgmt')); loadTestFile(require.resolve('./disabled_uis')); diff --git a/x-pack/test_serverless/functional/test_suites/common/management/transforms/search_bar_features.ts b/x-pack/test_serverless/functional/test_suites/common/management/transforms/search_bar_features.ts new file mode 100644 index 0000000000000..995d17a6dc2c4 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/common/management/transforms/search_bar_features.ts @@ -0,0 +1,64 @@ +/* + * 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 expect from '@kbn/expect'; +import type { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['svlCommonPage', 'svlCommonNavigation']); + + const allLabels = [{ search: 'transform', label: 'Data / Transforms', expected: true }]; + const expectedLabels = allLabels.filter((l) => l.expected); + const notExpectedLabels = allLabels.filter((l) => !l.expected); + + describe('Search bar features', () => { + before(async () => { + await PageObjects.svlCommonPage.login(); + }); + + after(async () => { + await PageObjects.svlCommonPage.forceLogout(); + }); + + describe('list features', () => { + if (expectedLabels.length > 0) { + it('has the correct features enabled', async () => { + await PageObjects.svlCommonNavigation.search.showSearch(); + + for (const expectedLabel of expectedLabels) { + await PageObjects.svlCommonNavigation.search.searchFor(expectedLabel.search); + const [result] = await PageObjects.svlCommonNavigation.search.getDisplayedResults(); + const label = result?.label; + expect(label).to.eql( + expectedLabel.label, + `First result should be ${expectedLabel.label} (got matching items '${label}')` + ); + } + + await PageObjects.svlCommonNavigation.search.hideSearch(); + }); + } + + if (notExpectedLabels.length > 0) { + it('has the correct features disabled', async () => { + await PageObjects.svlCommonNavigation.search.showSearch(); + + for (const notExpectedLabel of notExpectedLabels) { + await PageObjects.svlCommonNavigation.search.searchFor(notExpectedLabel.search); + const [result] = await PageObjects.svlCommonNavigation.search.getDisplayedResults(); + const label = result?.label; + expect(label).to.not.eql( + notExpectedLabel.label, + `First result should not be ${notExpectedLabel.label} (got matching items '${label}')` + ); + } + + await PageObjects.svlCommonNavigation.search.hideSearch(); + }); + } + }); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/common/management/transforms/transform_list.ts b/x-pack/test_serverless/functional/test_suites/common/management/transforms/transform_list.ts new file mode 100644 index 0000000000000..453db13c33c28 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/common/management/transforms/transform_list.ts @@ -0,0 +1,48 @@ +/* + * 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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default ({ getPageObjects, getService }: FtrProviderContext) => { + const pageObjects = getPageObjects(['svlCommonPage', 'common', 'header']); + const browser = getService('browser'); + const security = getService('security'); + const transform = getService('transform'); + + describe('Transform List', function () { + before(async () => { + await security.testUser.setRoles(['transform_user']); + await pageObjects.svlCommonPage.login(); + + // For this test to work, make sure there are no pre-existing transform present. + // For example, solutions might set up transforms automatically. + await transform.api.cleanTransformIndices(); + }); + + it('renders the transform list', async () => { + await transform.testExecution.logTestStep('should load the Transform list page'); + await transform.navigation.navigateTo(); + await transform.management.assertTransformListPageExists(); + + const url = await browser.getCurrentUrl(); + expect(url).to.contain(`/transform`); + + await transform.testExecution.logTestStep('should display the stats bar'); + await transform.management.assertTransformStatsBarExists(); + + await transform.testExecution.logTestStep('should display the "No transforms found" message'); + await transform.management.assertNoTransformsFoundMessageExists(); + + await transform.testExecution.logTestStep( + 'should display a disabled "Create first transform" button' + ); + await transform.management.assertCreateFirstTransformButtonExists(); + await transform.management.assertCreateFirstTransformButtonEnabled(true); + }); + }); +}; From ce9a765d8eb7a5db88e57d93ed918b169421bcf3 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Wed, 25 Oct 2023 11:42:45 +0200 Subject: [PATCH 06/24] Improve ML serverless breadcrumbs for oblt and security (#169513) ## Summary close https://github.com/elastic/kibana/issues/167337 It introduces a new way to automatically set the deeper context breadcrumbs in serverless navigation. Instead of using the `serverless.setBreadcrumbs` for setting deeper context breadcrumbs in serverless, the project navigation service merges navigational breadcrumbs with regular chrome's breadcrumbs by deepLinkId --- .../src/chrome_service.tsx | 7 +- .../src/project_navigation/breadcrumbs.tsx | 81 +++++++++++++++++++ .../project_navigation_service.test.ts | 49 +++++++++-- .../project_navigation_service.ts | 37 +++------ .../src/ui/header/header_breadcrumbs.tsx | 4 +- .../core-chrome-browser/src/breadcrumb.ts | 9 ++- packages/default-nav/ml/default_navigation.ts | 27 ++++++- .../default_navigation.test.tsx.snap | 65 ++++++++++++--- .../ui/components/navigation_section_ui.tsx | 2 +- .../public/application/routing/breadcrumbs.ts | 18 ++++- .../navigation_tree/chrome_navigation_tree.ts | 2 + .../components/side_navigation/index.tsx | 12 +++ .../test_suites/observability/navigation.ts | 9 +++ 13 files changed, 266 insertions(+), 56 deletions(-) create mode 100644 packages/core/chrome/core-chrome-browser-internal/src/project_navigation/breadcrumbs.tsx diff --git a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx index 7cd8cec951a3e..0e99c96656d59 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx @@ -223,7 +223,12 @@ export class ChromeService { const navControls = this.navControls.start(); const navLinks = this.navLinks.start({ application, http }); - const projectNavigation = this.projectNavigation.start({ application, navLinks, http }); + const projectNavigation = this.projectNavigation.start({ + application, + navLinks, + http, + chromeBreadcrumbs$: breadcrumbs$, + }); const recentlyAccessed = await this.recentlyAccessed.start({ http }); const docTitle = this.docTitle.start(); const { customBranding$ } = customBranding; diff --git a/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/breadcrumbs.tsx b/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/breadcrumbs.tsx new file mode 100644 index 0000000000000..1f0057e9670de --- /dev/null +++ b/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/breadcrumbs.tsx @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + AppDeepLinkId, + ChromeProjectBreadcrumb, + ChromeProjectNavigationNode, + ChromeSetProjectBreadcrumbsParams, + ChromeBreadcrumb, +} from '@kbn/core-chrome-browser'; +import { createHomeBreadcrumb } from './home_breadcrumbs'; + +export function buildBreadcrumbs({ + homeHref, + projectBreadcrumbs, + activeNodes, + chromeBreadcrumbs, +}: { + homeHref: string; + projectBreadcrumbs: { + breadcrumbs: ChromeProjectBreadcrumb[]; + params: ChromeSetProjectBreadcrumbsParams; + }; + chromeBreadcrumbs: ChromeBreadcrumb[]; + activeNodes: ChromeProjectNavigationNode[][]; +}): ChromeProjectBreadcrumb[] { + const homeBreadcrumb = createHomeBreadcrumb({ + homeHref, + }); + + if (projectBreadcrumbs.params.absolute) { + return [homeBreadcrumb, ...projectBreadcrumbs.breadcrumbs]; + } + + // breadcrumbs take the first active path + const activePath: ChromeProjectNavigationNode[] = activeNodes[0] ?? []; + const navBreadcrumbPath = activePath.filter( + (n) => Boolean(n.title) && n.breadcrumbStatus !== 'hidden' + ); + const navBreadcrumbs = navBreadcrumbPath.map( + (node): ChromeProjectBreadcrumb => ({ + href: node.deepLink?.url ?? node.href, + deepLinkId: node.deepLink?.id as AppDeepLinkId, + text: node.title, + }) + ); + + // if there are project breadcrumbs set, use them + if (projectBreadcrumbs.breadcrumbs.length !== 0) { + return [homeBreadcrumb, ...navBreadcrumbs, ...projectBreadcrumbs.breadcrumbs]; + } + + // otherwise try to merge legacy breadcrumbs with navigational project breadcrumbs using deeplinkid + let chromeBreadcrumbStartIndex = -1; + let navBreadcrumbEndIndex = -1; + navBreadcrumbsLoop: for (let i = navBreadcrumbs.length - 1; i >= 0; i--) { + if (!navBreadcrumbs[i].deepLinkId) continue; + for (let j = 0; j < chromeBreadcrumbs.length; j++) { + if (chromeBreadcrumbs[j].deepLinkId === navBreadcrumbs[i].deepLinkId) { + chromeBreadcrumbStartIndex = j; + navBreadcrumbEndIndex = i; + break navBreadcrumbsLoop; + } + } + } + + if (chromeBreadcrumbStartIndex === -1) { + return [homeBreadcrumb, ...navBreadcrumbs]; + } else { + return [ + homeBreadcrumb, + ...navBreadcrumbs.slice(0, navBreadcrumbEndIndex), + ...chromeBreadcrumbs.slice(chromeBreadcrumbStartIndex), + ]; + } +} diff --git a/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.test.ts b/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.test.ts index fd1fadff60512..52e27669e5515 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.test.ts +++ b/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.test.ts @@ -7,15 +7,17 @@ */ import { createMemoryHistory } from 'history'; -import { firstValueFrom, lastValueFrom, take } from 'rxjs'; +import { firstValueFrom, lastValueFrom, take, BehaviorSubject } from 'rxjs'; import { httpServiceMock } from '@kbn/core-http-browser-mocks'; import { applicationServiceMock } from '@kbn/core-application-browser-mocks'; -import type { ChromeNavLinks } from '@kbn/core-chrome-browser'; +import type { ChromeNavLinks, ChromeBreadcrumb, AppDeepLinkId } from '@kbn/core-chrome-browser'; import { ProjectNavigationService } from './project_navigation_service'; const setup = ({ locationPathName = '/' }: { locationPathName?: string } = {}) => { const projectNavigationService = new ProjectNavigationService(); const history = createMemoryHistory(); + const chromeBreadcrumbs$ = new BehaviorSubject([]); + history.replace(locationPathName); const projectNavigation = projectNavigationService.start({ application: { @@ -24,15 +26,18 @@ const setup = ({ locationPathName = '/' }: { locationPathName?: string } = {}) = }, navLinks: {} as unknown as ChromeNavLinks, http: httpServiceMock.createStartContract(), + chromeBreadcrumbs$, }); - return { projectNavigation, history }; + return { projectNavigation, history, chromeBreadcrumbs$ }; }; describe('breadcrumbs', () => { const setupWithNavTree = () => { const currentLocationPathName = '/foo/item1'; - const { projectNavigation, history } = setup({ locationPathName: currentLocationPathName }); + const { projectNavigation, chromeBreadcrumbs$, history } = setup({ + locationPathName: currentLocationPathName, + }); const mockNavigation = { navigationTree: [ @@ -66,7 +71,7 @@ describe('breadcrumbs', () => { ], }; projectNavigation.setProjectNavigation(mockNavigation); - return { projectNavigation, history, mockNavigation }; + return { projectNavigation, history, mockNavigation, chromeBreadcrumbs$ }; }; test('should set breadcrumbs home / nav / custom', async () => { @@ -89,7 +94,7 @@ describe('breadcrumbs', () => { "title": "Home", }, Object { - "data-test-subj": "breadcrumb-deepLinkId-navItem1", + "deepLinkId": "navItem1", "href": "/foo/item1", "text": "Nav Item 1", }, @@ -139,6 +144,38 @@ describe('breadcrumbs', () => { `); }); + test('should merge nav breadcrumbs and chrome breadcrumbs', async () => { + const { projectNavigation, chromeBreadcrumbs$ } = setupWithNavTree(); + + projectNavigation.setProjectBreadcrumbs([]); + chromeBreadcrumbs$.next([ + { text: 'Kibana' }, + { deepLinkId: 'navItem1' as AppDeepLinkId, text: 'Nav Item 1 from Chrome' }, + { text: 'Deep context from Chrome' }, + ]); + + const breadcrumbs = await firstValueFrom(projectNavigation.getProjectBreadcrumbs$()); + expect(breadcrumbs).toMatchInlineSnapshot(` + Array [ + Object { + "data-test-subj": "breadcrumb-home", + "href": "/", + "text": , + "title": "Home", + }, + Object { + "deepLinkId": "navItem1", + "text": "Nav Item 1 from Chrome", + }, + Object { + "text": "Deep context from Chrome", + }, + ] + `); + }); + test('should reset custom breadcrumbs when active path changes', async () => { const { projectNavigation, history } = setupWithNavTree(); projectNavigation.setProjectBreadcrumbs([ diff --git a/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.ts b/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.ts index 65d7fcc1bf559..9fb81c0c9a977 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.ts +++ b/packages/core/chrome/core-chrome-browser-internal/src/project_navigation/project_navigation_service.ts @@ -12,6 +12,7 @@ import { ChromeProjectNavigation, SideNavComponent, ChromeProjectBreadcrumb, + ChromeBreadcrumb, ChromeSetProjectBreadcrumbsParams, ChromeProjectNavigationNode, } from '@kbn/core-chrome-browser'; @@ -29,15 +30,15 @@ import { } from 'rxjs'; import type { Location } from 'history'; import deepEqual from 'react-fast-compare'; -import classnames from 'classnames'; -import { createHomeBreadcrumb } from './home_breadcrumbs'; import { findActiveNodes, flattenNav, stripQueryParams } from './utils'; +import { buildBreadcrumbs } from './breadcrumbs'; interface StartDeps { application: InternalApplicationStart; navLinks: ChromeNavLinks; http: HttpStart; + chromeBreadcrumbs$: Observable; } export class ProjectNavigationService { @@ -60,7 +61,7 @@ export class ProjectNavigationService { private http?: HttpStart; private unlistenHistory?: () => void; - public start({ application, navLinks, http }: StartDeps) { + public start({ application, navLinks, http, chromeBreadcrumbs$ }: StartDeps) { this.application = application; this.http = http; this.onHistoryLocationChange(application.history.location); @@ -136,33 +137,15 @@ export class ProjectNavigationService { this.projectBreadcrumbs$, this.activeNodes$, this.projectHome$.pipe(map((homeHref) => homeHref ?? '/')), + chromeBreadcrumbs$, ]).pipe( - map(([breadcrumbs, activeNodes, homeHref]) => { - const homeBreadcrumb = createHomeBreadcrumb({ + map(([projectBreadcrumbs, activeNodes, homeHref, chromeBreadcrumbs]) => { + return buildBreadcrumbs({ homeHref: this.http?.basePath.prepend?.(homeHref) ?? homeHref, + projectBreadcrumbs, + activeNodes, + chromeBreadcrumbs, }); - - if (breadcrumbs.params.absolute) { - return [homeBreadcrumb, ...breadcrumbs.breadcrumbs]; - } else { - // breadcrumbs take the first active path - const activePath: ChromeProjectNavigationNode[] = activeNodes[0] ?? []; - const navBreadcrumbs = activePath - .filter((n) => Boolean(n.title) && n.breadcrumbStatus !== 'hidden') - .map( - (node): ChromeProjectBreadcrumb => ({ - href: node.deepLink?.url ?? node.href, - text: node.title, - 'data-test-subj': classnames({ - [`breadcrumb-deepLinkId-${node.deepLink?.id}`]: !!node.deepLink, - }), - }) - ); - - const result = [homeBreadcrumb, ...navBreadcrumbs, ...breadcrumbs.breadcrumbs]; - - return result; - } }) ); }, diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header_breadcrumbs.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header_breadcrumbs.tsx index 6403bc62ad877..c7d250dea4f7a 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header_breadcrumbs.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/header/header_breadcrumbs.tsx @@ -27,13 +27,15 @@ export function HeaderBreadcrumbs({ breadcrumbs$ }: Props) { crumbs = crumbs.map((breadcrumb, i) => { const isLast = i === breadcrumbs.length - 1; + const { deepLinkId, ...rest } = breadcrumb; return { - ...breadcrumb, + ...rest, href: isLast ? undefined : breadcrumb.href, onClick: isLast ? undefined : breadcrumb.onClick, 'data-test-subj': classNames( 'breadcrumb', + deepLinkId && `breadcrumb-deepLinkId-${deepLinkId}`, breadcrumb['data-test-subj'], i === 0 && 'first', isLast && 'last' diff --git a/packages/core/chrome/core-chrome-browser/src/breadcrumb.ts b/packages/core/chrome/core-chrome-browser/src/breadcrumb.ts index 68a75b4db36af..2f68077e96e82 100644 --- a/packages/core/chrome/core-chrome-browser/src/breadcrumb.ts +++ b/packages/core/chrome/core-chrome-browser/src/breadcrumb.ts @@ -8,9 +8,16 @@ import type { EuiBreadcrumb } from '@elastic/eui'; import type { MountPoint } from '@kbn/core-mount-utils-browser'; +import type { AppDeepLinkId } from './project_navigation'; /** @public */ -export type ChromeBreadcrumb = EuiBreadcrumb; +export interface ChromeBreadcrumb extends EuiBreadcrumb { + /** + * The deepLinkId can be used to merge the navigational breadcrumbs set via project navigation + * with the deeper context breadcrumbs set via the `chrome.setBreadcrumbs` API. + */ + deepLinkId?: AppDeepLinkId; +} /** @public */ export interface ChromeBreadcrumbsAppendExtension { diff --git a/packages/default-nav/ml/default_navigation.ts b/packages/default-nav/ml/default_navigation.ts index 9d388d92a861e..f82ec721b22ee 100644 --- a/packages/default-nav/ml/default_navigation.ts +++ b/packages/default-nav/ml/default_navigation.ts @@ -33,11 +33,14 @@ export const defaultNavigation: MlNodeDefinition = { { link: 'ml:notifications', }, + { + link: 'ml:memoryUsage', + }, { title: i18n.translate('defaultNavigation.ml.anomalyDetection', { defaultMessage: 'Anomaly Detection', }), - id: 'anomaly_detection', + link: 'ml:anomalyDetection', renderAs: 'accordion', children: [ { @@ -45,6 +48,7 @@ export const defaultNavigation: MlNodeDefinition = { defaultMessage: 'Jobs', }), link: 'ml:anomalyDetection', + breadcrumbStatus: 'hidden', }, { link: 'ml:anomalyExplorer', @@ -58,7 +62,7 @@ export const defaultNavigation: MlNodeDefinition = { ], }, { - id: 'data_frame_analytics', + link: 'ml:dataFrameAnalytics', title: i18n.translate('defaultNavigation.ml.dataFrameAnalytics', { defaultMessage: 'Data Frame Analytics', }), @@ -67,6 +71,7 @@ export const defaultNavigation: MlNodeDefinition = { { title: 'Jobs', link: 'ml:dataFrameAnalytics', + breadcrumbStatus: 'hidden', }, { link: 'ml:resultExplorer', @@ -109,12 +114,21 @@ export const defaultNavigation: MlNodeDefinition = { defaultMessage: 'Data view', }), link: 'ml:indexDataVisualizer', + getIsActive: ({ pathNameSerialized, prepend }) => { + return ( + pathNameSerialized.includes(prepend('/app/ml/datavisualizer')) || + pathNameSerialized.includes(prepend('/app/ml/jobs/new_job/datavisualizer')) + ); + }, }, { title: i18n.translate('defaultNavigation.ml.dataComparison', { defaultMessage: 'Data drift', }), link: 'ml:dataDrift', + getIsActive: ({ pathNameSerialized, prepend }) => { + return pathNameSerialized.includes(prepend('/app/ml/data_drift')); + }, }, ], }, @@ -127,12 +141,21 @@ export const defaultNavigation: MlNodeDefinition = { children: [ { link: 'ml:logRateAnalysis', + getIsActive: ({ pathNameSerialized, prepend }) => { + return pathNameSerialized.includes(prepend('/app/ml/aiops/log_rate_analysis')); + }, }, { link: 'ml:logPatternAnalysis', + getIsActive: ({ pathNameSerialized, prepend }) => { + return pathNameSerialized.includes(prepend('/app/ml/aiops/log_categorization')); + }, }, { link: 'ml:changePointDetections', + getIsActive: ({ pathNameSerialized, prepend }) => { + return pathNameSerialized.includes(prepend('/app/ml/aiops/change_point_detection')); + }, }, ], }, diff --git a/packages/shared-ux/chrome/navigation/src/ui/__snapshots__/default_navigation.test.tsx.snap b/packages/shared-ux/chrome/navigation/src/ui/__snapshots__/default_navigation.test.tsx.snap index 251b07e75da5c..b4121ace0bf7f 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/__snapshots__/default_navigation.test.tsx.snap +++ b/packages/shared-ux/chrome/navigation/src/ui/__snapshots__/default_navigation.test.tsx.snap @@ -190,9 +190,30 @@ Array [ "sideNavStatus": "visible", "title": "Deeplink ml:notifications", }, + Object { + "children": undefined, + "deepLink": Object { + "baseUrl": "/mocked", + "href": "http://mocked/ml:memoryUsage", + "id": "ml:memoryUsage", + "title": "Deeplink ml:memoryUsage", + "url": "/mocked/ml:memoryUsage", + }, + "href": undefined, + "id": "ml:memoryUsage", + "isActive": false, + "isGroup": false, + "path": Array [ + "rootNav:ml", + "ml:memoryUsage", + ], + "sideNavStatus": "visible", + "title": "Deeplink ml:memoryUsage", + }, Object { "children": Array [ Object { + "breadcrumbStatus": "hidden", "children": undefined, "deepLink": Object { "baseUrl": "/mocked", @@ -207,7 +228,7 @@ Array [ "isGroup": false, "path": Array [ "rootNav:ml", - "anomaly_detection", + "ml:anomalyDetection", "ml:anomalyDetection", ], "sideNavStatus": "visible", @@ -228,7 +249,7 @@ Array [ "isGroup": false, "path": Array [ "rootNav:ml", - "anomaly_detection", + "ml:anomalyDetection", "ml:anomalyExplorer", ], "sideNavStatus": "visible", @@ -249,7 +270,7 @@ Array [ "isGroup": false, "path": Array [ "rootNav:ml", - "anomaly_detection", + "ml:anomalyDetection", "ml:singleMetricViewer", ], "sideNavStatus": "visible", @@ -270,21 +291,27 @@ Array [ "isGroup": false, "path": Array [ "rootNav:ml", - "anomaly_detection", + "ml:anomalyDetection", "ml:settings", ], "sideNavStatus": "visible", "title": "Deeplink ml:settings", }, ], - "deepLink": undefined, + "deepLink": Object { + "baseUrl": "/mocked", + "href": "http://mocked/ml:anomalyDetection", + "id": "ml:anomalyDetection", + "title": "Deeplink ml:anomalyDetection", + "url": "/mocked/ml:anomalyDetection", + }, "href": undefined, - "id": "anomaly_detection", + "id": "ml:anomalyDetection", "isActive": false, "isGroup": true, "path": Array [ "rootNav:ml", - "anomaly_detection", + "ml:anomalyDetection", ], "renderAs": "accordion", "sideNavStatus": "visible", @@ -293,6 +320,7 @@ Array [ Object { "children": Array [ Object { + "breadcrumbStatus": "hidden", "children": undefined, "deepLink": Object { "baseUrl": "/mocked", @@ -307,7 +335,7 @@ Array [ "isGroup": false, "path": Array [ "rootNav:ml", - "data_frame_analytics", + "ml:dataFrameAnalytics", "ml:dataFrameAnalytics", ], "sideNavStatus": "visible", @@ -328,7 +356,7 @@ Array [ "isGroup": false, "path": Array [ "rootNav:ml", - "data_frame_analytics", + "ml:dataFrameAnalytics", "ml:resultExplorer", ], "sideNavStatus": "visible", @@ -349,21 +377,27 @@ Array [ "isGroup": false, "path": Array [ "rootNav:ml", - "data_frame_analytics", + "ml:dataFrameAnalytics", "ml:analyticsMap", ], "sideNavStatus": "visible", "title": "Deeplink ml:analyticsMap", }, ], - "deepLink": undefined, + "deepLink": Object { + "baseUrl": "/mocked", + "href": "http://mocked/ml:dataFrameAnalytics", + "id": "ml:dataFrameAnalytics", + "title": "Deeplink ml:dataFrameAnalytics", + "url": "/mocked/ml:dataFrameAnalytics", + }, "href": undefined, - "id": "data_frame_analytics", + "id": "ml:dataFrameAnalytics", "isActive": false, "isGroup": true, "path": Array [ "rootNav:ml", - "data_frame_analytics", + "ml:dataFrameAnalytics", ], "renderAs": "accordion", "sideNavStatus": "visible", @@ -459,6 +493,7 @@ Array [ "title": "Deeplink ml:indexDataVisualizer", "url": "/mocked/ml:indexDataVisualizer", }, + "getIsActive": [Function], "href": undefined, "id": "ml:indexDataVisualizer", "isActive": false, @@ -480,6 +515,7 @@ Array [ "title": "Deeplink ml:dataDrift", "url": "/mocked/ml:dataDrift", }, + "getIsActive": [Function], "href": undefined, "id": "ml:dataDrift", "isActive": false, @@ -517,6 +553,7 @@ Array [ "title": "Deeplink ml:logRateAnalysis", "url": "/mocked/ml:logRateAnalysis", }, + "getIsActive": [Function], "href": undefined, "id": "ml:logRateAnalysis", "isActive": false, @@ -538,6 +575,7 @@ Array [ "title": "Deeplink ml:logPatternAnalysis", "url": "/mocked/ml:logPatternAnalysis", }, + "getIsActive": [Function], "href": undefined, "id": "ml:logPatternAnalysis", "isActive": false, @@ -559,6 +597,7 @@ Array [ "title": "Deeplink ml:changePointDetections", "url": "/mocked/ml:changePointDetections", }, + "getIsActive": [Function], "href": undefined, "id": "ml:changePointDetections", "isActive": false, diff --git a/packages/shared-ux/chrome/navigation/src/ui/components/navigation_section_ui.tsx b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_section_ui.tsx index af0f0632a94a7..947b642f95178 100644 --- a/packages/shared-ux/chrome/navigation/src/ui/components/navigation_section_ui.tsx +++ b/packages/shared-ux/chrome/navigation/src/ui/components/navigation_section_ui.tsx @@ -172,7 +172,7 @@ const nodeToEuiCollapsibleNavProps = ( spaceBefore: _spaceBefore, } = navNode; const isExternal = Boolean(href) && isAbsoluteLink(href!); - const isSelected = hasChildren ? false : isActive; + const isSelected = hasChildren && !isItem ? false : isActive; const dataTestSubj = classnames(`nav-item`, `nav-item-${id}`, { [`nav-item-deepLinkId-${deepLink?.id}`]: !!deepLink, [`nav-item-id-${id}`]: id, diff --git a/x-pack/plugins/ml/public/application/routing/breadcrumbs.ts b/x-pack/plugins/ml/public/application/routing/breadcrumbs.ts index bbb80e11fbe84..743964c552bfa 100644 --- a/x-pack/plugins/ml/public/application/routing/breadcrumbs.ts +++ b/x-pack/plugins/ml/public/application/routing/breadcrumbs.ts @@ -5,8 +5,6 @@ * 2.0. */ -import { EuiBreadcrumb } from '@elastic/eui'; - import { i18n } from '@kbn/i18n'; import { ChromeBreadcrumb } from '@kbn/core/public'; @@ -25,6 +23,7 @@ export const SETTINGS_BREADCRUMB: ChromeBreadcrumb = Object.freeze({ defaultMessage: 'Settings', }), href: '/settings', + deepLinkId: 'ml:settings', }); export const ANOMALY_DETECTION_BREADCRUMB: ChromeBreadcrumb = Object.freeze({ @@ -32,6 +31,7 @@ export const ANOMALY_DETECTION_BREADCRUMB: ChromeBreadcrumb = Object.freeze({ defaultMessage: 'Anomaly Detection', }), href: '/jobs', + deepLinkId: 'ml:anomalyDetection', }); export const DATA_FRAME_ANALYTICS_BREADCRUMB: ChromeBreadcrumb = Object.freeze({ @@ -39,6 +39,7 @@ export const DATA_FRAME_ANALYTICS_BREADCRUMB: ChromeBreadcrumb = Object.freeze({ defaultMessage: 'Data Frame Analytics', }), href: '/data_frame_analytics', + deepLinkId: 'ml:dataFrameAnalytics', }); export const TRAINED_MODELS: ChromeBreadcrumb = Object.freeze({ @@ -46,6 +47,7 @@ export const TRAINED_MODELS: ChromeBreadcrumb = Object.freeze({ defaultMessage: 'Model Management', }), href: '/trained_models', + deepLinkId: 'ml:modelManagement', }); export const DATA_VISUALIZER_BREADCRUMB: ChromeBreadcrumb = Object.freeze({ @@ -53,6 +55,7 @@ export const DATA_VISUALIZER_BREADCRUMB: ChromeBreadcrumb = Object.freeze({ defaultMessage: 'Data Visualizer', }), href: '/datavisualizer', + deepLinkId: 'ml:dataVisualizer', }); // we need multiple AIOPS_BREADCRUMB breadcrumb items as they each need to link @@ -83,6 +86,7 @@ export const LOG_RATE_ANALYSIS: ChromeBreadcrumb = Object.freeze({ defaultMessage: 'Log Rate Analysis', }), href: '/aiops/log_rate_analysis_index_select', + deepLinkId: 'ml:logRateAnalysis', }); export const LOG_PATTERN_ANALYSIS: ChromeBreadcrumb = Object.freeze({ @@ -90,6 +94,7 @@ export const LOG_PATTERN_ANALYSIS: ChromeBreadcrumb = Object.freeze({ defaultMessage: 'Log Pattern Analysis', }), href: '/aiops/log_categorization_index_select', + deepLinkId: 'ml:logPatternAnalysis', }); export const CHANGE_POINT_DETECTION: ChromeBreadcrumb = Object.freeze({ @@ -97,6 +102,7 @@ export const CHANGE_POINT_DETECTION: ChromeBreadcrumb = Object.freeze({ defaultMessage: 'Change Point Detection', }), href: '/aiops/change_point_detection_index_select', + deepLinkId: 'ml:changePointDetections', }); export const CREATE_JOB_BREADCRUMB: ChromeBreadcrumb = Object.freeze({ @@ -111,6 +117,7 @@ export const CALENDAR_MANAGEMENT_BREADCRUMB: ChromeBreadcrumb = Object.freeze({ defaultMessage: 'Calendar management', }), href: '/settings/calendars_list', + deepLinkId: 'ml:calendarSettings', }); export const FILTER_LISTS_BREADCRUMB: ChromeBreadcrumb = Object.freeze({ @@ -118,6 +125,7 @@ export const FILTER_LISTS_BREADCRUMB: ChromeBreadcrumb = Object.freeze({ defaultMessage: 'Filter lists', }), href: '/settings/filter_lists', + deepLinkId: 'ml:filterListsSettings', }); export const DATA_DRIFT_BREADCRUMB: ChromeBreadcrumb = Object.freeze({ @@ -125,6 +133,7 @@ export const DATA_DRIFT_BREADCRUMB: ChromeBreadcrumb = Object.freeze({ defaultMessage: 'Data drift', }), href: '/data_drift_index_select', + deepLinkId: 'ml:dataDrift', }); const breadcrumbs = { @@ -150,7 +159,7 @@ type Breadcrumb = keyof typeof breadcrumbs; export const breadcrumbOnClickFactory = ( path: string | undefined, navigateToPath: NavigateToPath -): EuiBreadcrumb['onClick'] => { +): ChromeBreadcrumb['onClick'] => { return (e) => { e.preventDefault(); navigateToPath(path); @@ -161,12 +170,13 @@ export const getBreadcrumbWithUrlForApp = ( breadcrumbName: Breadcrumb, navigateToPath?: NavigateToPath, basePath?: string -): EuiBreadcrumb => { +): ChromeBreadcrumb => { return { text: breadcrumbs[breadcrumbName].text, ...(navigateToPath ? { href: `${basePath}/app/ml${breadcrumbs[breadcrumbName].href}`, + deepLinkId: breadcrumbs[breadcrumbName].deepLinkId, onClick: breadcrumbOnClickFactory(breadcrumbs[breadcrumbName].href, navigateToPath), } : {}), diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/navigation_tree/chrome_navigation_tree.ts b/x-pack/plugins/security_solution_serverless/public/navigation/navigation_tree/chrome_navigation_tree.ts index 16a676e34dd90..0dc8a1140aaab 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/navigation_tree/chrome_navigation_tree.ts +++ b/x-pack/plugins/security_solution_serverless/public/navigation/navigation_tree/chrome_navigation_tree.ts @@ -81,6 +81,8 @@ export const getFormatChromeProjectNavNodes = (services: Services) => { id, title: node.title || '', path: [...path, id], + breadcrumbStatus: node.breadcrumbStatus, + getIsActive: node.getIsActive, }; if (chrome.navLinks.has(id)) { const deepLink = chrome.navLinks.get(id); diff --git a/x-pack/plugins/serverless_observability/public/components/side_navigation/index.tsx b/x-pack/plugins/serverless_observability/public/components/side_navigation/index.tsx index 8f02bc29dff30..07b76980804e0 100644 --- a/x-pack/plugins/serverless_observability/public/components/side_navigation/index.tsx +++ b/x-pack/plugins/serverless_observability/public/components/side_navigation/index.tsx @@ -90,6 +90,18 @@ const navigationTree: NavigationTreeDefinition = { defaultMessage: 'Anomaly detection', }), link: 'ml:anomalyDetection', + renderAs: 'item', + children: [ + { + link: 'ml:singleMetricViewer', + }, + { + link: 'ml:anomalyExplorer', + }, + { + link: 'ml:settings', + }, + ], }, { title: i18n.translate('xpack.serverlessObservability.ml.logRateAnalysis', { diff --git a/x-pack/test_serverless/functional/test_suites/observability/navigation.ts b/x-pack/test_serverless/functional/test_suites/observability/navigation.ts index aa0fa02f55a4e..012c7811c5a80 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/navigation.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/navigation.ts @@ -59,6 +59,15 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ deepLinkId: 'ml:anomalyDetection', }); + await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ + text: 'Jobs', + }); + await testSubjects.click('mlCreateNewJobButton'); + await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts([ + 'AIOps', + 'Anomaly Detection', + 'Create job', + ]); // navigate to a different section await svlCommonNavigation.sidenav.openSection('project_settings_project_nav'); From d06b98b88049dab43da12fb1a6b329e10b4d1833 Mon Sep 17 00:00:00 2001 From: Sergi Massaneda Date: Wed, 25 Oct 2023 11:59:43 +0200 Subject: [PATCH 07/24] [Security Solution] Management landing cards added (#169625) ## Summary issue: https://github.com/elastic/kibana/issues/167453 Adds the Security-specific project cards to the Management cards landing page: - Entity Risk Score - Maps - Visualize library ### Screenshot management_cards --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../cards_navigation/src/cards_navigation.tsx | 2 +- .../security-solution/navigation/index.ts | 7 ++- .../navigation/src/navigation.ts | 15 ++++++ .../public/common/icons/entity_analytics.tsx | 3 +- .../public/management/links.ts | 2 +- .../public/navigation/index.ts | 3 +- .../links/sections/project_settings_links.ts | 21 ++++++-- .../sections/project_settings_translations.ts | 17 ++++++- .../public/navigation/links/util.ts | 1 - .../public/navigation/management_cards.ts | 50 +++++++++++++++++++ .../tsconfig.json | 3 +- 11 files changed, 109 insertions(+), 15 deletions(-) create mode 100644 x-pack/plugins/security_solution_serverless/public/navigation/management_cards.ts diff --git a/packages/kbn-management/cards_navigation/src/cards_navigation.tsx b/packages/kbn-management/cards_navigation/src/cards_navigation.tsx index f9d5624a05447..303742dd25fdf 100644 --- a/packages/kbn-management/cards_navigation/src/cards_navigation.tsx +++ b/packages/kbn-management/cards_navigation/src/cards_navigation.tsx @@ -184,7 +184,7 @@ export const CardsNavigation = ({ } + icon={} titleSize="xs" title={app.title} description={app.description} diff --git a/x-pack/packages/security-solution/navigation/index.ts b/x-pack/packages/security-solution/navigation/index.ts index 6088006869153..d14d119612c04 100644 --- a/x-pack/packages/security-solution/navigation/index.ts +++ b/x-pack/packages/security-solution/navigation/index.ts @@ -5,7 +5,12 @@ * 2.0. */ -export { useGetAppUrl, useNavigateTo, useNavigation } from './src/navigation'; +export { + useGetAppUrl, + useNavigateTo, + useNavigation, + getNavigationPropsFromId, +} from './src/navigation'; export type { GetAppUrl, NavigateTo } from './src/navigation'; export { NavigationProvider } from './src/context'; export { SecurityPageName, LinkCategoryType } from './src/constants'; diff --git a/x-pack/packages/security-solution/navigation/src/navigation.ts b/x-pack/packages/security-solution/navigation/src/navigation.ts index 7474baf1fd3ba..6c18d55d5745f 100644 --- a/x-pack/packages/security-solution/navigation/src/navigation.ts +++ b/x-pack/packages/security-solution/navigation/src/navigation.ts @@ -9,6 +9,7 @@ import type { NavigateToAppOptions } from '@kbn/core/public'; import { useCallback } from 'react'; import { SECURITY_UI_APP_ID } from './constants'; import { useNavigationContext } from './context'; +import { getAppIdsFromId } from './links'; export type GetAppUrl = (param: { appId?: string; @@ -82,3 +83,17 @@ export const useNavigation = () => { const { getAppUrl } = useGetAppUrl(); return { navigateTo, getAppUrl }; }; + +/** + * Returns the appId, deepLinkId, and path from a given navigation id + */ +export const getNavigationPropsFromId = ( + id: string +): { + appId: string; + deepLinkId?: string; + path?: string; +} => { + const { appId = SECURITY_UI_APP_ID, ...options } = getAppIdsFromId(id); + return { appId, ...options }; +}; diff --git a/x-pack/plugins/security_solution/public/common/icons/entity_analytics.tsx b/x-pack/plugins/security_solution/public/common/icons/entity_analytics.tsx index e282f07c4dcdb..a9e4123fe4d41 100644 --- a/x-pack/plugins/security_solution/public/common/icons/entity_analytics.tsx +++ b/x-pack/plugins/security_solution/public/common/icons/entity_analytics.tsx @@ -20,13 +20,12 @@ export const IconEntityAnalytics: React.FC> = ({ ...prop fillRule="evenodd" clipRule="evenodd" d="M25.332 7C25.332 8.10457 26.2275 9 27.332 9C28.4366 9 29.332 8.10457 29.332 7C29.332 5.89543 28.4366 5 27.332 5C26.2275 5 25.332 5.89543 25.332 7ZM23.332 7C23.332 7.37644 23.384 7.74073 23.4812 8.08609L17.6976 11.1707C15.9888 8.65367 13.1035 7 9.83203 7C4.58533 7 0.332031 11.2533 0.332031 16.5C0.332031 21.7467 4.58533 26 9.83203 26C12.6903 26 15.2537 24.7377 16.9952 22.7403L23.387 26.3356C23.3508 26.5517 23.332 26.7737 23.332 27C23.332 29.2091 25.1229 31 27.332 31C29.5412 31 31.332 29.2091 31.332 27C31.332 24.7909 29.5412 23 27.332 23C26.0677 23 24.9404 23.5866 24.2074 24.5024L18.1491 21.0946C18.672 20.15 19.0387 19.1068 19.2143 18H24.4581C24.9021 19.7252 26.4682 21 28.332 21C30.5412 21 32.332 19.2091 32.332 17C32.332 14.7909 30.5412 13 28.332 13C26.4682 13 24.9021 14.2748 24.458 16H19.3191C19.2631 14.9207 19.027 13.8891 18.6403 12.9346L24.49 9.81475C25.2149 10.5466 26.2205 11 27.332 11C29.5412 11 31.332 9.20914 31.332 7C31.332 4.79086 29.5412 3 27.332 3C25.1229 3 23.332 4.79086 23.332 7ZM28.332 19C27.2275 19 26.332 18.1046 26.332 17C26.332 15.8954 27.2275 15 28.332 15C29.4366 15 30.332 15.8954 30.332 17C30.332 18.1046 29.4366 19 28.332 19ZM25.332 27C25.332 28.1046 26.2275 29 27.332 29C28.4366 29 29.332 28.1046 29.332 27C29.332 25.8954 28.4366 25 27.332 25C26.2275 25 25.332 25.8954 25.332 27ZM9.83203 24C5.68989 24 2.33203 20.6421 2.33203 16.5C2.33203 12.3579 5.6899 9 9.83203 9C13.9742 9 17.332 12.3579 17.332 16.5C17.332 20.6421 13.9742 24 9.83203 24Z" - fill="#343741" + className="euiIcon__fillSecondary" /> diff --git a/x-pack/plugins/security_solution/public/management/links.ts b/x-pack/plugins/security_solution/public/management/links.ts index 60c4c93d43fa6..919291c482996 100644 --- a/x-pack/plugins/security_solution/public/management/links.ts +++ b/x-pack/plugins/security_solution/public/management/links.ts @@ -170,7 +170,7 @@ export const links: LinkItem = { id: SecurityPageName.entityAnalyticsManagement, title: ENTITY_ANALYTICS_RISK_SCORE, description: i18n.translate('xpack.securitySolution.appLinks.entityRiskScoringDescription', { - defaultMessage: 'Manage entity risk scoring and detect insider threats.', + defaultMessage: 'Monitor user and host risk scores, and track anomalies.', }), landingIcon: IconEntityAnalytics, path: ENTITY_ANALYTICS_MANAGEMENT_PATH, diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/index.ts b/x-pack/plugins/security_solution_serverless/public/navigation/index.ts index 0dd14ebacd544..e88261f165413 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/index.ts +++ b/x-pack/plugins/security_solution_serverless/public/navigation/index.ts @@ -16,6 +16,7 @@ import { getSecuritySideNavComponent } from './side_navigation'; import { SecuritySideNavComponent } from './project_navigation'; import { projectAppLinksSwitcher } from './links/app_links'; import { formatProjectDeepLinks } from './links/deep_links'; +import { enableManagementCardsLanding } from './management_cards'; export const setupNavigation = ( _core: CoreSetup, @@ -29,7 +30,7 @@ export const startNavigation = (services: Services) => { const { serverless, management } = services; serverless.setProjectHome(APP_PATH); - management.setupCardsNavigation({ enabled: true }); + enableManagementCardsLanding(services); const projectNavigationTree = new ProjectNavigationTree(services); diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/project_settings_links.ts b/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/project_settings_links.ts index f2deadd1fdf25..5e94100e3fee1 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/project_settings_links.ts +++ b/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/project_settings_links.ts @@ -15,7 +15,14 @@ export const createProjectSettingsLinksFromManage = (manageLink: LinkItem): Link const entityAnalyticsLink = manageLink.links?.find( ({ id }) => id === SecurityPageName.entityAnalyticsManagement ); - return entityAnalyticsLink ? [{ ...entityAnalyticsLink, sideNavDisabled: true }] : []; + return entityAnalyticsLink + ? [ + { + ...entityAnalyticsLink, + sideNavDisabled: true, // Link disabled from the side nav but configured in the navigationTree (breadcrumbs). It is displayed in the management cards landing. + }, + ] + : []; }; export const projectSettingsNavLinks: ProjectNavigationLink[] = [ @@ -37,12 +44,16 @@ export const projectSettingsNavLinks: ProjectNavigationLink[] = [ }, { id: ExternalPageName.maps, - title: i18n.CLOUD_MAPS_TITLE, - disabled: true, // the link will be available in the navigationTree (breadcrumbs) but not appear in the sideNav + title: i18n.MAPS_TITLE, + description: i18n.MAPS_DESCRIPTION, + landingIcon: 'graphApp', + disabled: true, // Link disabled from the side nav but configured in the navigationTree (breadcrumbs). It is displayed in the management cards landing. }, { id: ExternalPageName.visualize, - title: i18n.CLOUD_VISUALIZE_TITLE, - disabled: true, // the link will be available in the navigationTree (breadcrumbs) but not appear in the sideNav + title: i18n.VISUALIZE_TITLE, + description: i18n.VISUALIZE_DESCRIPTION, + landingIcon: 'visualizeApp', + disabled: true, // Link disabled from the side nav but configured in the navigationTree (breadcrumbs). It is displayed in the management cards landing. }, ]; diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/project_settings_translations.ts b/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/project_settings_translations.ts index 50b8086bfb0d9..dec726efea277 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/project_settings_translations.ts +++ b/x-pack/plugins/security_solution_serverless/public/navigation/links/sections/project_settings_translations.ts @@ -38,15 +38,28 @@ export const CLOUD_BILLING_TITLE = i18n.translate( } ); -export const CLOUD_MAPS_TITLE = i18n.translate( +export const MAPS_TITLE = i18n.translate( 'xpack.securitySolutionServerless.navLinks.projectSettings.maps.title', { defaultMessage: 'Maps', } ); -export const CLOUD_VISUALIZE_TITLE = i18n.translate( +export const MAPS_DESCRIPTION = i18n.translate( + 'xpack.securitySolutionServerless.navLinks.projectSettings.maps.description', + { + defaultMessage: + 'Analyze geospatial data and identify geo patterns in multiple layers and indices.', + } +); +export const VISUALIZE_TITLE = i18n.translate( 'xpack.securitySolutionServerless.navLinks.projectSettings.visualize.title', { defaultMessage: 'Visualize library', } ); +export const VISUALIZE_DESCRIPTION = i18n.translate( + 'xpack.securitySolutionServerless.navLinks.projectSettings.visualize.description', + { + defaultMessage: 'Manage visualization library. Create, edit, and share visualizations.', + } +); diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/links/util.ts b/x-pack/plugins/security_solution_serverless/public/navigation/links/util.ts index edaf40529e16d..e77e860e93538 100644 --- a/x-pack/plugins/security_solution_serverless/public/navigation/links/util.ts +++ b/x-pack/plugins/security_solution_serverless/public/navigation/links/util.ts @@ -54,5 +54,4 @@ export const isBottomNavItemId = (id: string) => id === ExternalPageName.management || id === ExternalPageName.integrationsSecurity || id === ExternalPageName.cloudUsersAndRoles || - id === ExternalPageName.cloudPerformance || id === ExternalPageName.cloudBilling; diff --git a/x-pack/plugins/security_solution_serverless/public/navigation/management_cards.ts b/x-pack/plugins/security_solution_serverless/public/navigation/management_cards.ts new file mode 100644 index 0000000000000..79587f8f0843b --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/navigation/management_cards.ts @@ -0,0 +1,50 @@ +/* + * 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 { + AppDefinition, + CardNavExtensionDefinition, +} from '@kbn/management-cards-navigation/src/types'; +import { getNavigationPropsFromId, SecurityPageName } from '@kbn/security-solution-navigation'; +import type { Services } from '../common/services'; +import { ExternalPageName } from './links/constants'; +import type { ProjectPageName } from './links/types'; + +const SecurityManagementCards = new Map([ + [ExternalPageName.visualize, 'content'], + [ExternalPageName.maps, 'content'], + [SecurityPageName.entityAnalyticsManagement, 'alerts'], +]); + +export const enableManagementCardsLanding = (services: Services) => { + const { management, application } = services; + + services.getProjectNavLinks$().subscribe((projectNavLinks) => { + const extendCardNavDefinitions = projectNavLinks.reduce< + Record + >((acc, projectNavLink) => { + if (SecurityManagementCards.has(projectNavLink.id)) { + const { appId, deepLinkId, path } = getNavigationPropsFromId(projectNavLink.id); + + acc[projectNavLink.id] = { + category: SecurityManagementCards.get(projectNavLink.id) ?? 'other', + title: projectNavLink.title, + description: projectNavLink.description ?? '', + icon: projectNavLink.landingIcon ?? '', + href: application.getUrlForApp(appId, { deepLinkId, path }), + skipValidation: true, + }; + } + return acc; + }, {}); + + management.setupCardsNavigation({ + enabled: true, + extendCardNavDefinitions, + }); + }); +}; diff --git a/x-pack/plugins/security_solution_serverless/tsconfig.json b/x-pack/plugins/security_solution_serverless/tsconfig.json index 154284ca9fab8..a626590c08e1a 100644 --- a/x-pack/plugins/security_solution_serverless/tsconfig.json +++ b/x-pack/plugins/security_solution_serverless/tsconfig.json @@ -45,6 +45,7 @@ "@kbn/core-logging-server-mocks", "@kbn/shared-ux-chrome-navigation", "@kbn/stack-connectors-plugin", - "@kbn/actions-plugin" + "@kbn/actions-plugin", + "@kbn/management-cards-navigation" ] } From bccfaafa7c4db85573f11e8bff4433b66a67a6f9 Mon Sep 17 00:00:00 2001 From: Ignacio Rivas Date: Wed, 25 Oct 2023 12:00:57 +0200 Subject: [PATCH 08/24] [Snapshot Restore] Remove duped version in snapshot details flyout (#169615) --- .../__jest__/client_integration/home.test.ts | 6 ++---- .../snapshot_list/snapshot_details/tabs/tab_summary.tsx | 5 ++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts index 3e38602b4be89..efbf38bccbf94 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/home.test.ts @@ -755,13 +755,11 @@ describe('', () => { describe('summary tab', () => { test('should set the correct summary values', () => { - const { version, versionId, uuid, indices } = snapshot1; + const { version, uuid, indices } = snapshot1; const { find } = testBed; - expect(find('snapshotDetail.version.value').text()).toBe( - `${version} / ${versionId}` - ); + expect(find('snapshotDetail.version.value').text()).toBe(version); expect(find('snapshotDetail.uuid.value').text()).toBe(uuid); expect(find('snapshotDetail.state.value').text()).toBe('Snapshot complete'); expect(find('snapshotDetail.includeGlobalState.value').text()).toEqual('Yes'); diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_details/tabs/tab_summary.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_details/tabs/tab_summary.tsx index 47313aaeea626..00ea3fa27109b 100644 --- a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_details/tabs/tab_summary.tsx +++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_details/tabs/tab_summary.tsx @@ -37,7 +37,6 @@ interface Props { export const TabSummary: React.FC = ({ snapshotDetails }) => { const { - versionId, version, // TODO: Add a tooltip explaining that: a false value means that the cluster global state // is not stored as part of the snapshot. @@ -62,12 +61,12 @@ export const TabSummary: React.FC = ({ snapshotDetails }) => { - {version} / {versionId} + {version} From 6f334cd51015c1e4ff85d69f2fa83ee23f7902e3 Mon Sep 17 00:00:00 2001 From: Miriam <31922082+MiriamAparicio@users.noreply.github.com> Date: Wed, 25 Oct 2023 11:03:02 +0100 Subject: [PATCH 09/24] =?UTF-8?q?[ObsUX]=20Change=20link=20from=20instance?= =?UTF-8?q?s=20table=20of=20java=20agent=20to=20open=20new=20Metrics=20pa?= =?UTF-8?q?=E2=80=A6=20(#169672)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes https://github.com/elastic/kibana/issues/169085 BEFORE https://github.com/elastic/kibana/assets/31922082/da089db4-9164-4eb6-8542-03ec8233cea6 AFTER https://github.com/elastic/kibana/assets/31922082/544ed491-1b69-4fa7-8053-0f711d9ecdb2 --- .../service_overview_instances_table/get_columns.tsx | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/get_columns.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/get_columns.tsx index d7cfc9aa0f76c..ea1cc64e9fff9 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/get_columns.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_table/get_columns.tsx @@ -14,7 +14,6 @@ import { i18n } from '@kbn/i18n'; import React, { ReactNode } from 'react'; import { ActionMenu } from '@kbn/observability-shared-plugin/public'; import { isTimeComparison } from '../../../shared/time_comparison/get_comparison_options'; -import { isJavaAgentName } from '../../../../../common/agent_name'; import { LatencyAggregationType } from '../../../../../common/latency_aggregation_types'; import { getServiceNodeName, @@ -27,7 +26,6 @@ import { } from '../../../../../common/utils/formatters'; import { APIReturnType } from '../../../../services/rest/create_call_apm_api'; import { MetricOverviewLink } from '../../../shared/links/apm/metric_overview_link'; -import { ServiceNodeMetricOverviewLink } from '../../../shared/links/apm/service_node_metric_overview_link'; import { ListMetric } from '../../../shared/list_metric'; import { getLatencyColumnLabel } from '../../../shared/transactions_table/get_latency_column_label'; import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; @@ -87,14 +85,7 @@ export function getColumns({ serviceNodeName === SERVICE_NODE_NAME_MISSING; const text = getServiceNodeName(serviceNodeName); - const link = isJavaAgentName(agentName) ? ( - - {text} - - ) : ( + const link = ( ({ From 9a9a51b4545793a9a13da35e2b749f89be5e2914 Mon Sep 17 00:00:00 2001 From: Vadim Kibana <82822460+vadimkibana@users.noreply.github.com> Date: Wed, 25 Oct 2023 12:07:55 +0200 Subject: [PATCH 10/24] Remove unused sharedux avatar components (#168686) ## Summary Closes https://github.com/elastic/kibana/issues/168689 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .github/CODEOWNERS | 1 - package.json | 1 - .../avatar/user_profile/impl/README.mdx | 12 - .../avatar/user_profile/impl/index.ts | 15 - .../avatar/user_profile/impl/jest.config.js | 13 - .../avatar/user_profile/impl/kibana.jsonc | 5 - .../avatar/user_profile/impl/package.json | 6 - .../avatar/user_profile/impl/tsconfig.json | 25 -- .../user_profile/impl/user_avatar.test.tsx | 94 ------ .../avatar/user_profile/impl/user_avatar.tsx | 79 ----- .../avatar/user_profile/impl/user_profile.ts | 137 -------- .../impl/user_profiles.stories.tsx | 60 ---- .../impl/user_profiles_popover.test.tsx | 125 ------- .../impl/user_profiles_popover.tsx | 48 --- .../impl/user_profiles_selectable.test.tsx | 203 ----------- .../impl/user_profiles_selectable.tsx | 319 ------------------ .../shared-ux/page/no_data/impl/tsconfig.json | 2 +- src/plugins/kibana_overview/tsconfig.json | 2 +- tsconfig.base.json | 2 - x-pack/plugins/spaces/tsconfig.json | 2 +- .../translations/translations/fr-FR.json | 4 - .../translations/translations/ja-JP.json | 4 - .../translations/translations/zh-CN.json | 4 - yarn.lock | 4 - 24 files changed, 3 insertions(+), 1164 deletions(-) delete mode 100644 packages/shared-ux/avatar/user_profile/impl/README.mdx delete mode 100644 packages/shared-ux/avatar/user_profile/impl/index.ts delete mode 100644 packages/shared-ux/avatar/user_profile/impl/jest.config.js delete mode 100644 packages/shared-ux/avatar/user_profile/impl/kibana.jsonc delete mode 100644 packages/shared-ux/avatar/user_profile/impl/package.json delete mode 100644 packages/shared-ux/avatar/user_profile/impl/tsconfig.json delete mode 100644 packages/shared-ux/avatar/user_profile/impl/user_avatar.test.tsx delete mode 100644 packages/shared-ux/avatar/user_profile/impl/user_avatar.tsx delete mode 100644 packages/shared-ux/avatar/user_profile/impl/user_profile.ts delete mode 100644 packages/shared-ux/avatar/user_profile/impl/user_profiles.stories.tsx delete mode 100644 packages/shared-ux/avatar/user_profile/impl/user_profiles_popover.test.tsx delete mode 100644 packages/shared-ux/avatar/user_profile/impl/user_profiles_popover.tsx delete mode 100644 packages/shared-ux/avatar/user_profile/impl/user_profiles_selectable.test.tsx delete mode 100644 packages/shared-ux/avatar/user_profile/impl/user_profiles_selectable.tsx diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 983754e6dd8db..9751dc527b95c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -676,7 +676,6 @@ examples/share_examples @elastic/appex-sharedux src/plugins/share @elastic/appex-sharedux packages/kbn-shared-svg @elastic/apm-ui packages/shared-ux/avatar/solution @elastic/appex-sharedux -packages/shared-ux/avatar/user_profile/impl @elastic/appex-sharedux packages/shared-ux/button/exit_full_screen/impl @elastic/appex-sharedux packages/shared-ux/button/exit_full_screen/mocks @elastic/appex-sharedux packages/shared-ux/button/exit_full_screen/types @elastic/appex-sharedux diff --git a/package.json b/package.json index 9291646fac18a..e5213d00d20d4 100644 --- a/package.json +++ b/package.json @@ -679,7 +679,6 @@ "@kbn/share-plugin": "link:src/plugins/share", "@kbn/shared-svg": "link:packages/kbn-shared-svg", "@kbn/shared-ux-avatar-solution": "link:packages/shared-ux/avatar/solution", - "@kbn/shared-ux-avatar-user-profile-components": "link:packages/shared-ux/avatar/user_profile/impl", "@kbn/shared-ux-button-exit-full-screen": "link:packages/shared-ux/button/exit_full_screen/impl", "@kbn/shared-ux-button-exit-full-screen-mocks": "link:packages/shared-ux/button/exit_full_screen/mocks", "@kbn/shared-ux-button-exit-full-screen-types": "link:packages/shared-ux/button/exit_full_screen/types", diff --git a/packages/shared-ux/avatar/user_profile/impl/README.mdx b/packages/shared-ux/avatar/user_profile/impl/README.mdx deleted file mode 100644 index 1522cec340b9b..0000000000000 --- a/packages/shared-ux/avatar/user_profile/impl/README.mdx +++ /dev/null @@ -1,12 +0,0 @@ ---- -id: sharedUX/Components/UserProfileAvatar -slug: /shared-ux/components/user-profile-avatar -title: User Profile Avatar -description: A wrapper around `EuiAvatar` -tags: ['shared-ux', 'component'] -date: 2022-09-01 ---- - -## Description - -A wrapper around `EuiAvatar` tailored for user profiles diff --git a/packages/shared-ux/avatar/user_profile/impl/index.ts b/packages/shared-ux/avatar/user_profile/impl/index.ts deleted file mode 100644 index e36215e36896a..0000000000000 --- a/packages/shared-ux/avatar/user_profile/impl/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export type { UserAvatarProps, UserProfileWithAvatar } from './user_avatar'; -export type { UserProfilesSelectableProps } from './user_profiles_selectable'; -export type { UserProfilesPopoverProps } from './user_profiles_popover'; -export { UserAvatar } from './user_avatar'; -export { UserProfilesSelectable } from './user_profiles_selectable'; -export { UserProfilesPopover } from './user_profiles_popover'; -export type { UserProfile, UserProfileUserInfo, UserProfileAvatarData } from './user_profile'; diff --git a/packages/shared-ux/avatar/user_profile/impl/jest.config.js b/packages/shared-ux/avatar/user_profile/impl/jest.config.js deleted file mode 100644 index 111a2a8105057..0000000000000 --- a/packages/shared-ux/avatar/user_profile/impl/jest.config.js +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -module.exports = { - preset: '@kbn/test', - rootDir: '../../../../..', - roots: ['/packages/shared-ux/avatar/user_profile/impl'], -}; diff --git a/packages/shared-ux/avatar/user_profile/impl/kibana.jsonc b/packages/shared-ux/avatar/user_profile/impl/kibana.jsonc deleted file mode 100644 index f5105c929224b..0000000000000 --- a/packages/shared-ux/avatar/user_profile/impl/kibana.jsonc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "type": "shared-common", - "id": "@kbn/shared-ux-avatar-user-profile-components", - "owner": "@elastic/appex-sharedux" -} diff --git a/packages/shared-ux/avatar/user_profile/impl/package.json b/packages/shared-ux/avatar/user_profile/impl/package.json deleted file mode 100644 index 6af2682d533bf..0000000000000 --- a/packages/shared-ux/avatar/user_profile/impl/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "@kbn/shared-ux-avatar-user-profile-components", - "private": true, - "version": "1.0.0", - "license": "SSPL-1.0 OR Elastic License 2.0" -} \ No newline at end of file diff --git a/packages/shared-ux/avatar/user_profile/impl/tsconfig.json b/packages/shared-ux/avatar/user_profile/impl/tsconfig.json deleted file mode 100644 index 833908e045033..0000000000000 --- a/packages/shared-ux/avatar/user_profile/impl/tsconfig.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "extends": "../../../../../tsconfig.base.json", - "compilerOptions": { - "outDir": "target/types", - "types": [ - "jest", - "node", - "react", - "@kbn/ambient-ui-types" - ] - }, - "include": [ - "*ts*", - "*.md*", - "**/*.ts", - "**/*.md*", - ], - "kbn_references": [ - "@kbn/i18n-react", - "@kbn/i18n", - ], - "exclude": [ - "target/**/*", - ] -} diff --git a/packages/shared-ux/avatar/user_profile/impl/user_avatar.test.tsx b/packages/shared-ux/avatar/user_profile/impl/user_avatar.test.tsx deleted file mode 100644 index 6a62d14c75642..0000000000000 --- a/packages/shared-ux/avatar/user_profile/impl/user_avatar.test.tsx +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { shallow } from 'enzyme'; -import React from 'react'; - -import { UserAvatar } from './user_avatar'; - -describe('UserAvatar', () => { - it('should render `EuiAvatar` correctly with image avatar', () => { - const wrapper = shallow( - - ); - expect(wrapper).toMatchInlineSnapshot(` - - `); - }); - - it('should render `EuiAvatar` correctly with initials avatar', () => { - const wrapper = shallow( - - ); - expect(wrapper).toMatchInlineSnapshot(` - - `); - }); - - it('should render `EuiAvatar` correctly without avatar data', () => { - const wrapper = shallow( - - ); - expect(wrapper).toMatchInlineSnapshot(` - - `); - }); - - it('should render `EuiAvatar` correctly without user data', () => { - const wrapper = shallow(); - expect(wrapper).toMatchInlineSnapshot(` - - `); - }); -}); diff --git a/packages/shared-ux/avatar/user_profile/impl/user_avatar.tsx b/packages/shared-ux/avatar/user_profile/impl/user_avatar.tsx deleted file mode 100644 index 2413694317c27..0000000000000 --- a/packages/shared-ux/avatar/user_profile/impl/user_avatar.tsx +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { EuiAvatarProps } from '@elastic/eui'; -import { EuiAvatar, useEuiTheme } from '@elastic/eui'; -import type { FunctionComponent } from 'react'; -import React from 'react'; - -import type { UserProfile, UserProfileUserInfo, UserProfileAvatarData } from './user_profile'; -import { - getUserAvatarColor, - getUserAvatarInitials, - getUserDisplayName, - USER_AVATAR_MAX_INITIALS, -} from './user_profile'; - -/** - * Convenience type for a {@link UserProfile} with avatar data - */ -export type UserProfileWithAvatar = UserProfile<{ avatar?: UserProfileAvatarData }>; - -/** - * Props of {@link UserAvatar} component - */ -export interface UserAvatarProps - extends Omit< - EuiAvatarProps, - | 'initials' - | 'initialsLength' - | 'imageUrl' - | 'iconType' - | 'iconSize' - | 'iconColor' - | 'name' - | 'color' - | 'type' - > { - /** - * User to be rendered - */ - user?: UserProfileUserInfo; - - /** - * Avatar data of user to be rendered - */ - avatar?: UserProfileAvatarData; -} - -/** - * Renders an avatar given a user profile - */ -export const UserAvatar: FunctionComponent = ({ user, avatar, ...rest }) => { - const { euiTheme } = useEuiTheme(); - - if (!user) { - return ; - } - - const displayName = getUserDisplayName(user); - - if (avatar?.imageUrl) { - return ; - } - - return ( - - ); -}; diff --git a/packages/shared-ux/avatar/user_profile/impl/user_profile.ts b/packages/shared-ux/avatar/user_profile/impl/user_profile.ts deleted file mode 100644 index a278e2bc61a9c..0000000000000 --- a/packages/shared-ux/avatar/user_profile/impl/user_profile.ts +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { VISUALIZATION_COLORS } from '@elastic/eui'; - -/** - * IMPORTANT: - * - * The types in this file have been imported from - * `x-pack/plugins/security/common/model/user_profile.ts` - * - * When making changes please ensure to keep both files in sync. - */ - -/** - * Describes basic properties stored in user profile. - */ -export interface UserProfile { - /** - * Unique ID for of the user profile. - */ - uid: string; - - /** - * Indicates whether user profile is enabled or not. - */ - enabled: boolean; - - /** - * Information about the user that owns profile. - */ - user: UserProfileUserInfo; - - /** - * User specific data associated with the profile. - */ - data: Partial; -} - -/** - * Basic user information returned in user profile. - */ -export interface UserProfileUserInfo { - /** - * Username of the user. - */ - username: string; - /** - * Optional email of the user. - */ - email?: string; - /** - * Optional full name of the user. - */ - fullName?: string; - /** - * Optional display name of the user. - */ - displayName?: string; -} - -/** - * Placeholder for data stored in user profile. - */ -export type UserProfileData = Record; - -/** - * Avatar stored in user profile. - */ -export interface UserProfileAvatarData { - /** - * Optional initials (two letters) of the user to use as avatar if avatar picture isn't specified. - */ - initials?: string; - /** - * Background color of the avatar when initials are used. - */ - color?: string; - /** - * Base64 data URL for the user avatar image. - */ - imageUrl?: string | null; -} - -export const USER_AVATAR_FALLBACK_CODE_POINT = 97; // code point for lowercase "a" -export const USER_AVATAR_MAX_INITIALS = 2; - -/** - * Determines the color for the provided user profile. - * If a color is present on the user profile itself, then that is used. - * Otherwise, a color is provided from EUI's Visualization Colors based on the display name. - * - * @param {UserProfileUserInfo} user User info - * @param {UserProfileAvatarData} avatar User avatar - */ -export function getUserAvatarColor( - user: Pick, - avatar?: UserProfileAvatarData -) { - const firstCodePoint = getUserDisplayName(user).codePointAt(0) || USER_AVATAR_FALLBACK_CODE_POINT; - - return avatar?.color ?? VISUALIZATION_COLORS[firstCodePoint % VISUALIZATION_COLORS.length]; -} - -/** - * Determines the initials for the provided user profile. - * If initials are present on the user profile itself, then that is used. - * Otherwise, the initials are calculated based off the words in the display name, with a max length of 2 characters. - * - * @param {UserProfileUserInfo} user User info - * @param {UserProfileAvatarData} avatar User avatar - */ -export function getUserAvatarInitials( - user: Pick, - avatar?: UserProfileAvatarData -) { - const words = getUserDisplayName(user).split(' '); - const numInitials = Math.min(USER_AVATAR_MAX_INITIALS, words.length); - - words.splice(numInitials, words.length); - - return avatar?.initials ?? words.map((word) => word.substring(0, 1)).join(''); -} - -/** - * Determines the display name for the provided user profile. - * - * @param {UserProfileUserInfo} user User info - */ -export function getUserDisplayName(user: Pick) { - return user.fullName || user.username; -} diff --git a/packages/shared-ux/avatar/user_profile/impl/user_profiles.stories.tsx b/packages/shared-ux/avatar/user_profile/impl/user_profiles.stories.tsx deleted file mode 100644 index e7a7fa719f2e8..0000000000000 --- a/packages/shared-ux/avatar/user_profile/impl/user_profiles.stories.tsx +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { UserAvatar, UserAvatarProps } from './user_avatar'; -import mdx from './README.mdx'; -import { UserProfileUserInfo } from './user_profile'; - -export default { - title: 'Avatar/User Profile', - description: '', - parameters: { - docs: { - page: mdx, - }, - }, -}; - -type UserAvatarParams = Pick; -const sampleUsers = [ - { - username: 'Peggy', - email: 'test@email.com', - fullName: 'Peggy Simms', - displayName: 'Peggy', - }, - { - username: 'Martin', - email: 'test@email.com', - fullName: 'Martin Gatsby', - displayName: 'Martin', - }, - { - username: 'Leonardo DiCaprio', - email: 'test@email.com', - fullName: 'Leonardo DiCaprio', - displayName: 'Leonardo DiCaprio', - }, -]; - -export const userAvatar = ( - params: Pick, - rest: UserAvatarParams -) => { - const username = params; - return ; -}; - -userAvatar.argTypes = { - username: { - control: { type: 'radio' }, - options: sampleUsers.map(({ username }) => username), - defaultValue: sampleUsers.map(({ username }) => username)[0], - }, -}; diff --git a/packages/shared-ux/avatar/user_profile/impl/user_profiles_popover.test.tsx b/packages/shared-ux/avatar/user_profile/impl/user_profiles_popover.test.tsx deleted file mode 100644 index 9412904c8b5a4..0000000000000 --- a/packages/shared-ux/avatar/user_profile/impl/user_profiles_popover.test.tsx +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { shallow } from 'enzyme'; -import React from 'react'; - -import { UserProfilesPopover } from './user_profiles_popover'; - -const userProfiles = [ - { - uid: 'u_BOulL4QMPSyV9jg5lQI2JmCkUnokHTazBnet3xVHNv0_0', - enabled: true, - data: {}, - user: { - username: 'delighted_nightingale', - email: 'delighted_nightingale@profiles.elastic.co', - fullName: 'Delighted Nightingale', - }, - }, - { - uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0', - enabled: true, - data: {}, - user: { - username: 'damaged_raccoon', - email: 'damaged_raccoon@profiles.elastic.co', - fullName: 'Damaged Raccoon', - }, - }, - { - uid: 'u_A_tM4n0wPkdiQ9smmd8o0Hr_h61XQfu8aRPh9GMoRoc_0', - enabled: true, - data: {}, - user: { - username: 'physical_dinosaur', - email: 'physical_dinosaur@profiles.elastic.co', - fullName: 'Physical Dinosaur', - }, - }, - { - uid: 'u_9xDEQqUqoYCnFnPPLq5mIRHKL8gBTo_NiKgOnd5gGk0_0', - enabled: true, - data: {}, - user: { - username: 'wet_dingo', - email: 'wet_dingo@profiles.elastic.co', - fullName: 'Wet Dingo', - }, - }, -]; - -describe('UserProfilesPopover', () => { - it('should render `EuiPopover` and `UserProfilesSelectable` correctly', () => { - const [firstOption, secondOption] = userProfiles; - const wrapper = shallow( - Toggle} - closePopover={jest.fn()} - selectableProps={{ - selectedOptions: [firstOption], - defaultOptions: [secondOption], - }} - /> - ); - expect(wrapper).toMatchInlineSnapshot(` - - Toggle - - } - closePopover={[MockFunction]} - display="inline-block" - hasArrow={true} - isOpen={false} - ownFocus={true} - panelPaddingSize="none" - repositionToCrossAxis={true} - > - - - - - `); - }); -}); diff --git a/packages/shared-ux/avatar/user_profile/impl/user_profiles_popover.tsx b/packages/shared-ux/avatar/user_profile/impl/user_profiles_popover.tsx deleted file mode 100644 index 9fc553d9be689..0000000000000 --- a/packages/shared-ux/avatar/user_profile/impl/user_profiles_popover.tsx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { EuiPopoverProps, EuiContextMenuPanelProps } from '@elastic/eui'; -import type { FunctionComponent } from 'react'; -import React from 'react'; -import { EuiPopover, EuiContextMenuPanel } from '@elastic/eui'; - -import { UserProfilesSelectable, UserProfilesSelectableProps } from './user_profiles_selectable'; - -/** - * Props of {@link UserProfilesPopover} component - */ -export interface UserProfilesPopoverProps extends EuiPopoverProps { - /** - * Title of the popover - * @see EuiContextMenuPanelProps - */ - title?: EuiContextMenuPanelProps['title']; - - /** - * Props forwarded to selectable component - * @see UserProfilesSelectableProps - */ - selectableProps: UserProfilesSelectableProps; -} - -/** - * Renders a selectable component inside a popover given a list of user profiles - */ -export const UserProfilesPopover: FunctionComponent = ({ - title, - selectableProps, - ...popoverProps -}) => { - return ( - - - - - - ); -}; diff --git a/packages/shared-ux/avatar/user_profile/impl/user_profiles_selectable.test.tsx b/packages/shared-ux/avatar/user_profile/impl/user_profiles_selectable.test.tsx deleted file mode 100644 index d17e70c566f43..0000000000000 --- a/packages/shared-ux/avatar/user_profile/impl/user_profiles_selectable.test.tsx +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { mount } from 'enzyme'; -import React from 'react'; - -import { UserProfilesSelectable } from './user_profiles_selectable'; - -const userProfiles = [ - { - uid: 'u_BOulL4QMPSyV9jg5lQI2JmCkUnokHTazBnet3xVHNv0_0', - enabled: true, - data: {}, - user: { - username: 'delighted_nightingale', - email: 'delighted_nightingale@profiles.elastic.co', - fullName: 'Delighted Nightingale', - }, - }, - { - uid: 'u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0', - enabled: true, - data: {}, - user: { - username: 'damaged_raccoon', - email: 'damaged_raccoon@profiles.elastic.co', - fullName: 'Damaged Raccoon', - }, - }, - { - uid: 'u_A_tM4n0wPkdiQ9smmd8o0Hr_h61XQfu8aRPh9GMoRoc_0', - enabled: true, - data: {}, - user: { - username: 'physical_dinosaur', - email: 'physical_dinosaur@profiles.elastic.co', - fullName: 'Physical Dinosaur', - }, - }, - { - uid: 'u_9xDEQqUqoYCnFnPPLq5mIRHKL8gBTo_NiKgOnd5gGk0_0', - enabled: true, - data: {}, - user: { - username: 'wet_dingo', - email: 'wet_dingo@profiles.elastic.co', - fullName: 'Wet Dingo', - }, - }, -]; - -describe('UserProfilesSelectable', () => { - it('should render `selectedOptions` before `defaultOptions` separated by a group label', () => { - const [firstOption, secondOption, thirdOption] = userProfiles; - const wrapper = mount( - - ); - expect(wrapper.find('EuiSelectable').prop('options')).toEqual([ - expect.objectContaining({ - key: firstOption.uid, - checked: 'on', - }), - expect.objectContaining({ - isGroupLabel: true, - label: 'Suggested', - }), - expect.objectContaining({ - key: secondOption.uid, - checked: undefined, - }), - expect.objectContaining({ - key: thirdOption.uid, - checked: undefined, - }), - ]); - }); - - it('should hide `selectedOptions` and `defaultOptions` when `options` has been provided', () => { - const [firstOption, secondOption, thirdOption] = userProfiles; - const wrapper = mount( - - ); - expect(wrapper.find('EuiSelectable').prop('options')).toEqual([ - expect.objectContaining({ - key: thirdOption.uid, - checked: undefined, - }), - ]); - }); - - it('should hide `selectedOptions` and `defaultOptions` when `options` gets updated', () => { - const [firstOption, secondOption, thirdOption] = userProfiles; - const wrapper = mount( - - ); - expect(wrapper.find('EuiSelectable').prop('options')).toEqual([ - expect.objectContaining({ - key: firstOption.uid, - checked: 'on', - }), - expect.objectContaining({ - isGroupLabel: true, - label: 'Suggested', - }), - expect.objectContaining({ - key: secondOption.uid, - checked: undefined, - }), - ]); - - wrapper.setProps({ options: [thirdOption] }).update(); - - expect(wrapper.find('EuiSelectable').prop('options')).toEqual([ - expect.objectContaining({ - key: thirdOption.uid, - checked: undefined, - }), - ]); - }); - - it('should render `options` with correct checked status', () => { - const [firstOption, secondOption] = userProfiles; - const wrapper = mount( - - ); - expect(wrapper.find('EuiSelectable').prop('options')).toEqual([ - expect.objectContaining({ - key: firstOption.uid, - checked: 'on', - }), - expect.objectContaining({ - key: secondOption.uid, - checked: undefined, - }), - ]); - }); - - it('should trigger `onChange` callback when selection changes', () => { - const onChange = jest.fn(); - const [firstOption, secondOption] = userProfiles; - const wrapper = mount( - - ); - wrapper.find('EuiSelectableListItem').last().simulate('click'); - expect(onChange).toHaveBeenCalledWith( - expect.arrayContaining([ - expect.objectContaining({ - uid: firstOption.uid, - }), - expect.objectContaining({ - uid: secondOption.uid, - }), - ]) - ); - }); - - it('should continue to display `selectedOptions` when getting unchecked', () => { - const onChange = jest.fn(); - const [firstOption] = userProfiles; - const wrapper = mount( - - ); - expect(wrapper.find('EuiSelectable').prop('options')).toEqual([ - expect.objectContaining({ - key: firstOption.uid, - checked: 'on', - }), - ]); - wrapper.setProps({ selectedOptions: [] }).update(); - expect(wrapper.find('EuiSelectable').prop('options')).toEqual([ - expect.objectContaining({ - key: firstOption.uid, - checked: undefined, - }), - ]); - }); - - it('should trigger `onSearchChange` callback when search term changes', () => { - const onSearchChange = jest.fn(); - const wrapper = mount(); - wrapper.find('input[type="search"]').simulate('change', { target: { value: 'search' } }); - expect(onSearchChange).toHaveBeenCalledWith('search', []); - }); -}); diff --git a/packages/shared-ux/avatar/user_profile/impl/user_profiles_selectable.tsx b/packages/shared-ux/avatar/user_profile/impl/user_profiles_selectable.tsx deleted file mode 100644 index ea0e2260f44fd..0000000000000 --- a/packages/shared-ux/avatar/user_profile/impl/user_profiles_selectable.tsx +++ /dev/null @@ -1,319 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { EuiSelectableOption, EuiSelectableProps } from '@elastic/eui'; -import { - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiHorizontalRule, - EuiPanel, - EuiSelectable, - EuiSpacer, - EuiText, - EuiTextColor, -} from '@elastic/eui'; -import type { FunctionComponent, ReactNode } from 'react'; -import React, { useEffect, useState } from 'react'; - -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; - -import { getUserDisplayName } from './user_profile'; -import type { UserProfileWithAvatar } from './user_avatar'; -import { UserAvatar } from './user_avatar'; - -/** - * Props of {@link UserProfilesSelectable} component - */ -export interface UserProfilesSelectableProps - extends Pick< - EuiSelectableProps, - | 'height' - | 'singleSelection' - | 'loadingMessage' - | 'noMatchesMessage' - | 'emptyMessage' - | 'errorMessage' - > { - /** - * List of users to be rendered as suggestions. - */ - defaultOptions?: UserProfileWithAvatar[]; - - /** - * List of selected users. - */ - selectedOptions?: UserProfileWithAvatar[]; - - /** - * List of users from search results. Should be updated based on the search term provided by `onSearchChange` callback. - */ - options?: UserProfileWithAvatar[]; - - /** - * Passes back the list of selected users. - * @param options List of selected users - */ - onChange?(options: UserProfileWithAvatar[]): void; - - /** - * Passes back the search term. - * @param searchTerm Search term - */ - onSearchChange?(searchTerm: string): void; - - /** - * Loading indicator for asynchronous search operations. - */ - isLoading?: boolean; - - /** - * Placeholder text for search box. - */ - searchPlaceholder?: string; - - /** - * Returns text for selected status. - * @param selectedCount Number of selected users - */ - selectedStatusMessage?(selectedCount: number): ReactNode; - - /** - * Text for label of clear button. - */ - clearButtonLabel?: ReactNode; -} - -/** - * Renders a selectable component given a list of user profiles - */ -export const UserProfilesSelectable: FunctionComponent = ({ - selectedOptions, - defaultOptions, - options, - onChange, - onSearchChange, - isLoading = false, - singleSelection = false, - height, - loadingMessage, - noMatchesMessage, - emptyMessage, - errorMessage, - searchPlaceholder, - selectedStatusMessage, - clearButtonLabel, -}) => { - const [displayedOptions, setDisplayedOptions] = useState([]); - - // Resets all displayed options - const resetDisplayedOptions = () => { - if (options) { - setDisplayedOptions(options.map(toSelectableOption)); - return; - } - - setDisplayedOptions([]); - updateDisplayedOptions(); - }; - - const ensureSeparator = (values: SelectableOption[]) => { - let index = values.findIndex((option) => option.isGroupLabel); - if (index === -1) { - const length = values.push({ - label: i18n.translate( - 'sharedUXPackages.userProfileComponents.userProfilesSelectable.suggestedLabel', - { - defaultMessage: 'Suggested', - } - ), - isGroupLabel: true, - } as SelectableOption); - index = length - 1; - } - return index; - }; - - // Updates displayed options without removing or resorting exiting options - const updateDisplayedOptions = () => { - if (options) { - return; - } - - setDisplayedOptions((values) => { - // Copy all displayed options - const nextOptions: SelectableOption[] = [...values]; - - // Get any newly added selected options - const selectedOptionsToAdd: SelectableOption[] = selectedOptions - ? selectedOptions - .filter((profile) => !nextOptions.find((option) => option.key === profile.uid)) - .map(toSelectableOption) - : []; - - // Get any newly added default options - const defaultOptionsToAdd: SelectableOption[] = defaultOptions - ? defaultOptions - .filter( - (profile) => - !nextOptions.find((option) => option.key === profile.uid) && - !selectedOptionsToAdd.find((option) => option.key === profile.uid) - ) - .map(toSelectableOption) - : []; - - // Merge in any new options and add group separator if necessary - if (defaultOptionsToAdd.length) { - const separatorIndex = ensureSeparator(nextOptions); - nextOptions.splice(separatorIndex, 0, ...selectedOptionsToAdd); - nextOptions.push(...defaultOptionsToAdd); - } else { - nextOptions.push(...selectedOptionsToAdd); - } - - return nextOptions; - }); - }; - - // Marks displayed options as checked or unchecked depending on `props.selectedOptions` - const updateCheckedStatus = () => { - setDisplayedOptions((values) => - values.map((option) => { - if (selectedOptions) { - const match = selectedOptions.find((p) => p.uid === option.key); - return { ...option, checked: match ? 'on' : undefined }; - } - return { ...option, checked: undefined }; - }) - ); - }; - - useEffect(resetDisplayedOptions, [options]); // eslint-disable-line react-hooks/exhaustive-deps - useEffect(updateDisplayedOptions, [defaultOptions, selectedOptions]); // eslint-disable-line react-hooks/exhaustive-deps - useEffect(updateCheckedStatus, [options, defaultOptions, selectedOptions]); - - const selectedCount = selectedOptions ? selectedOptions.length : 0; - - const placeholder = - searchPlaceholder ?? - i18n.translate( - 'sharedUXPackages.userProfileComponents.userProfilesSelectable.searchPlaceholder', - { - defaultMessage: 'Search', - } - ); - - return ( - >) => { - if (!onChange) { - return; - } - - // Take all selected options from `nextOptions` unless already in `props.selectedOptions` - const values: UserProfileWithAvatar[] = nextOptions - .filter((option) => { - if (option.isGroupLabel || option.checked !== 'on') { - return false; - } - if (selectedOptions && selectedOptions.find((p) => p.uid === option.key)) { - return false; - } - return true; - }) - .map((option) => option.data); - - // Add all options from `props.selectedOptions` unless they have been deselected in `nextOptions` - if (selectedOptions && !singleSelection) { - selectedOptions.forEach((profile) => { - const match = nextOptions.find((o) => o.key === profile.uid); - if (!match || match.checked === 'on') { - values.push(profile); - } - }); - } - - onChange(values); - }} - style={{ maxHeight: height }} - singleSelection={singleSelection} - searchable - searchProps={{ - placeholder, - onChange: onSearchChange, - isLoading, - isClearable: !isLoading, - }} - isPreFiltered - listProps={{ onFocusBadge: false }} - loadingMessage={loadingMessage} - noMatchesMessage={noMatchesMessage} - emptyMessage={emptyMessage} - errorMessage={errorMessage} - > - {(list, search) => ( - <> - - {search} - - - - - {selectedStatusMessage ? ( - selectedStatusMessage(selectedCount) - ) : ( - - )} - - - - {selectedCount ? ( - onChange?.([])} - style={{ height: '1rem' }} - > - {clearButtonLabel ?? ( - - )} - - ) : null} - - - - - {list} - - )} - - ); -}; - -type SelectableOption = EuiSelectableOption; - -function toSelectableOption(userProfile: UserProfileWithAvatar): SelectableOption { - // @ts-ignore: `isGroupLabel` is not required here but TS complains - return { - key: userProfile.uid, - prepend: , - label: getUserDisplayName(userProfile.user), - append: {userProfile.user.email}, - data: userProfile, - }; -} diff --git a/packages/shared-ux/page/no_data/impl/tsconfig.json b/packages/shared-ux/page/no_data/impl/tsconfig.json index 99daacfdb7087..d36c0c9a9ff95 100644 --- a/packages/shared-ux/page/no_data/impl/tsconfig.json +++ b/packages/shared-ux/page/no_data/impl/tsconfig.json @@ -14,13 +14,13 @@ "**/*.tsx", ], "kbn_references": [ - "@kbn/shared-ux-avatar-solution", "@kbn/shared-ux-card-no-data", "@kbn/shared-ux-page-no-data-types", "@kbn/test-jest-helpers", "@kbn/shared-ux-page-no-data-mocks", "@kbn/i18n", "@kbn/i18n-react", + "@kbn/shared-ux-avatar-solution", ], "exclude": [ "target/**/*", diff --git a/src/plugins/kibana_overview/tsconfig.json b/src/plugins/kibana_overview/tsconfig.json index 2f450f1e57609..976eed58d31aa 100644 --- a/src/plugins/kibana_overview/tsconfig.json +++ b/src/plugins/kibana_overview/tsconfig.json @@ -23,9 +23,9 @@ "@kbn/test-jest-helpers", "@kbn/shared-ux-page-kibana-template", "@kbn/shared-ux-page-analytics-no-data", - "@kbn/shared-ux-avatar-solution", "@kbn/shared-ux-link-redirect-app", "@kbn/shared-ux-router", + "@kbn/shared-ux-avatar-solution", ], "exclude": [ "target/**/*", diff --git a/tsconfig.base.json b/tsconfig.base.json index b0fd477a6aff3..79e998206483d 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1346,8 +1346,6 @@ "@kbn/shared-svg/*": ["packages/kbn-shared-svg/*"], "@kbn/shared-ux-avatar-solution": ["packages/shared-ux/avatar/solution"], "@kbn/shared-ux-avatar-solution/*": ["packages/shared-ux/avatar/solution/*"], - "@kbn/shared-ux-avatar-user-profile-components": ["packages/shared-ux/avatar/user_profile/impl"], - "@kbn/shared-ux-avatar-user-profile-components/*": ["packages/shared-ux/avatar/user_profile/impl/*"], "@kbn/shared-ux-button-exit-full-screen": ["packages/shared-ux/button/exit_full_screen/impl"], "@kbn/shared-ux-button-exit-full-screen/*": ["packages/shared-ux/button/exit_full_screen/impl/*"], "@kbn/shared-ux-button-exit-full-screen-mocks": ["packages/shared-ux/button/exit_full_screen/mocks"], diff --git a/x-pack/plugins/spaces/tsconfig.json b/x-pack/plugins/spaces/tsconfig.json index d59e30e3a0194..060fd35cffcbc 100644 --- a/x-pack/plugins/spaces/tsconfig.json +++ b/x-pack/plugins/spaces/tsconfig.json @@ -16,7 +16,6 @@ "@kbn/core", "@kbn/test-jest-helpers", "@kbn/i18n-react", - "@kbn/shared-ux-avatar-solution", "@kbn/core-saved-objects-api-server", "@kbn/config-schema", "@kbn/utility-types", @@ -32,6 +31,7 @@ "@kbn/core-custom-branding-common", "@kbn/shared-ux-link-redirect-app", "@kbn/config", + "@kbn/shared-ux-avatar-solution", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index b8221e1102355..bd3f3f1395d8b 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -5214,7 +5214,6 @@ "sharedUXPackages.noDataPage.intro": "Ajoutez vos données pour commencer, ou {link} sur {solution}.", "sharedUXPackages.noDataPage.welcomeTitle": "Bienvenue dans Elastic {solution} !", "sharedUXPackages.solutionNav.mobileTitleText": "{solutionName} {menuText}", - "sharedUXPackages.userProfileComponents.userProfilesSelectable.selectedStatusMessage": "{count, plural, one {# utilisateur sélectionné} many {# utilisateurs sélectionnés} other {# utilisateurs sélectionnés}}", "sharedUXPackages.buttonToolbar.buttons.addFromLibrary.libraryButtonLabel": "Ajouter depuis la bibliothèque", "sharedUXPackages.buttonToolbar.toolbar.errorToolbarText": "Il y a plus de 120 boutons supplémentaires. Nous vous invitons à limiter le nombre de boutons.", "sharedUXPackages.card.noData.description": "Utilisez Elastic Agent pour collecter de manière simple et unifiée les données de vos machines.", @@ -5268,9 +5267,6 @@ "sharedUXPackages.solutionNav.collapsibleLabel": "Réduire la navigation latérale", "sharedUXPackages.solutionNav.menuText": "menu", "sharedUXPackages.solutionNav.openLabel": "Ouvrir la navigation latérale", - "sharedUXPackages.userProfileComponents.userProfilesSelectable.clearButtonLabel": "Retirer tous les utilisateurs", - "sharedUXPackages.userProfileComponents.userProfilesSelectable.searchPlaceholder": "Recherche", - "sharedUXPackages.userProfileComponents.userProfilesSelectable.suggestedLabel": "Suggérée", "telemetry.callout.appliesSettingTitle": "Les modifications apportées à ce paramètre s'appliquent dans {allOfKibanaText} et sont enregistrées automatiquement.", "telemetry.dataManagementDisclaimerPrivacy": "{optInStatus} Ceci nous permet de savoir ce qui intéresse le plus nos utilisateurs, afin d’améliorer nos produits et services. Consultez notre {privacyStatementLink}.", "telemetry.seeExampleOfClusterDataAndEndpointSecuity": "Découvrez des exemples des {clusterData} et {securityData} que nous collectons.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index cf97123016c9d..938c92c1aa10e 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -5230,7 +5230,6 @@ "sharedUXPackages.noDataPage.intro": "データを追加して開始するか、{solution}については{link}をご覧ください。", "sharedUXPackages.noDataPage.welcomeTitle": "Elastic {solution}へようこそ!", "sharedUXPackages.solutionNav.mobileTitleText": "{solutionName} {menuText}", - "sharedUXPackages.userProfileComponents.userProfilesSelectable.selectedStatusMessage": "{count, plural, other {#人のユーザーが選択されました}}", "sharedUXPackages.buttonToolbar.buttons.addFromLibrary.libraryButtonLabel": "ライブラリから追加", "sharedUXPackages.buttonToolbar.toolbar.errorToolbarText": "120以上のボタンがあります。ボタンの数を制限することを検討してください。", "sharedUXPackages.card.noData.description": "Elasticエージェントを使用すると、シンプルで統一された方法でコンピューターからデータを収集するできます。", @@ -5284,9 +5283,6 @@ "sharedUXPackages.solutionNav.collapsibleLabel": "サイドナビゲーションを折りたたむ", "sharedUXPackages.solutionNav.menuText": "メニュー", "sharedUXPackages.solutionNav.openLabel": "サイドナビゲーションを開く", - "sharedUXPackages.userProfileComponents.userProfilesSelectable.clearButtonLabel": "すべてのユーザーを削除", - "sharedUXPackages.userProfileComponents.userProfilesSelectable.searchPlaceholder": "検索", - "sharedUXPackages.userProfileComponents.userProfilesSelectable.suggestedLabel": "候補", "telemetry.callout.appliesSettingTitle": "この設定に加えた変更は{allOfKibanaText}に適用され、自動的に保存されます。", "telemetry.dataManagementDisclaimerPrivacy": "{optInStatus} これにより、ユーザーが最も関心を持っている項目を把握できるため、製品とサービスを改善できます。{privacyStatementLink}を参照してください。", "telemetry.seeExampleOfClusterDataAndEndpointSecuity": "収集される{clusterData}および{securityData}の例を参照してください。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 821c07ad58fa1..74f0152cb1dfc 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -5229,7 +5229,6 @@ "sharedUXPackages.noDataPage.intro": "添加您的数据以开始,或{link}{solution}。", "sharedUXPackages.noDataPage.welcomeTitle": "欢迎使用 Elastic {solution}!", "sharedUXPackages.solutionNav.mobileTitleText": "{solutionName} {menuText}", - "sharedUXPackages.userProfileComponents.userProfilesSelectable.selectedStatusMessage": "{count, plural, other {# 个用户已选择}}", "sharedUXPackages.buttonToolbar.buttons.addFromLibrary.libraryButtonLabel": "从库中添加", "sharedUXPackages.buttonToolbar.toolbar.errorToolbarText": "有 120 多个附加按钮。请考虑限制按钮数量。", "sharedUXPackages.card.noData.description": "使用 Elastic 代理以简单统一的方式从您的计算机中收集数据。", @@ -5283,9 +5282,6 @@ "sharedUXPackages.solutionNav.collapsibleLabel": "折叠侧边导航", "sharedUXPackages.solutionNav.menuText": "菜单", "sharedUXPackages.solutionNav.openLabel": "打开侧边导航", - "sharedUXPackages.userProfileComponents.userProfilesSelectable.clearButtonLabel": "移除所有用户", - "sharedUXPackages.userProfileComponents.userProfilesSelectable.searchPlaceholder": "搜索", - "sharedUXPackages.userProfileComponents.userProfilesSelectable.suggestedLabel": "已建议", "telemetry.callout.appliesSettingTitle": "对此设置的更改将应用到{allOfKibanaText} 且会自动保存。", "telemetry.dataManagementDisclaimerPrivacy": "{optInStatus} 这便于我们了解用户最感兴趣的内容,以便我们改善产品和服务。请参阅我们的{privacyStatementLink}。", "telemetry.seeExampleOfClusterDataAndEndpointSecuity": "查看我们收集的{clusterData}和{securityData}示例。", diff --git a/yarn.lock b/yarn.lock index c2b7799faa3f7..28df3eac7c3f2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5596,10 +5596,6 @@ version "0.0.0" uid "" -"@kbn/shared-ux-avatar-user-profile-components@link:packages/shared-ux/avatar/user_profile/impl": - version "0.0.0" - uid "" - "@kbn/shared-ux-button-exit-full-screen-mocks@link:packages/shared-ux/button/exit_full_screen/mocks": version "0.0.0" uid "" From 1d66dcad4ca148bbf1ad8ce8c2d933f861e51b0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Wed, 25 Oct 2023 11:19:07 +0100 Subject: [PATCH 11/24] [Profiling] Self-managed set up (#168488) Profiling running in a self-managed environment: Screenshot 2023-10-12 at 13 47 30 Screenshot 2023-10-12 at 13 47 42 --------- Co-authored-by: Francesco Gualazzi --- .../e2e/profiling_views/functions.cy.ts | 2 +- x-pack/plugins/profiling/kibana.jsonc | 6 +- x-pack/plugins/profiling/public/services.ts | 2 +- .../server/lib/setup/cluster_settings.ts | 12 +- .../server/lib/setup/fleet_policies.ts | 10 +- .../server/lib/setup/security_role.ts | 2 +- .../profiling/server/lib/setup/types.ts | 13 +- .../plugins/profiling/server/routes/index.ts | 2 +- .../setup/get_cloud_setup_instructions.ts} | 2 +- .../setup/get_self_managed_instructions.ts | 21 ++ .../routes/{setup.ts => setup/route.ts} | 140 +++++++------- .../server/routes/setup/setup_cloud.ts | 39 ++++ .../server/routes/setup/setup_self_managed.ts | 26 +++ x-pack/plugins/profiling/server/types.ts | 8 +- .../utils/create_profiling_es_client.ts | 8 +- .../common/cloud_setup.test.ts | 181 ++++++++++++++++++ .../common/cloud_setup.ts | 75 ++++++++ .../common/cluster_settings.ts | 1 + .../common/fleet_policies.ts | 8 +- .../common/has_profiling_data.ts | 4 +- .../profiling_data_access/common/index.ts | 2 + .../common/profiling_es_client.ts | 2 +- .../common/setup.test.ts | 87 +-------- .../profiling_data_access/common/setup.ts | 54 ++---- .../profiling_data_access/kibana.jsonc | 4 +- .../server/services/register_services.ts | 8 +- .../cloud_setup_state.ts} | 37 ++-- .../server/services/setup_state/index.ts | 77 ++++++++ .../setup_state/self_managed_setup_state.ts | 34 ++++ .../server/services/status/index.ts | 45 +---- .../profiling_data_access/server/types.ts | 4 +- .../utils/create_profiling_es_client.ts | 6 +- .../translations/translations/fr-FR.json | 2 +- .../translations/translations/ja-JP.json | 2 +- .../translations/translations/zh-CN.json | 2 +- 35 files changed, 622 insertions(+), 306 deletions(-) rename x-pack/plugins/profiling/server/{lib/setup/get_setup_instructions.ts => routes/setup/get_cloud_setup_instructions.ts} (97%) create mode 100644 x-pack/plugins/profiling/server/routes/setup/get_self_managed_instructions.ts rename x-pack/plugins/profiling/server/routes/{setup.ts => setup/route.ts} (54%) create mode 100644 x-pack/plugins/profiling/server/routes/setup/setup_cloud.ts create mode 100644 x-pack/plugins/profiling/server/routes/setup/setup_self_managed.ts create mode 100644 x-pack/plugins/profiling_data_access/common/cloud_setup.test.ts create mode 100644 x-pack/plugins/profiling_data_access/common/cloud_setup.ts rename x-pack/plugins/profiling_data_access/server/services/{get_setup_state/index.ts => setup_state/cloud_setup_state.ts} (55%) create mode 100644 x-pack/plugins/profiling_data_access/server/services/setup_state/index.ts create mode 100644 x-pack/plugins/profiling_data_access/server/services/setup_state/self_managed_setup_state.ts diff --git a/x-pack/plugins/profiling/e2e/cypress/e2e/profiling_views/functions.cy.ts b/x-pack/plugins/profiling/e2e/cypress/e2e/profiling_views/functions.cy.ts index ac678e4650d02..738f800756072 100644 --- a/x-pack/plugins/profiling/e2e/cypress/e2e/profiling_views/functions.cy.ts +++ b/x-pack/plugins/profiling/e2e/cypress/e2e/profiling_views/functions.cy.ts @@ -10,7 +10,7 @@ import { profilingPerCoreWatt, } from '@kbn/observability-plugin/common'; -describe.skip('Functions page', () => { +describe('Functions page', () => { const rangeFrom = '2023-04-18T00:00:00.000Z'; const rangeTo = '2023-04-18T00:00:30.000Z'; diff --git a/x-pack/plugins/profiling/kibana.jsonc b/x-pack/plugins/profiling/kibana.jsonc index 1eae495f8c85e..aa1ae58a2b190 100644 --- a/x-pack/plugins/profiling/kibana.jsonc +++ b/x-pack/plugins/profiling/kibana.jsonc @@ -9,15 +9,15 @@ "configPath": ["xpack", "profiling"], "optionalPlugins": [ "spaces", - "usageCollection" + "usageCollection", + "cloud", + "fleet" ], "requiredPlugins": [ "charts", - "cloud", "data", "dataViews", "features", - "fleet", "licensing", "observability", "observabilityShared", diff --git a/x-pack/plugins/profiling/public/services.ts b/x-pack/plugins/profiling/public/services.ts index 785179b5bc65c..750e9eab65a96 100644 --- a/x-pack/plugins/profiling/public/services.ts +++ b/x-pack/plugins/profiling/public/services.ts @@ -19,7 +19,7 @@ import type { StorageHostDetailsAPIResponse, } from '../common/storage_explorer'; import { TopNResponse } from '../common/topn'; -import type { SetupDataCollectionInstructions } from '../server/lib/setup/get_setup_instructions'; +import type { SetupDataCollectionInstructions } from '../server/routes/setup/get_cloud_setup_instructions'; import { AutoAbortedHttpService } from './hooks/use_auto_aborted_http_client'; export interface ProfilingSetupStatus { diff --git a/x-pack/plugins/profiling/server/lib/setup/cluster_settings.ts b/x-pack/plugins/profiling/server/lib/setup/cluster_settings.ts index 3d838bc7a5c31..b1b2fb8a24724 100644 --- a/x-pack/plugins/profiling/server/lib/setup/cluster_settings.ts +++ b/x-pack/plugins/profiling/server/lib/setup/cluster_settings.ts @@ -6,7 +6,7 @@ */ import { MAX_BUCKETS } from '@kbn/profiling-data-access-plugin/common'; -import { ProfilingSetupOptions } from './types'; +import { ProfilingSetupOptions } from '@kbn/profiling-data-access-plugin/common/setup'; export async function setMaximumBuckets({ client }: ProfilingSetupOptions) { await client.getEsClient().cluster.putSettings({ @@ -20,14 +20,6 @@ export async function setMaximumBuckets({ client }: ProfilingSetupOptions) { export async function enableResourceManagement({ client }: ProfilingSetupOptions) { await client.getEsClient().cluster.putSettings({ - persistent: { - xpack: { - profiling: { - templates: { - enabled: true, - }, - }, - }, - }, + persistent: { xpack: { profiling: { templates: { enabled: true } } } }, }); } diff --git a/x-pack/plugins/profiling/server/lib/setup/fleet_policies.ts b/x-pack/plugins/profiling/server/lib/setup/fleet_policies.ts index 326ebc19dd9f1..0342f12eb8284 100644 --- a/x-pack/plugins/profiling/server/lib/setup/fleet_policies.ts +++ b/x-pack/plugins/profiling/server/lib/setup/fleet_policies.ts @@ -9,12 +9,12 @@ import { fetchFindLatestPackageOrThrow } from '@kbn/fleet-plugin/server/services import { COLLECTOR_PACKAGE_POLICY_NAME, ELASTIC_CLOUD_APM_POLICY, - SYMBOLIZER_PACKAGE_POLICY_NAME, getApmPolicy, + SYMBOLIZER_PACKAGE_POLICY_NAME, } from '@kbn/profiling-data-access-plugin/common'; import { omit } from 'lodash'; import { PackageInputType } from '../..'; -import { ProfilingSetupOptions } from './types'; +import { ProfilingCloudSetupOptions } from './types'; const CLOUD_AGENT_POLICY_ID = 'policy-elastic-agent-on-cloud'; @@ -60,7 +60,7 @@ export async function createCollectorPackagePolicy({ soClient, packagePolicyClient, config, -}: ProfilingSetupOptions) { +}: ProfilingCloudSetupOptions) { const packageName = 'profiler_collector'; const { version } = await fetchFindLatestPackageOrThrow(packageName, { prerelease: true }); const packagePolicy = { @@ -96,7 +96,7 @@ export async function createSymbolizerPackagePolicy({ soClient, packagePolicyClient, config, -}: ProfilingSetupOptions) { +}: ProfilingCloudSetupOptions) { const packageName = 'profiler_symbolizer'; const { version } = await fetchFindLatestPackageOrThrow(packageName, { prerelease: true }); const packagePolicy = { @@ -132,7 +132,7 @@ export async function removeProfilingFromApmPackagePolicy({ client, soClient, packagePolicyClient, -}: ProfilingSetupOptions) { +}: ProfilingCloudSetupOptions) { const apmPackagePolicy = await getApmPolicy({ packagePolicyClient, soClient }); if (!apmPackagePolicy) { throw new Error(`Could not find APM package policy`); diff --git a/x-pack/plugins/profiling/server/lib/setup/security_role.ts b/x-pack/plugins/profiling/server/lib/setup/security_role.ts index b578c2ef2cff6..b48a1d9f63a28 100644 --- a/x-pack/plugins/profiling/server/lib/setup/security_role.ts +++ b/x-pack/plugins/profiling/server/lib/setup/security_role.ts @@ -9,7 +9,7 @@ import { METADATA_VERSION, PROFILING_READER_ROLE_NAME, } from '@kbn/profiling-data-access-plugin/common'; -import { ProfilingSetupOptions } from './types'; +import { ProfilingSetupOptions } from '@kbn/profiling-data-access-plugin/common/setup'; export async function setSecurityRole({ client }: ProfilingSetupOptions) { const esClient = client.getEsClient(); diff --git a/x-pack/plugins/profiling/server/lib/setup/types.ts b/x-pack/plugins/profiling/server/lib/setup/types.ts index 40001649f861c..0ef5a5a4dd826 100644 --- a/x-pack/plugins/profiling/server/lib/setup/types.ts +++ b/x-pack/plugins/profiling/server/lib/setup/types.ts @@ -5,18 +5,9 @@ * 2.0. */ -import { SavedObjectsClientContract } from '@kbn/core/server'; -import { PackagePolicyClient } from '@kbn/fleet-plugin/server'; -import { Logger } from '@kbn/logging'; +import { ProfilingCloudSetupOptions as BaseProfilingCloudSetupOptions } from '@kbn/profiling-data-access-plugin/common'; import { ProfilingConfig } from '../..'; -import { ProfilingESClient } from '../../utils/create_profiling_es_client'; -export interface ProfilingSetupOptions { - client: ProfilingESClient; - soClient: SavedObjectsClientContract; - packagePolicyClient: PackagePolicyClient; - logger: Logger; - spaceId: string; - isCloudEnabled: boolean; +export interface ProfilingCloudSetupOptions extends BaseProfilingCloudSetupOptions { config: ProfilingConfig; } diff --git a/x-pack/plugins/profiling/server/routes/index.ts b/x-pack/plugins/profiling/server/routes/index.ts index d4b75e3cc1fa5..14473897720d9 100644 --- a/x-pack/plugins/profiling/server/routes/index.ts +++ b/x-pack/plugins/profiling/server/routes/index.ts @@ -18,7 +18,7 @@ import { import { ProfilingESClient } from '../utils/create_profiling_es_client'; import { registerFlameChartSearchRoute } from './flamechart'; import { registerTopNFunctionsSearchRoute } from './functions'; -import { registerSetupRoute } from './setup'; +import { registerSetupRoute } from './setup/route'; import { registerStorageExplorerRoute } from './storage_explorer/route'; import { registerTraceEventsTopNContainersSearchRoute, diff --git a/x-pack/plugins/profiling/server/lib/setup/get_setup_instructions.ts b/x-pack/plugins/profiling/server/routes/setup/get_cloud_setup_instructions.ts similarity index 97% rename from x-pack/plugins/profiling/server/lib/setup/get_setup_instructions.ts rename to x-pack/plugins/profiling/server/routes/setup/get_cloud_setup_instructions.ts index 64b857f93de27..5fd6513e15dff 100644 --- a/x-pack/plugins/profiling/server/lib/setup/get_setup_instructions.ts +++ b/x-pack/plugins/profiling/server/routes/setup/get_cloud_setup_instructions.ts @@ -25,7 +25,7 @@ export interface SetupDataCollectionInstructions { stackVersion: string; } -export async function getSetupInstructions({ +export async function getCloudSetupInstructions({ packagePolicyClient, soClient, apmServerHost, diff --git a/x-pack/plugins/profiling/server/routes/setup/get_self_managed_instructions.ts b/x-pack/plugins/profiling/server/routes/setup/get_self_managed_instructions.ts new file mode 100644 index 0000000000000..edd62bbc85aef --- /dev/null +++ b/x-pack/plugins/profiling/server/routes/setup/get_self_managed_instructions.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SetupDataCollectionInstructions } from './get_cloud_setup_instructions'; + +export function getSelfManagedInstructions({ + stackVersion, +}: { + stackVersion: string; +}): SetupDataCollectionInstructions { + return { + collector: { host: '', secretToken: '' }, + profilerAgent: { version: '' }, + symbolizer: { host: '' }, + stackVersion, + }; +} diff --git a/x-pack/plugins/profiling/server/routes/setup.ts b/x-pack/plugins/profiling/server/routes/setup/route.ts similarity index 54% rename from x-pack/plugins/profiling/server/routes/setup.ts rename to x-pack/plugins/profiling/server/routes/setup/route.ts index 45a5f9d691307..5ee297ee68791 100644 --- a/x-pack/plugins/profiling/server/routes/setup.ts +++ b/x-pack/plugins/profiling/server/routes/setup/route.ts @@ -6,18 +6,15 @@ */ import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; -import { RouteRegisterParameters } from '.'; -import { getRoutePaths } from '../../common'; -import { enableResourceManagement, setMaximumBuckets } from '../lib/setup/cluster_settings'; -import { - createCollectorPackagePolicy, - createSymbolizerPackagePolicy, - removeProfilingFromApmPackagePolicy, -} from '../lib/setup/fleet_policies'; -import { getSetupInstructions } from '../lib/setup/get_setup_instructions'; -import { setSecurityRole } from '../lib/setup/security_role'; -import { handleRouteHandlerError } from '../utils/handle_route_error_handler'; -import { getClient } from './compat'; +import { ProfilingSetupOptions } from '@kbn/profiling-data-access-plugin/common/setup'; +import { RouteRegisterParameters } from '..'; +import { getRoutePaths } from '../../../common'; +import { getCloudSetupInstructions } from './get_cloud_setup_instructions'; +import { handleRouteHandlerError } from '../../utils/handle_route_error_handler'; +import { getClient } from '../compat'; +import { setupCloud } from './setup_cloud'; +import { setupSelfManaged } from './setup_self_managed'; +import { getSelfManagedInstructions } from './get_self_managed_instructions'; export function registerSetupRoute({ router, @@ -55,7 +52,7 @@ export function registerSetupRoute({ } } ); - // Set up Elasticsearch and Fleet for Universal Profiling + router.post( { path: paths.HasSetupESResources, @@ -64,19 +61,6 @@ export function registerSetupRoute({ }, async (context, request, response) => { try { - const isCloudEnabled = dependencies.setup.cloud.isCloudEnabled; - - if (!isCloudEnabled) { - const msg = `Elastic Cloud is required to set up Elasticsearch and Fleet for Universal Profiling`; - logger.error(msg); - return response.custom({ - statusCode: 500, - body: { - message: msg, - }, - }); - } - const esClient = await getClient(context); const core = await context.core; const clientWithDefaultAuth = createProfilingEsClient({ @@ -90,45 +74,59 @@ export function registerSetupRoute({ useDefaultAuth: false, }); - const commonParams = { + const commonSetupParams: ProfilingSetupOptions = { client: clientWithDefaultAuth, + clientWithProfilingAuth, logger, - packagePolicyClient: dependencies.start.fleet.packagePolicyService, soClient: core.savedObjects.client, spaceId: dependencies.setup.spaces?.spacesService?.getSpaceId(request) ?? DEFAULT_SPACE_ID, - isCloudEnabled, }; - const setupState = await dependencies.start.profilingDataAccess.services.getSetupState( - commonParams, - clientWithProfilingAuth - ); - - const executeAdminFunctions = [ - ...(setupState.resource_management.enabled ? [] : [enableResourceManagement]), - ...(setupState.permissions.configured ? [] : [setSecurityRole]), - ...(setupState.settings.configured ? [] : [setMaximumBuckets]), - ]; - - const executeViewerFunctions = [ - ...(setupState.policies.collector.installed ? [] : [createCollectorPackagePolicy]), - ...(setupState.policies.symbolizer.installed ? [] : [createSymbolizerPackagePolicy]), - ...(setupState.policies.apm.profilingEnabled - ? [removeProfilingFromApmPackagePolicy] - : []), - ]; - - if (!executeAdminFunctions.length && !executeViewerFunctions.length) { - return response.ok(); + const { type, setupState } = + await dependencies.start.profilingDataAccess.services.getSetupState({ + esClient, + soClient: core.savedObjects.client, + spaceId: + dependencies.setup.spaces?.spacesService?.getSpaceId(request) ?? DEFAULT_SPACE_ID, + }); + + const isCloudEnabled = dependencies.setup.cloud?.isCloudEnabled; + if (isCloudEnabled && type === 'cloud') { + if (!dependencies.start.fleet) { + const msg = `Elastic Fleet is required to set up Universal Profiling on Cloud`; + logger.error(msg); + return response.custom({ + statusCode: 500, + body: { message: msg }, + }); + } + logger.debug('Setting up Universal Profiling on Cloud'); + + await setupCloud({ + setupState, + setupParams: { + ...commonSetupParams, + packagePolicyClient: dependencies.start.fleet.packagePolicyService, + isCloudEnabled, + config: dependencies.config, + }, + }); + + logger.debug('[DONE] Setting up Universal Profiling on Cloud'); + } else { + logger.debug('Setting up self-managed Universal Profiling'); + + await setupSelfManaged({ + setupState, + setupParams: commonSetupParams, + }); + + logger.debug('[DONE] Setting up self-managed Universal Profiling'); } - const setupParams = { - ...commonParams, - config: dependencies.config, - }; - await Promise.all(executeAdminFunctions.map((fn) => fn(setupParams))); - await Promise.all(executeViewerFunctions.map((fn) => fn(setupParams))); + // Wait until Profiling ES plugin creates all resources + await clientWithDefaultAuth.profilingStatus({ waitForResourcesCreated: true }); if (dependencies.telemetryUsageCounter) { dependencies.telemetryUsageCounter.incrementCounter({ @@ -166,16 +164,30 @@ export function registerSetupRoute({ }, async (context, request, response) => { try { - const apmServerHost = dependencies.setup.cloud?.apm?.url; const stackVersion = dependencies.stackVersion; - const setupInstructions = await getSetupInstructions({ - packagePolicyClient: dependencies.start.fleet.packagePolicyService, - soClient: (await context.core).savedObjects.client, - apmServerHost, - stackVersion, - }); + const isCloudEnabled = dependencies.setup.cloud?.isCloudEnabled; + if (isCloudEnabled) { + if (!dependencies.start.fleet) { + const msg = `Elastic Fleet is required to set up Universal Profiling on Cloud`; + logger.error(msg); + return response.custom({ + statusCode: 500, + body: { message: msg }, + }); + } + + const apmServerHost = dependencies.setup.cloud?.apm?.url; + const setupInstructions = await getCloudSetupInstructions({ + packagePolicyClient: dependencies.start.fleet?.packagePolicyService, + soClient: (await context.core).savedObjects.client, + apmServerHost, + stackVersion, + }); + + return response.ok({ body: setupInstructions }); + } - return response.ok({ body: setupInstructions }); + return response.ok({ body: getSelfManagedInstructions({ stackVersion }) }); } catch (error) { return handleRouteHandlerError({ error, diff --git a/x-pack/plugins/profiling/server/routes/setup/setup_cloud.ts b/x-pack/plugins/profiling/server/routes/setup/setup_cloud.ts new file mode 100644 index 0000000000000..c4978710991ce --- /dev/null +++ b/x-pack/plugins/profiling/server/routes/setup/setup_cloud.ts @@ -0,0 +1,39 @@ +/* + * 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 { CloudSetupState } from '@kbn/profiling-data-access-plugin/common/cloud_setup'; +import { enableResourceManagement, setMaximumBuckets } from '../../lib/setup/cluster_settings'; +import { + createCollectorPackagePolicy, + createSymbolizerPackagePolicy, + removeProfilingFromApmPackagePolicy, +} from '../../lib/setup/fleet_policies'; +import { setSecurityRole } from '../../lib/setup/security_role'; +import { ProfilingCloudSetupOptions } from '../../lib/setup/types'; + +export async function setupCloud({ + setupState, + setupParams, +}: { + setupState: CloudSetupState; + setupParams: ProfilingCloudSetupOptions; +}) { + const executeAdminFunctions = [ + ...(setupState.resource_management.enabled ? [] : [enableResourceManagement]), + ...(setupState.permissions.configured ? [] : [setSecurityRole]), + ...(setupState.settings.configured ? [] : [setMaximumBuckets]), + ]; + + const executeViewerFunctions = [ + ...(setupState.policies.collector.installed ? [] : [createCollectorPackagePolicy]), + ...(setupState.policies.symbolizer.installed ? [] : [createSymbolizerPackagePolicy]), + ...(setupState.policies.apm.profilingEnabled ? [removeProfilingFromApmPackagePolicy] : []), + ]; + // Give priority to admin functions as if something fails we won't procceed to viewer functions + await Promise.all(executeAdminFunctions.map((fn) => fn(setupParams))); + await Promise.all(executeViewerFunctions.map((fn) => fn(setupParams))); +} diff --git a/x-pack/plugins/profiling/server/routes/setup/setup_self_managed.ts b/x-pack/plugins/profiling/server/routes/setup/setup_self_managed.ts new file mode 100644 index 0000000000000..c82721780cd0c --- /dev/null +++ b/x-pack/plugins/profiling/server/routes/setup/setup_self_managed.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ProfilingSetupOptions, SetupState } from '@kbn/profiling-data-access-plugin/common/setup'; +import { enableResourceManagement, setMaximumBuckets } from '../../lib/setup/cluster_settings'; +import { setSecurityRole } from '../../lib/setup/security_role'; + +export async function setupSelfManaged({ + setupState, + setupParams, +}: { + setupState: SetupState; + setupParams: ProfilingSetupOptions; +}) { + const executeFunctions = [ + ...(setupState.resource_management.enabled ? [] : [enableResourceManagement]), + ...(setupState.permissions.configured ? [] : [setSecurityRole]), + ...(setupState.settings.configured ? [] : [setMaximumBuckets]), + ]; + + await Promise.all(executeFunctions.map((fn) => fn(setupParams))); +} diff --git a/x-pack/plugins/profiling/server/types.ts b/x-pack/plugins/profiling/server/types.ts index 6ee94e238effa..24705921bbbf9 100644 --- a/x-pack/plugins/profiling/server/types.ts +++ b/x-pack/plugins/profiling/server/types.ts @@ -20,8 +20,8 @@ import { export interface ProfilingPluginSetupDeps { observability: ObservabilityPluginSetup; features: FeaturesPluginSetup; - cloud: CloudSetup; - fleet: FleetSetupContract; + cloud?: CloudSetup; + fleet?: FleetSetupContract; spaces?: SpacesPluginSetup; usageCollection?: UsageCollectionSetup; profilingDataAccess: ProfilingDataAccessPluginSetup; @@ -30,8 +30,8 @@ export interface ProfilingPluginSetupDeps { export interface ProfilingPluginStartDeps { observability: {}; features: {}; - cloud: CloudStart; - fleet: FleetStartContract; + cloud?: CloudStart; + fleet?: FleetStartContract; spaces?: SpacesPluginStart; profilingDataAccess: ProfilingDataAccessPluginStart; } diff --git a/x-pack/plugins/profiling/server/utils/create_profiling_es_client.ts b/x-pack/plugins/profiling/server/utils/create_profiling_es_client.ts index 76ea2788fea57..f085a89b2f3db 100644 --- a/x-pack/plugins/profiling/server/utils/create_profiling_es_client.ts +++ b/x-pack/plugins/profiling/server/utils/create_profiling_es_client.ts @@ -38,7 +38,7 @@ export interface ProfilingESClient { query: QueryDslQueryContainer; sampleSize: number; }): Promise; - profilingStatus(): Promise; + profilingStatus(params?: { waitForResourcesCreated?: boolean }): Promise; getEsClient(): ElasticsearchClient; profilingFlamegraph({}: { query: QueryDslQueryContainer; @@ -101,7 +101,7 @@ export function createProfilingEsClient({ return unwrapEsResponse(promise) as Promise; }, - profilingStatus() { + profilingStatus({ waitForResourcesCreated = false } = {}) { const controller = new AbortController(); const promise = withProfilingSpan('_profiling/status', () => { @@ -109,7 +109,9 @@ export function createProfilingEsClient({ esClient.transport.request( { method: 'GET', - path: encodeURI('/_profiling/status'), + path: encodeURI( + `/_profiling/status?wait_for_resources_created=${waitForResourcesCreated}` + ), }, { signal: controller.signal, diff --git a/x-pack/plugins/profiling_data_access/common/cloud_setup.test.ts b/x-pack/plugins/profiling_data_access/common/cloud_setup.test.ts new file mode 100644 index 0000000000000..1d99c6346c4c6 --- /dev/null +++ b/x-pack/plugins/profiling_data_access/common/cloud_setup.test.ts @@ -0,0 +1,181 @@ +/* + * 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 { + areCloudResourcesSetup, + createDefaultCloudSetupState, + PartialCloudSetupState, +} from './cloud_setup'; +import { mergePartialSetupStates } from './setup'; + +const createCloudState = (available: boolean): PartialCloudSetupState => ({ cloud: { available } }); +const createDataState = (available: boolean): PartialCloudSetupState => ({ data: { available } }); +const createPermissionState = (configured: boolean): PartialCloudSetupState => ({ + permissions: { configured }, +}); +const createCollectorPolicyState = (installed: boolean): PartialCloudSetupState => ({ + policies: { collector: { installed } }, +}); +const createSymbolizerPolicyState = (installed: boolean): PartialCloudSetupState => ({ + policies: { symbolizer: { installed } }, +}); +const createProfilingInApmPolicyState = (profilingEnabled: boolean): PartialCloudSetupState => ({ + policies: { apm: { profilingEnabled } }, +}); + +function createResourceState({ + enabled, + created, +}: { + enabled: boolean; + created: boolean; +}): PartialCloudSetupState { + return { + resource_management: { + enabled, + }, + resources: { + created, + }, + }; +} + +function createSettingsState(configured: boolean): PartialCloudSetupState { + return { + settings: { + configured, + }, + }; +} + +describe('Merging partial state operations', () => { + const defaultSetupState = createDefaultCloudSetupState(); + + it('returns partial states with missing key', () => { + const mergedState = mergePartialSetupStates(defaultSetupState, [ + createCloudState(true), + createDataState(true), + ]); + + expect(mergedState.cloud.available).toEqual(true); + expect(mergedState.cloud.required).toEqual(true); + expect(mergedState.data.available).toEqual(true); + }); + + it('should deeply nested partial states with overlap', () => { + const mergedState = mergePartialSetupStates(defaultSetupState, [ + createCollectorPolicyState(true), + createSymbolizerPolicyState(true), + ]); + + expect(mergedState.policies.collector.installed).toEqual(true); + expect(mergedState.policies.symbolizer.installed).toEqual(true); + }); + it('returns false when permission is not configured', () => { + const mergedState = mergePartialSetupStates(defaultSetupState, [ + createCollectorPolicyState(true), + createSymbolizerPolicyState(true), + createProfilingInApmPolicyState(true), + createResourceState({ enabled: true, created: true }), + createSettingsState(true), + createPermissionState(false), + ]); + + expect(areCloudResourcesSetup(mergedState)).toBeFalsy(); + }); + + it('returns false when resource management is not enabled', () => { + const mergedState = mergePartialSetupStates(defaultSetupState, [ + createCollectorPolicyState(true), + createSymbolizerPolicyState(true), + createProfilingInApmPolicyState(true), + createResourceState({ enabled: false, created: true }), + createSettingsState(true), + createPermissionState(true), + ]); + + expect(areCloudResourcesSetup(mergedState)).toBeFalsy(); + }); + + it('returns false when resources are not created', () => { + const mergedState = mergePartialSetupStates(defaultSetupState, [ + createCollectorPolicyState(true), + createSymbolizerPolicyState(true), + createProfilingInApmPolicyState(true), + createResourceState({ enabled: true, created: false }), + createSettingsState(true), + createPermissionState(true), + ]); + + expect(areCloudResourcesSetup(mergedState)).toBeFalsy(); + }); + + it('returns false when settings are not configured', () => { + const mergedState = mergePartialSetupStates(defaultSetupState, [ + createCollectorPolicyState(true), + createSymbolizerPolicyState(true), + createProfilingInApmPolicyState(true), + createResourceState({ enabled: true, created: true }), + createSettingsState(false), + createPermissionState(true), + ]); + + expect(areCloudResourcesSetup(mergedState)).toBeFalsy(); + }); + + it('returns true when all checks are valid', () => { + const mergedState = mergePartialSetupStates(defaultSetupState, [ + createCollectorPolicyState(true), + createSymbolizerPolicyState(true), + createProfilingInApmPolicyState(false), + createResourceState({ enabled: true, created: true }), + createSettingsState(true), + createPermissionState(true), + ]); + + expect(areCloudResourcesSetup(mergedState)).toBeTruthy(); + }); + + it('returns false when collector is not found', () => { + const mergedState = mergePartialSetupStates(defaultSetupState, [ + createCollectorPolicyState(false), + createSymbolizerPolicyState(true), + createProfilingInApmPolicyState(false), + createResourceState({ enabled: true, created: true }), + createSettingsState(true), + createPermissionState(true), + ]); + + expect(areCloudResourcesSetup(mergedState)).toBeFalsy(); + }); + + it('returns false when symbolizer is not found', () => { + const mergedState = mergePartialSetupStates(defaultSetupState, [ + createCollectorPolicyState(true), + createSymbolizerPolicyState(false), + createProfilingInApmPolicyState(false), + createResourceState({ enabled: true, created: true }), + createSettingsState(true), + createPermissionState(true), + ]); + + expect(areCloudResourcesSetup(mergedState)).toBeFalsy(); + }); + + it('returns false when profiling is in APM server', () => { + const mergedState = mergePartialSetupStates(defaultSetupState, [ + createCollectorPolicyState(true), + createSymbolizerPolicyState(true), + createProfilingInApmPolicyState(true), + createResourceState({ enabled: true, created: true }), + createSettingsState(true), + createPermissionState(true), + ]); + + expect(areCloudResourcesSetup(mergedState)).toBeFalsy(); + }); +}); diff --git a/x-pack/plugins/profiling_data_access/common/cloud_setup.ts b/x-pack/plugins/profiling_data_access/common/cloud_setup.ts new file mode 100644 index 0000000000000..1c03451cbd2b2 --- /dev/null +++ b/x-pack/plugins/profiling_data_access/common/cloud_setup.ts @@ -0,0 +1,75 @@ +/* + * 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 { RecursivePartial } from '@elastic/eui'; +import type { PackagePolicyClient } from '@kbn/fleet-plugin/server'; +import { + areResourcesSetup, + createDefaultSetupState, + ProfilingSetupOptions, + SetupState, +} from './setup'; + +export interface ProfilingCloudSetupOptions extends ProfilingSetupOptions { + packagePolicyClient: PackagePolicyClient; + isCloudEnabled: boolean; +} + +export interface CloudSetupStateType { + type: 'cloud'; + setupState: CloudSetupState; +} + +export interface CloudSetupState extends SetupState { + cloud: { + available: boolean; + required: boolean; + }; + policies: { + collector: { + installed: boolean; + }; + symbolizer: { + installed: boolean; + }; + apm: { + profilingEnabled: boolean; + }; + }; +} + +export type PartialCloudSetupState = RecursivePartial; + +export function createDefaultCloudSetupState(): CloudSetupState { + const defaultSetupState = createDefaultSetupState(); + return { + cloud: { + available: false, + required: true, + }, + policies: { + collector: { + installed: false, + }, + symbolizer: { + installed: false, + }, + apm: { + profilingEnabled: false, + }, + }, + ...defaultSetupState, + }; +} + +export function areCloudResourcesSetup(state: CloudSetupState): boolean { + return ( + areResourcesSetup(state) && + state.policies.collector.installed && + state.policies.symbolizer.installed && + !state.policies.apm.profilingEnabled + ); +} diff --git a/x-pack/plugins/profiling_data_access/common/cluster_settings.ts b/x-pack/plugins/profiling_data_access/common/cluster_settings.ts index a1e92ab1c996f..e1e65330f47d4 100644 --- a/x-pack/plugins/profiling_data_access/common/cluster_settings.ts +++ b/x-pack/plugins/profiling_data_access/common/cluster_settings.ts @@ -30,6 +30,7 @@ export async function validateResourceManagement({ enabled: statusResponse.resource_management.enabled, }, resources: { + // If the flag is true, that means that all index templates / data streams and indices have been created created: statusResponse.resources.created, pre_8_9_1_data: statusResponse.resources.pre_8_9_1_data, }, diff --git a/x-pack/plugins/profiling_data_access/common/fleet_policies.ts b/x-pack/plugins/profiling_data_access/common/fleet_policies.ts index d3a9b51dd55d5..ad599bb1e2551 100644 --- a/x-pack/plugins/profiling_data_access/common/fleet_policies.ts +++ b/x-pack/plugins/profiling_data_access/common/fleet_policies.ts @@ -9,7 +9,7 @@ import { SavedObjectsClientContract } from '@kbn/core/server'; import type { PackagePolicyClient } from '@kbn/fleet-plugin/server'; import { PACKAGE_POLICY_SAVED_OBJECT_TYPE, PackagePolicy } from '@kbn/fleet-plugin/common'; import { getApmPolicy } from './get_apm_policy'; -import { PartialSetupState, ProfilingSetupOptions } from './setup'; +import { PartialCloudSetupState, ProfilingCloudSetupOptions } from './cloud_setup'; export const COLLECTOR_PACKAGE_POLICY_NAME = 'elastic-universal-profiling-collector'; export const SYMBOLIZER_PACKAGE_POLICY_NAME = 'elastic-universal-profiling-symbolizer'; @@ -46,7 +46,7 @@ export async function getCollectorPolicy({ export async function validateCollectorPackagePolicy({ soClient, packagePolicyClient, -}: ProfilingSetupOptions): Promise { +}: ProfilingCloudSetupOptions): Promise { const collectorPolicy = await getCollectorPolicy({ soClient, packagePolicyClient }); return { policies: { collector: { installed: !!collectorPolicy } } }; } @@ -80,7 +80,7 @@ export async function getSymbolizerPolicy({ export async function validateSymbolizerPackagePolicy({ soClient, packagePolicyClient, -}: ProfilingSetupOptions): Promise { +}: ProfilingCloudSetupOptions): Promise { const symbolizerPackagePolicy = await getSymbolizerPolicy({ soClient, packagePolicyClient }); return { policies: { symbolizer: { installed: !!symbolizerPackagePolicy } } }; } @@ -88,7 +88,7 @@ export async function validateSymbolizerPackagePolicy({ export async function validateProfilingInApmPackagePolicy({ soClient, packagePolicyClient, -}: ProfilingSetupOptions): Promise { +}: ProfilingCloudSetupOptions): Promise { try { const apmPolicy = await getApmPolicy({ packagePolicyClient, soClient }); return { diff --git a/x-pack/plugins/profiling_data_access/common/has_profiling_data.ts b/x-pack/plugins/profiling_data_access/common/has_profiling_data.ts index 13ed8a2830543..1f62b6e1510a9 100644 --- a/x-pack/plugins/profiling_data_access/common/has_profiling_data.ts +++ b/x-pack/plugins/profiling_data_access/common/has_profiling_data.ts @@ -8,9 +8,9 @@ import { PartialSetupState, ProfilingSetupOptions } from './setup'; export async function hasProfilingData({ - client, + clientWithProfilingAuth, }: ProfilingSetupOptions): Promise { - const hasProfilingDataResponse = await client.search('has_any_profiling_data', { + const hasProfilingDataResponse = await clientWithProfilingAuth.search('has_any_profiling_data', { index: 'profiling*', size: 0, track_total_hits: 1, diff --git a/x-pack/plugins/profiling_data_access/common/index.ts b/x-pack/plugins/profiling_data_access/common/index.ts index 8e654f7a85144..8482620dcb474 100644 --- a/x-pack/plugins/profiling_data_access/common/index.ts +++ b/x-pack/plugins/profiling_data_access/common/index.ts @@ -14,3 +14,5 @@ export { COLLECTOR_PACKAGE_POLICY_NAME, SYMBOLIZER_PACKAGE_POLICY_NAME, } from './fleet_policies'; + +export type { ProfilingCloudSetupOptions } from './cloud_setup'; diff --git a/x-pack/plugins/profiling_data_access/common/profiling_es_client.ts b/x-pack/plugins/profiling_data_access/common/profiling_es_client.ts index 820aefea45090..ae25dfe57b3cd 100644 --- a/x-pack/plugins/profiling_data_access/common/profiling_es_client.ts +++ b/x-pack/plugins/profiling_data_access/common/profiling_es_client.ts @@ -23,7 +23,7 @@ export interface ProfilingESClient { query: QueryDslQueryContainer; sampleSize: number; }): Promise; - profilingStatus(): Promise; + profilingStatus(params?: { waitForResourcesCreated?: boolean }): Promise; getEsClient(): ElasticsearchClient; profilingFlamegraph({}: { query: QueryDslQueryContainer; diff --git a/x-pack/plugins/profiling_data_access/common/setup.test.ts b/x-pack/plugins/profiling_data_access/common/setup.test.ts index 48b8136e39020..01826ac7fa913 100644 --- a/x-pack/plugins/profiling_data_access/common/setup.test.ts +++ b/x-pack/plugins/profiling_data_access/common/setup.test.ts @@ -6,26 +6,16 @@ */ import { - areResourcesSetup, - createDefaultSetupState, mergePartialSetupStates, PartialSetupState, + areResourcesSetup, + createDefaultSetupState, } from './setup'; -const createCloudState = (available: boolean): PartialSetupState => ({ cloud: { available } }); const createDataState = (available: boolean): PartialSetupState => ({ data: { available } }); const createPermissionState = (configured: boolean): PartialSetupState => ({ permissions: { configured }, }); -const createCollectorPolicyState = (installed: boolean): PartialSetupState => ({ - policies: { collector: { installed } }, -}); -const createSymbolizerPolicyState = (installed: boolean): PartialSetupState => ({ - policies: { symbolizer: { installed } }, -}); -const createProfilingInApmPolicyState = (profilingEnabled: boolean): PartialSetupState => ({ - policies: { apm: { profilingEnabled } }, -}); function createResourceState({ enabled, @@ -56,30 +46,24 @@ describe('Merging partial state operations', () => { const defaultSetupState = createDefaultSetupState(); it('returns partial states with missing key', () => { - const mergedState = mergePartialSetupStates(defaultSetupState, [ - createCloudState(true), - createDataState(true), - ]); - - expect(mergedState.cloud.available).toEqual(true); - expect(mergedState.cloud.required).toEqual(true); + const mergedState = mergePartialSetupStates(defaultSetupState, [createDataState(true)]); expect(mergedState.data.available).toEqual(true); + expect(mergedState.settings.configured).toEqual(false); + expect(mergedState.permissions.configured).toEqual(false); + expect(mergedState.resources.created).toEqual(false); }); it('should deeply nested partial states with overlap', () => { const mergedState = mergePartialSetupStates(defaultSetupState, [ - createCollectorPolicyState(true), - createSymbolizerPolicyState(true), + createResourceState({ created: true, enabled: true }), ]); - expect(mergedState.policies.collector.installed).toEqual(true); - expect(mergedState.policies.symbolizer.installed).toEqual(true); + expect(mergedState.resource_management.enabled).toEqual(true); + expect(mergedState.resources.created).toEqual(true); }); + it('returns false when permission is not configured', () => { const mergedState = mergePartialSetupStates(defaultSetupState, [ - createCollectorPolicyState(true), - createSymbolizerPolicyState(true), - createProfilingInApmPolicyState(true), createResourceState({ enabled: true, created: true }), createSettingsState(true), createPermissionState(false), @@ -90,9 +74,6 @@ describe('Merging partial state operations', () => { it('returns false when resource management is not enabled', () => { const mergedState = mergePartialSetupStates(defaultSetupState, [ - createCollectorPolicyState(true), - createSymbolizerPolicyState(true), - createProfilingInApmPolicyState(true), createResourceState({ enabled: false, created: true }), createSettingsState(true), createPermissionState(true), @@ -103,9 +84,6 @@ describe('Merging partial state operations', () => { it('returns false when resources are not created', () => { const mergedState = mergePartialSetupStates(defaultSetupState, [ - createCollectorPolicyState(true), - createSymbolizerPolicyState(true), - createProfilingInApmPolicyState(true), createResourceState({ enabled: true, created: false }), createSettingsState(true), createPermissionState(true), @@ -116,9 +94,6 @@ describe('Merging partial state operations', () => { it('returns false when settings are not configured', () => { const mergedState = mergePartialSetupStates(defaultSetupState, [ - createCollectorPolicyState(true), - createSymbolizerPolicyState(true), - createProfilingInApmPolicyState(true), createResourceState({ enabled: true, created: true }), createSettingsState(false), createPermissionState(true), @@ -129,9 +104,6 @@ describe('Merging partial state operations', () => { it('returns true when all checks are valid', () => { const mergedState = mergePartialSetupStates(defaultSetupState, [ - createCollectorPolicyState(true), - createSymbolizerPolicyState(true), - createProfilingInApmPolicyState(false), createResourceState({ enabled: true, created: true }), createSettingsState(true), createPermissionState(true), @@ -139,43 +111,4 @@ describe('Merging partial state operations', () => { expect(areResourcesSetup(mergedState)).toBeTruthy(); }); - - it('returns false when collector is not found', () => { - const mergedState = mergePartialSetupStates(defaultSetupState, [ - createCollectorPolicyState(false), - createSymbolizerPolicyState(true), - createProfilingInApmPolicyState(false), - createResourceState({ enabled: true, created: true }), - createSettingsState(true), - createPermissionState(true), - ]); - - expect(areResourcesSetup(mergedState)).toBeFalsy(); - }); - - it('returns false when symbolizer is not found', () => { - const mergedState = mergePartialSetupStates(defaultSetupState, [ - createCollectorPolicyState(true), - createSymbolizerPolicyState(false), - createProfilingInApmPolicyState(false), - createResourceState({ enabled: true, created: true }), - createSettingsState(true), - createPermissionState(true), - ]); - - expect(areResourcesSetup(mergedState)).toBeFalsy(); - }); - - it('returns false when profiling is in APM server', () => { - const mergedState = mergePartialSetupStates(defaultSetupState, [ - createCollectorPolicyState(true), - createSymbolizerPolicyState(true), - createProfilingInApmPolicyState(true), - createResourceState({ enabled: true, created: true }), - createSettingsState(true), - createPermissionState(true), - ]); - - expect(areResourcesSetup(mergedState)).toBeFalsy(); - }); }); diff --git a/x-pack/plugins/profiling_data_access/common/setup.ts b/x-pack/plugins/profiling_data_access/common/setup.ts index d3411ea9ee020..934c425ed0af9 100644 --- a/x-pack/plugins/profiling_data_access/common/setup.ts +++ b/x-pack/plugins/profiling_data_access/common/setup.ts @@ -4,43 +4,31 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { RecursivePartial } from '@elastic/eui'; +import { RecursivePartial } from '@elastic/eui'; import { Logger, SavedObjectsClientContract } from '@kbn/core/server'; -import type { PackagePolicyClient } from '@kbn/fleet-plugin/server'; import { merge } from 'lodash'; import { ProfilingESClient } from './profiling_es_client'; export interface ProfilingSetupOptions { client: ProfilingESClient; + clientWithProfilingAuth: ProfilingESClient; soClient: SavedObjectsClientContract; - packagePolicyClient: PackagePolicyClient; logger: Logger; spaceId: string; - isCloudEnabled: boolean; +} + +export interface SetupStateType { + type: 'self-managed'; + setupState: SetupState; } export interface SetupState { - cloud: { - available: boolean; - required: boolean; - }; data: { available: boolean; }; permissions: { configured: boolean; }; - policies: { - collector: { - installed: boolean; - }; - symbolizer: { - installed: boolean; - }; - apm: { - profilingEnabled: boolean; - }; - }; resource_management: { enabled: boolean; }; @@ -57,27 +45,12 @@ export type PartialSetupState = RecursivePartial; export function createDefaultSetupState(): SetupState { return { - cloud: { - available: false, - required: true, - }, data: { available: false, }, permissions: { configured: false, }, - policies: { - collector: { - installed: false, - }, - symbolizer: { - installed: false, - }, - apm: { - profilingEnabled: false, - }, - }, resource_management: { enabled: false, }, @@ -93,9 +66,6 @@ export function createDefaultSetupState(): SetupState { export function areResourcesSetup(state: SetupState): boolean { return ( - state.policies.collector.installed && - state.policies.symbolizer.installed && - !state.policies.apm.profilingEnabled && state.resource_management.enabled && state.resources.created && state.permissions.configured && @@ -107,9 +77,9 @@ function mergeRecursivePartial(base: T, partial: RecursivePartial): T { return merge(base, partial); } -export function mergePartialSetupStates( - base: SetupState, - partials: PartialSetupState[] -): SetupState { - return partials.reduce(mergeRecursivePartial, base); +export function mergePartialSetupStates( + base: T, + partials: Array> +): T { + return partials.reduce(mergeRecursivePartial, base); } diff --git a/x-pack/plugins/profiling_data_access/kibana.jsonc b/x-pack/plugins/profiling_data_access/kibana.jsonc index 654cb93a0460c..a6bcd9f7ecff4 100644 --- a/x-pack/plugins/profiling_data_access/kibana.jsonc +++ b/x-pack/plugins/profiling_data_access/kibana.jsonc @@ -9,10 +9,8 @@ "configPath": ["xpack", "profiling"], "requiredPlugins": [ "data", - "fleet", - "cloud" ], - "optionalPlugins": [], + "optionalPlugins": ["cloud", "fleet"], "requiredBundles": [] } } diff --git a/x-pack/plugins/profiling_data_access/server/services/register_services.ts b/x-pack/plugins/profiling_data_access/server/services/register_services.ts index 095f8e1826794..60f582ac16b7e 100644 --- a/x-pack/plugins/profiling_data_access/server/services/register_services.ts +++ b/x-pack/plugins/profiling_data_access/server/services/register_services.ts @@ -10,9 +10,9 @@ import { ElasticsearchClient, Logger } from '@kbn/core/server'; import { FleetStartContract } from '@kbn/fleet-plugin/server'; import { createFetchFlamechart } from './fetch_flamechart'; import { createGetStatusService } from './status'; -import { createGetSetupState } from './get_setup_state'; import { ProfilingESClient } from '../../common/profiling_es_client'; import { createFetchFunctions } from './functions'; +import { createSetupState } from './setup_state'; export interface RegisterServicesParams { createProfilingEsClient: (params: { @@ -21,8 +21,8 @@ export interface RegisterServicesParams { }) => ProfilingESClient; logger: Logger; deps: { - fleet: FleetStartContract; - cloud: CloudStart; + fleet?: FleetStartContract; + cloud?: CloudStart; }; } @@ -30,7 +30,7 @@ export function registerServices(params: RegisterServicesParams) { return { fetchFlamechartData: createFetchFlamechart(params), getStatus: createGetStatusService(params), - getSetupState: createGetSetupState(params), + getSetupState: createSetupState(params), fetchFunction: createFetchFunctions(params), }; } diff --git a/x-pack/plugins/profiling_data_access/server/services/get_setup_state/index.ts b/x-pack/plugins/profiling_data_access/server/services/setup_state/cloud_setup_state.ts similarity index 55% rename from x-pack/plugins/profiling_data_access/server/services/get_setup_state/index.ts rename to x-pack/plugins/profiling_data_access/server/services/setup_state/cloud_setup_state.ts index 9110883900c27..ed05677d21dfb 100644 --- a/x-pack/plugins/profiling_data_access/server/services/get_setup_state/index.ts +++ b/x-pack/plugins/profiling_data_access/server/services/setup_state/cloud_setup_state.ts @@ -5,6 +5,9 @@ * 2.0. */ +import { RecursivePartial } from '@elastic/eui'; +import { ProfilingCloudSetupOptions } from '../../../common'; +import { CloudSetupState, createDefaultCloudSetupState } from '../../../common/cloud_setup'; import { validateMaximumBuckets, validateResourceManagement, @@ -15,21 +18,14 @@ import { validateSymbolizerPackagePolicy, } from '../../../common/fleet_policies'; import { hasProfilingData } from '../../../common/has_profiling_data'; -import { ProfilingESClient } from '../../../common/profiling_es_client'; import { validateSecurityRole } from '../../../common/security_role'; -import { - ProfilingSetupOptions, - createDefaultSetupState, - mergePartialSetupStates, -} from '../../../common/setup'; -import { RegisterServicesParams } from '../register_services'; +import { mergePartialSetupStates } from '../../../common/setup'; -export async function getSetupState( - options: ProfilingSetupOptions, - clientWithProfilingAuth: ProfilingESClient -) { - const state = createDefaultSetupState(); - state.cloud.available = options.isCloudEnabled; +export async function cloudSetupState( + params: ProfilingCloudSetupOptions +): Promise { + const state = createDefaultCloudSetupState(); + state.cloud.available = params.isCloudEnabled; const verifyFunctions = [ validateMaximumBuckets, @@ -38,19 +34,12 @@ export async function getSetupState( validateCollectorPackagePolicy, validateSymbolizerPackagePolicy, validateProfilingInApmPackagePolicy, + hasProfilingData, ]; - const partialStates = await Promise.all([ - ...verifyFunctions.map((fn) => fn(options)), - hasProfilingData({ - ...options, - client: clientWithProfilingAuth, - }), - ]); + const partialStates = (await Promise.all(verifyFunctions.map((fn) => fn(params)))) as Array< + RecursivePartial + >; return mergePartialSetupStates(state, partialStates); } - -export function createGetSetupState(params: RegisterServicesParams) { - return getSetupState; -} diff --git a/x-pack/plugins/profiling_data_access/server/services/setup_state/index.ts b/x-pack/plugins/profiling_data_access/server/services/setup_state/index.ts new file mode 100644 index 0000000000000..99d81ab771793 --- /dev/null +++ b/x-pack/plugins/profiling_data_access/server/services/setup_state/index.ts @@ -0,0 +1,77 @@ +/* + * 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 { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; +import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; +import { CloudSetupStateType } from '../../../common/cloud_setup'; +import { SetupStateType } from '../../../common/setup'; +import { RegisterServicesParams } from '../register_services'; +import { cloudSetupState } from './cloud_setup_state'; +import { selfManagedSetupState } from './self_managed_setup_state'; + +export interface SetupStateParams { + soClient: SavedObjectsClientContract; + esClient: ElasticsearchClient; + spaceId?: string; +} + +export async function getSetupState({ + createProfilingEsClient, + deps, + esClient, + logger, + soClient, + spaceId, +}: RegisterServicesParams & SetupStateParams): Promise { + const clientWithDefaultAuth = createProfilingEsClient({ + esClient, + useDefaultAuth: true, + }); + const clientWithProfilingAuth = createProfilingEsClient({ + esClient, + useDefaultAuth: false, + }); + + const isCloudEnabled = deps.cloud?.isCloudEnabled; + if (isCloudEnabled) { + if (!deps.fleet) { + throw new Error('Elastic Fleet is required to set up Universal Profiling on Cloud'); + } + + const setupState = await cloudSetupState({ + client: clientWithDefaultAuth, + clientWithProfilingAuth, + logger, + soClient, + spaceId: spaceId ?? DEFAULT_SPACE_ID, + packagePolicyClient: deps.fleet.packagePolicyService, + isCloudEnabled, + }); + + return { + type: 'cloud', + setupState, + }; + } + + const setupState = await selfManagedSetupState({ + client: clientWithDefaultAuth, + clientWithProfilingAuth, + logger, + soClient, + spaceId: spaceId ?? DEFAULT_SPACE_ID, + }); + + return { + type: 'self-managed', + setupState, + }; +} + +export function createSetupState(params: RegisterServicesParams) { + return async ({ esClient, soClient, spaceId }: SetupStateParams) => + getSetupState({ ...params, esClient, soClient, spaceId }); +} diff --git a/x-pack/plugins/profiling_data_access/server/services/setup_state/self_managed_setup_state.ts b/x-pack/plugins/profiling_data_access/server/services/setup_state/self_managed_setup_state.ts new file mode 100644 index 0000000000000..062a75f0f1f02 --- /dev/null +++ b/x-pack/plugins/profiling_data_access/server/services/setup_state/self_managed_setup_state.ts @@ -0,0 +1,34 @@ +/* + * 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 { + validateMaximumBuckets, + validateResourceManagement, +} from '../../../common/cluster_settings'; +import { hasProfilingData } from '../../../common/has_profiling_data'; +import { validateSecurityRole } from '../../../common/security_role'; +import { + createDefaultSetupState, + mergePartialSetupStates, + ProfilingSetupOptions, + SetupState, +} from '../../../common/setup'; + +export async function selfManagedSetupState(params: ProfilingSetupOptions): Promise { + const state = createDefaultSetupState(); + + const verifyFunctions = [ + validateMaximumBuckets, + validateResourceManagement, + validateSecurityRole, + hasProfilingData, + ]; + + const partialStates = await Promise.all(verifyFunctions.map((fn) => fn(params))); + + return mergePartialSetupStates(state, partialStates); +} diff --git a/x-pack/plugins/profiling_data_access/server/services/status/index.ts b/x-pack/plugins/profiling_data_access/server/services/status/index.ts index 31581140b12e0..0e32989ea8828 100644 --- a/x-pack/plugins/profiling_data_access/server/services/status/index.ts +++ b/x-pack/plugins/profiling_data_access/server/services/status/index.ts @@ -7,10 +7,10 @@ import { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; import { ProfilingStatus } from '@kbn/profiling-utils'; -import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; -import { getSetupState } from '../get_setup_state'; +import { areCloudResourcesSetup } from '../../../common/cloud_setup'; +import { areResourcesSetup } from '../../../common/setup'; import { RegisterServicesParams } from '../register_services'; -import { ProfilingSetupOptions, areResourcesSetup } from '../../../common/setup'; +import { getSetupState } from '../setup_state'; export interface HasSetupParams { soClient: SavedObjectsClientContract; @@ -18,45 +18,16 @@ export interface HasSetupParams { spaceId?: string; } -export function createGetStatusService({ - createProfilingEsClient, - deps, - logger, -}: RegisterServicesParams) { +export function createGetStatusService(params: RegisterServicesParams) { return async ({ esClient, soClient, spaceId }: HasSetupParams): Promise => { try { - const isCloudEnabled = deps.cloud.isCloudEnabled; - if (!isCloudEnabled) { - // When not on cloud just return that is has not set up and has no data - return { - has_setup: false, - has_data: false, - pre_8_9_1_data: false, - }; - } - - const clientWithDefaultAuth = createProfilingEsClient({ - esClient, - useDefaultAuth: true, - }); - const clientWithProfilingAuth = createProfilingEsClient({ - esClient, - useDefaultAuth: false, - }); - - const setupOptions: ProfilingSetupOptions = { - client: clientWithDefaultAuth, - logger, - packagePolicyClient: deps.fleet.packagePolicyService, - soClient, - spaceId: spaceId ?? DEFAULT_SPACE_ID, - isCloudEnabled, - }; + const { type, setupState } = await getSetupState({ ...params, esClient, soClient, spaceId }); - const setupState = await getSetupState(setupOptions, clientWithProfilingAuth); + params.logger.debug(`Set up state for: ${type}: ${JSON.stringify(setupState, null, 2)}`); return { - has_setup: areResourcesSetup(setupState), + has_setup: + type === 'cloud' ? areCloudResourcesSetup(setupState) : areResourcesSetup(setupState), has_data: setupState.data.available, pre_8_9_1_data: setupState.resources.pre_8_9_1_data, }; diff --git a/x-pack/plugins/profiling_data_access/server/types.ts b/x-pack/plugins/profiling_data_access/server/types.ts index 4092b18c6b953..f7adb62b63b19 100644 --- a/x-pack/plugins/profiling_data_access/server/types.ts +++ b/x-pack/plugins/profiling_data_access/server/types.ts @@ -8,6 +8,6 @@ import { CloudStart } from '@kbn/cloud-plugin/server'; import { FleetStartContract } from '@kbn/fleet-plugin/server'; export interface ProfilingPluginStartDeps { - fleet: FleetStartContract; - cloud: CloudStart; + fleet?: FleetStartContract; + cloud?: CloudStart; } diff --git a/x-pack/plugins/profiling_data_access/server/utils/create_profiling_es_client.ts b/x-pack/plugins/profiling_data_access/server/utils/create_profiling_es_client.ts index 258b18c3e44f9..617e27a458b72 100644 --- a/x-pack/plugins/profiling_data_access/server/utils/create_profiling_es_client.ts +++ b/x-pack/plugins/profiling_data_access/server/utils/create_profiling_es_client.ts @@ -60,14 +60,16 @@ export function createProfilingEsClient({ return unwrapEsResponse(promise) as Promise; }, - profilingStatus() { + profilingStatus({ waitForResourcesCreated = false } = {}) { const controller = new AbortController(); const promise = withProfilingSpan('_profiling/status', () => { return esClient.transport.request( { method: 'GET', - path: encodeURI('/_profiling/status'), + path: encodeURI( + `/_profiling/status?wait_for_resources_created=${waitForResourcesCreated}` + ), }, { signal: controller.signal, diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index bd3f3f1395d8b..25530be35e9a7 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -39413,4 +39413,4 @@ "xpack.painlessLab.walkthroughButtonLabel": "Présentation", "xpack.serverlessObservability.nav.getStarted": "Démarrer" } -} +} \ No newline at end of file diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 938c92c1aa10e..3fa06a6d8c35f 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -39404,4 +39404,4 @@ "xpack.painlessLab.walkthroughButtonLabel": "実地検証", "xpack.serverlessObservability.nav.getStarted": "使ってみる" } -} +} \ No newline at end of file diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 74f0152cb1dfc..2c57adbd57df1 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -39398,4 +39398,4 @@ "xpack.painlessLab.walkthroughButtonLabel": "指导", "xpack.serverlessObservability.nav.getStarted": "开始使用" } -} +} \ No newline at end of file From 31accd60a034d4d50ecd961adf086a06a3a6008a Mon Sep 17 00:00:00 2001 From: Antonio Date: Wed, 25 Oct 2023 12:33:50 +0200 Subject: [PATCH 12/24] [Cases] Change the error message for missing required custom fields. (#169758) ## Summary Updated the error message when the user tried to update a case with missing required custom fields.
BeforeScreenshot
2023-10-24 at 14 42 18
After Screenshot 2023-10-25 at 10 38 14
--- .../cases/server/client/cases/create.test.ts | 12 ++++++------ .../cases/server/client/cases/update.test.ts | 4 ++-- .../server/client/cases/validators.test.ts | 18 +++++++++++------- .../cases/server/client/cases/validators.ts | 8 +++++--- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/cases/server/client/cases/create.test.ts b/x-pack/plugins/cases/server/client/cases/create.test.ts index be58f866cb556..b7cc876c47655 100644 --- a/x-pack/plugins/cases/server/client/cases/create.test.ts +++ b/x-pack/plugins/cases/server/client/cases/create.test.ts @@ -550,7 +550,7 @@ describe('create', () => { { key: 'first_key', type: CustomFieldTypes.TEXT, - label: 'foo', + label: 'missing field 1', required: true, }, { @@ -566,7 +566,7 @@ describe('create', () => { await expect( create({ ...theCase }, clientArgs, casesClient) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Failed to create case: Error: Missing required custom fields: first_key"` + `"Failed to create case: Error: Missing required custom fields: \\"missing field 1\\""` ); }); @@ -578,13 +578,13 @@ describe('create', () => { { key: 'first_key', type: CustomFieldTypes.TEXT, - label: 'foo', + label: 'missing field 1', required: true, }, { key: 'second_key', type: CustomFieldTypes.TOGGLE, - label: 'foo', + label: 'missing field 2', required: true, }, ], @@ -612,7 +612,7 @@ describe('create', () => { casesClient ) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Failed to create case: Error: Missing required custom fields: first_key,second_key"` + `"Failed to create case: Error: Missing required custom fields: \\"missing field 1\\", \\"missing field 2\\""` ); }); @@ -695,7 +695,7 @@ describe('create', () => { casesClient ) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Failed to create case: Error: Missing required custom fields: first_key"` + `"Failed to create case: Error: Missing required custom fields: \\"missing field 1\\""` ); }); diff --git a/x-pack/plugins/cases/server/client/cases/update.test.ts b/x-pack/plugins/cases/server/client/cases/update.test.ts index f6d1640fe9448..94d4767e93b25 100644 --- a/x-pack/plugins/cases/server/client/cases/update.test.ts +++ b/x-pack/plugins/cases/server/client/cases/update.test.ts @@ -920,7 +920,7 @@ describe('update', () => { { key: 'first_key', type: CustomFieldTypes.TEXT, - label: 'foo', + label: 'missing field 1', required: true, }, { @@ -1156,7 +1156,7 @@ describe('update', () => { casesClient ) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Failed to update case, ids: [{\\"id\\":\\"mock-id-1\\",\\"version\\":\\"WzAsMV0=\\"}]: Error: Missing required custom fields: first_key"` + `"Failed to update case, ids: [{\\"id\\":\\"mock-id-1\\",\\"version\\":\\"WzAsMV0=\\"}]: Error: Missing required custom fields: \\"missing field 1\\""` ); }); diff --git a/x-pack/plugins/cases/server/client/cases/validators.test.ts b/x-pack/plugins/cases/server/client/cases/validators.test.ts index b90f579435093..888852e6f90ed 100644 --- a/x-pack/plugins/cases/server/client/cases/validators.test.ts +++ b/x-pack/plugins/cases/server/client/cases/validators.test.ts @@ -355,7 +355,7 @@ describe('validators', () => { { key: 'first_key', type: CustomFieldTypes.TEXT, - label: 'foo', + label: 'missing field 1', required: true, }, { @@ -370,7 +370,9 @@ describe('validators', () => { requestCustomFields, customFieldsConfiguration, }) - ).toThrowErrorMatchingInlineSnapshot(`"Missing required custom fields: first_key"`); + ).toThrowErrorMatchingInlineSnapshot( + `"Missing required custom fields: \\"missing field 1\\""` + ); }); it('throws if required custom fields have null value', () => { @@ -385,13 +387,13 @@ describe('validators', () => { { key: 'first_key', type: CustomFieldTypes.TEXT, - label: 'foo', + label: 'missing field 1', required: true, }, { key: 'second_key', type: CustomFieldTypes.TOGGLE, - label: 'foo', + label: 'missing field 2', required: true, }, ]; @@ -401,7 +403,7 @@ describe('validators', () => { customFieldsConfiguration, }) ).toThrowErrorMatchingInlineSnapshot( - `"Missing required custom fields: first_key,second_key"` + `"Missing required custom fields: \\"missing field 1\\", \\"missing field 2\\""` ); }); @@ -425,7 +427,7 @@ describe('validators', () => { { key: 'first_key', type: CustomFieldTypes.TEXT, - label: 'foo', + label: 'missing field 1', required: true, }, ]; @@ -433,7 +435,9 @@ describe('validators', () => { validateRequiredCustomFields({ customFieldsConfiguration, }) - ).toThrowErrorMatchingInlineSnapshot(`"Missing required custom fields: first_key"`); + ).toThrowErrorMatchingInlineSnapshot( + `"Missing required custom fields: \\"missing field 1\\""` + ); }); }); }); diff --git a/x-pack/plugins/cases/server/client/cases/validators.ts b/x-pack/plugins/cases/server/client/cases/validators.ts index 87e677dca457b..7817e6646695a 100644 --- a/x-pack/plugins/cases/server/client/cases/validators.ts +++ b/x-pack/plugins/cases/server/client/cases/validators.ts @@ -115,7 +115,7 @@ export const validateRequiredCustomFields = ({ requiredCustomFields, requestCustomFields ?? [], (requiredVal, requestedVal) => requiredVal.key === requestedVal.key - ).map((e) => e.key); + ).map((e) => `"${e.label}"`); requiredCustomFields.forEach((requiredField) => { const found = requestCustomFields?.find( @@ -123,11 +123,13 @@ export const validateRequiredCustomFields = ({ ); if (found && found.value === null) { - missingRequiredCustomFields.push(found.key); + missingRequiredCustomFields.push(`"${requiredField.label}"`); } }); if (missingRequiredCustomFields.length) { - throw Boom.badRequest(`Missing required custom fields: ${missingRequiredCustomFields}`); + throw Boom.badRequest( + `Missing required custom fields: ${missingRequiredCustomFields.join(', ')}` + ); } }; From 9505fa97540a0124258ef528d22e2cc60290ddf3 Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Wed, 25 Oct 2023 12:34:47 +0200 Subject: [PATCH 13/24] [Ops] create-deploy-tag workflow: Add link to deployed commits overview (#169496) --- .github/workflows/create-deploy-tag.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/create-deploy-tag.yml b/.github/workflows/create-deploy-tag.yml index 6488aa0dc6417..7ce79f2bd1c07 100644 --- a/.github/workflows/create-deploy-tag.yml +++ b/.github/workflows/create-deploy-tag.yml @@ -48,6 +48,10 @@ jobs: echo "This workflow can only be run on the main branch" exit 1 fi + - name: Find previous tag + run: | + prev_tag_name=`git tag -l 'deploy@[0-9]*' | tail -1` + echo "PREV_TAG_NAME=${prev_tag_name}" >> "${GITHUB_ENV}" - name: Prepare tag run: | tag_name="deploy@$(date +%s)" @@ -103,7 +107,7 @@ jobs: JSON_USEFUL_LINKS_ARRAY: | [ "*Useful links:*\\n", - "", + "", "", " (use Elastic Cloud Staging VPN)", "", @@ -152,6 +156,5 @@ jobs: JSON_USEFUL_LINKS_ARRAY: | [ "*Useful links:*\\n", - "", "" ] From aff871a84a504cb0b4f7fa29e13f670ab75b447a Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Wed, 25 Oct 2023 11:45:28 +0100 Subject: [PATCH 14/24] [ML] Always sync before serverless trained model tests (#169658) Ensuring saved objects are synced before running the tests for the trained models. This should fix the occasional test failure where the build in `lang_ident_model_1` is missing from the list due to a sync being needed. Flaky test runner, all have passed. https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3703 --- .../functional/test_suites/search/ml/trained_models_list.ts | 1 + .../functional/test_suites/security/ml/trained_models_list.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/x-pack/test_serverless/functional/test_suites/search/ml/trained_models_list.ts b/x-pack/test_serverless/functional/test_suites/search/ml/trained_models_list.ts index 22adf85dc3926..cc2a98291923f 100644 --- a/x-pack/test_serverless/functional/test_suites/search/ml/trained_models_list.ts +++ b/x-pack/test_serverless/functional/test_suites/search/ml/trained_models_list.ts @@ -13,6 +13,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('Trained models list', () => { before(async () => { await PageObjects.svlCommonPage.login(); + await ml.api.syncSavedObjects(); }); after(async () => { diff --git a/x-pack/test_serverless/functional/test_suites/security/ml/trained_models_list.ts b/x-pack/test_serverless/functional/test_suites/security/ml/trained_models_list.ts index 19b1430dda1ff..92bdbd6ffab65 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ml/trained_models_list.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ml/trained_models_list.ts @@ -14,6 +14,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('Trained models list', () => { before(async () => { await PageObjects.svlCommonPage.login(); + await ml.api.syncSavedObjects(); }); after(async () => { From 560006f44491ae022077aced542fd114c30eee07 Mon Sep 17 00:00:00 2001 From: Rudolf Meijering Date: Wed, 25 Oct 2023 13:08:13 +0200 Subject: [PATCH 15/24] Reindexing into a new index might convince ES it cannot complete in 0s (#169650) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Fixes https://github.com/elastic/kibana/issues/166190 🤞 We were reusing an existing index as the reindex target. Maybe if we force ES to create a new index it would take a bit more time and stop the flakiness. ### Checklist Delete any items that are not applicable to this PR. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- .../migrations/group3/actions/actions_test_suite.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions_test_suite.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions_test_suite.ts index ccdf39133f93e..682478f8d5f83 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions_test_suite.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions_test_suite.ts @@ -1155,7 +1155,7 @@ export const runActionTestSuite = ({ const res = (await reindex({ client, sourceIndex: 'existing_index_with_100k_docs', - targetIndex: 'reindex_target', + targetIndex: 'reindex_target_7', reindexScript: Option.none, requireAlias: false, excludeOnUpgradeQuery: { match_all: {} }, From eaddc54f6c088abd7278c6da3cdabeaf39801924 Mon Sep 17 00:00:00 2001 From: Mark Hopkin Date: Wed, 25 Oct 2023 12:53:51 +0100 Subject: [PATCH 16/24] [Fleet] Output secrets UI (#169429) ## Summary Continuation of #169221. Part of https://github.com/elastic/kibana/issues/157458 _Note: The experimental feature flag `outputSecretsStorage` must be enabled to see these changes._ Introduces the UI components to create and edit output secrets, currently there are only 3 output secrets: - Kafka output password - Kafka output SSL key - Logstash output SSL key Some key behaviours of the new UI: - on creating an output, the user can opt to revert to using plain text values if they want - once an output has been created with a secret, when editing the output, the secret values can only be replaced, never viewed - If an output uses plain values, there currently isn't a way to convert to using secrets. **Create** Screenshot 2023-10-24 at 14 48 49 **Edit** https://github.com/elastic/kibana/assets/3315046/d8d44911-81d3-4a06-a0ff-ece981a36496 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../components/edit_output_flyout/index.tsx | 81 +++++++--- .../edit_output_flyout/output_form_kafka.tsx | 10 +- .../output_form_kafka_authentication.tsx | 116 ++++++++++---- .../output_form_secret_form_row.test.tsx | 70 +++++++++ .../output_form_secret_form_row.tsx | 144 ++++++++++++++++++ .../output_form_validators.tsx | 14 ++ .../edit_output_flyout/use_output_form.tsx | 96 +++++++++++- .../plugins/fleet/public/hooks/use_input.ts | 66 ++++++++ .../plugins/fleet/server/services/secrets.ts | 2 +- 9 files changed, 535 insertions(+), 64 deletions(-) create mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_secret_form_row.test.tsx create mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_secret_form_row.tsx diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/index.tsx index 05f44bb8b92a9..bbf0ca39ffd41 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/index.tsx @@ -7,6 +7,7 @@ import React, { useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; + import { EuiFlyout, EuiFlyoutBody, @@ -44,13 +45,14 @@ import { FLYOUT_MAX_WIDTH } from '../../constants'; import { LogstashInstructions } from '../logstash_instructions'; import { useBreadcrumbs, useStartServices } from '../../../../hooks'; +import { SecretFormRow } from './output_form_secret_form_row'; + import { OutputFormKafkaSection } from './output_form_kafka'; import { YamlCodeEditorWithPlaceholder } from './yaml_code_editor_with_placeholder'; import { useOutputForm } from './use_output_form'; import { EncryptionKeyRequiredCallout } from './encryption_key_required_callout'; import { AdvancedOptionsSection } from './advanced_options_section'; - export interface EditOutputFlyoutProps { output?: Output; onClose: () => void; @@ -67,6 +69,12 @@ export const EditOutputFlyout: React.FunctionComponent = const inputs = form.inputs; const { docLinks } = useStartServices(); const { euiTheme } = useEuiTheme(); + const { outputSecretsStorage: isOutputSecretsStorageEnabled } = ExperimentalFeaturesService.get(); + const [useSecretsStorage, setUseSecretsStorage] = React.useState(isOutputSecretsStorageEnabled); + + const onUsePlainText = () => { + setUseSecretsStorage(false); + }; const proxiesOptions = useMemo( () => proxies.map((proxy) => ({ value: proxy.id, label: proxy.name })), @@ -161,28 +169,51 @@ export const EditOutputFlyout: React.FunctionComponent = )} /> - + } + {...inputs.sslKeyInput.formRowProps} + > + - } - {...inputs.sslKeyInput.formRowProps} - > - + ) : ( + - + title={i18n.translate('xpack.fleet.settings.editOutputFlyout.sslKeySecretInputTitle', { + defaultMessage: 'Client SSL certificate key', + })} + {...inputs.sslKeySecretInput.formRowProps} + onUsePlainText={onUsePlainText} + > + + + )} ); }; @@ -231,7 +262,13 @@ export const EditOutputFlyout: React.FunctionComponent = const renderKafkaSection = () => { if (isKafkaOutputEnabled) { - return ; + return ( + + ); } return null; }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_kafka.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_kafka.tsx index 18a25601e1836..66493b328697c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_kafka.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_kafka.tsx @@ -32,10 +32,12 @@ import type { OutputFormInputsType } from './use_output_form'; interface Props { inputs: OutputFormInputsType; + useSecretsStorage: boolean; + onUsePlainText: () => void; } export const OutputFormKafkaSection: React.FunctionComponent = (props) => { - const { inputs } = props; + const { inputs, useSecretsStorage, onUsePlainText } = props; const { docLinks } = useStartServices(); @@ -104,7 +106,11 @@ export const OutputFormKafkaSection: React.FunctionComponent = (props) => /> - + diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_kafka_authentication.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_kafka_authentication.tsx index 42a7b66727597..30665eb0e7c44 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_kafka_authentication.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_kafka_authentication.tsx @@ -31,6 +31,7 @@ import { } from '../../../../../../../common/constants'; import type { OutputFormInputsType } from './use_output_form'; +import { SecretFormRow } from './output_form_secret_form_row'; const kafkaSaslOptions = [ { @@ -70,8 +71,10 @@ const kafkaAuthenticationsOptions = [ export const OutputFormKafkaAuthentication: React.FunctionComponent<{ inputs: OutputFormInputsType; + useSecretsStorage: boolean; + onUsePlainText: () => void; }> = (props) => { - const { inputs } = props; + const { inputs, useSecretsStorage, onUsePlainText } = props; const kafkaVerificationModeOptions = useMemo( () => @@ -145,28 +148,54 @@ export const OutputFormKafkaAuthentication: React.FunctionComponent<{ )} /> - + } + {...inputs.kafkaSslKeyInput.formRowProps} + > + - } - {...inputs.kafkaSslKeyInput.formRowProps} - > - + ) : ( + - + {...inputs.kafkaSslKeySecretInput.formRowProps} + onUsePlainText={onUsePlainText} + > + + + )} ); default: @@ -189,23 +218,44 @@ export const OutputFormKafkaAuthentication: React.FunctionComponent<{ {...inputs.kafkaAuthUsernameInput.props} /> - + } + {...inputs.kafkaAuthPasswordInput.formRowProps} + > + - } - {...inputs.kafkaAuthPasswordInput.formRowProps} - > - + ) : ( + - + title={i18n.translate( + 'xpack.fleet.settings.editOutputFlyout.kafkaPasswordInputtitle', + { + defaultMessage: 'Password', + } + )} + {...inputs.kafkaAuthPasswordSecretInput.formRowProps} + onUsePlainText={onUsePlainText} + > + + + )} { + const title = 'Test Secret'; + const initialValue = 'initial value'; + const clear = jest.fn(); + const onUsePlainText = jest.fn(); + + it('should switch to edit mode when the replace button is clicked', () => { + const { getByText, queryByText, container } = render( + + + + ); + + expect(container.querySelector('#myinput')).not.toBeInTheDocument(); + + fireEvent.click(getByText('Replace Test Secret')); + + expect(container.querySelector('#myinput')).toBeInTheDocument(); + expect(getByText(title)).toBeInTheDocument(); + expect(queryByText('Replace Test Secret')).not.toBeInTheDocument(); + expect(queryByText(initialValue)).not.toBeInTheDocument(); + }); + + it('should call the clear function when the cancel button is clicked', () => { + const { getByText } = render( + + + + ); + + fireEvent.click(getByText('Replace Test Secret')); + fireEvent.click(getByText('Cancel Test Secret change')); + + expect(clear).toHaveBeenCalled(); + }); + + it('should call the onUsePlainText function when the revert link is clicked', () => { + const { getByText } = render( + + + + ); + + fireEvent.click(getByText('Click to use plain text storage instead')); + + expect(onUsePlainText).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_secret_form_row.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_secret_form_row.tsx new file mode 100644 index 0000000000000..fc55835557403 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_secret_form_row.tsx @@ -0,0 +1,144 @@ +/* + * 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 { + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiIcon, + EuiPanel, + EuiSpacer, + EuiText, + EuiToolTip, +} from '@elastic/eui'; +import React, { useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { i18n } from '@kbn/i18n'; + +export const SecretFormRow: React.FC<{ + fullWidth?: boolean; + children: ConstructorParameters[0]['children']; + error?: string[]; + isInvalid?: boolean; + title: string; + clear: () => void; + initialValue?: any; + onUsePlainText: () => void; +}> = ({ fullWidth, error, isInvalid, children, clear, title, initialValue, onUsePlainText }) => { + const hasInitialValue = initialValue !== undefined; + const [editMode, setEditMode] = useState(!initialValue); + const valueHiddenPanel = ( + + + + + + setEditMode(true)} + color="primary" + iconType="refresh" + iconSide="left" + size="xs" + > + + + + ); + + const cancelButton = ( + { + setEditMode(false); + clear(); + }} + color="primary" + iconType="refresh" + iconSide="left" + size="xs" + > + + + ); + + const editValue = ( + <> + {children} + {hasInitialValue && ( + + {cancelButton} + + )} + + ); + + const label = ( + + +   + {title} +   + + + + + ); + + const helpText = !initialValue ? ( + + + + ), + }} + /> + ) : undefined; + + const inputComponent = editMode ? editValue : valueHiddenPanel; + + return ( + + {inputComponent} + + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_validators.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_validators.tsx index dec10c9e88cd8..f163c5bd9950a 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_validators.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_validators.tsx @@ -8,6 +8,16 @@ import { i18n } from '@kbn/i18n'; import { safeLoad } from 'js-yaml'; +const toSecretValidator = + (validator: (value: string) => string[] | undefined) => + (value: string | { id: string } | undefined) => { + if (!value || typeof value === 'object') { + return undefined; + } + + return validator(value); + }; + export function validateKafkaHosts(value: string[]) { const res: Array<{ message: string; index?: number }> = []; const urlIndexes: { [key: string]: number[] } = {}; @@ -237,6 +247,8 @@ export function validateKafkaPassword(value: string) { } } +export const validateKafkaPasswordSecret = toSecretValidator(validateKafkaPassword); + export function validateCATrustedFingerPrint(value: string) { if (value !== '' && !value.match(/^[a-zA-Z0-9]+$/)) { return [ @@ -268,6 +280,8 @@ export function validateSSLKey(value: string) { } } +export const validateSSLKeySecret = toSecretValidator(validateSSLKey); + export function validateKafkaDefaultTopic(value: string) { if (!value || value === '') { return [ diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/use_output_form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/use_output_form.tsx index 3f650734dff80..282b3433268ec 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/use_output_form.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/use_output_form.tsx @@ -32,6 +32,7 @@ import { sendPostOutput, useComboInput, useInput, + useSecretInput, useNumberInput, useSelectInput, useSwitchInput, @@ -54,8 +55,10 @@ import { validateCATrustedFingerPrint, validateSSLCertificate, validateSSLKey, + validateSSLKeySecret, validateKafkaUsername, validateKafkaPassword, + validateKafkaPasswordSecret, validateKafkaHeaders, validateKafkaDefaultTopic, validateKafkaTopics, @@ -84,6 +87,7 @@ export interface OutputFormInputsType { caTrustedFingerprintInput: ReturnType; sslCertificateInput: ReturnType; sslKeyInput: ReturnType; + sslKeySecretInput: ReturnType; sslCertificateAuthoritiesInput: ReturnType; proxyIdInput: ReturnType; loadBalanceEnabledInput: ReturnType; @@ -98,6 +102,7 @@ export interface OutputFormInputsType { kafkaSaslMechanismInput: ReturnType; kafkaAuthUsernameInput: ReturnType; kafkaAuthPasswordInput: ReturnType; + kafkaAuthPasswordSecretInput: ReturnType; kafkaPartitionTypeInput: ReturnType; kafkaPartitionTypeRandomInput: ReturnType; kafkaPartitionTypeHashInput: ReturnType; @@ -115,9 +120,34 @@ export interface OutputFormInputsType { kafkaKeyInput: ReturnType; kafkaSslCertificateInput: ReturnType; kafkaSslKeyInput: ReturnType; + kafkaSslKeySecretInput: ReturnType; kafkaSslCertificateAuthoritiesInput: ReturnType; } +function extractKafkaOutputSecrets( + inputs: Pick< + OutputFormInputsType, + | 'kafkaSslKeyInput' + | 'kafkaSslKeySecretInput' + | 'kafkaAuthPasswordInput' + | 'kafkaAuthPasswordSecretInput' + > +): KafkaOutput['secrets'] | null { + const secrets: KafkaOutput['secrets'] = {}; + + if (!inputs.kafkaSslKeyInput.value && inputs.kafkaSslKeySecretInput.value) { + secrets.ssl = { + key: inputs.kafkaSslKeySecretInput.value, + }; + } + + if (!inputs.kafkaAuthPasswordInput.value && inputs.kafkaAuthPasswordSecretInput.value) { + secrets.password = inputs.kafkaAuthPasswordSecretInput.value; + } + + return Object.keys(secrets).length ? secrets : null; +} + export function useOutputForm(onSucess: () => void, output?: Output) { const fleetStatus = useFleetStatus(); @@ -250,6 +280,12 @@ export function useOutputForm(onSucess: () => void, output?: Output) { ); const sslKeyInput = useInput(output?.ssl?.key ?? '', validateSSLKey, isSSLEditable); + const sslKeySecretInput = useSecretInput( + output?.secrets?.ssl?.key, + validateSSLKeySecret, + isSSLEditable + ); + const proxyIdInput = useInput(output?.proxy_id ?? '', () => undefined, isDisabled('proxy_id')); /** @@ -309,6 +345,12 @@ export function useOutputForm(onSucess: () => void, output?: Output) { isDisabled('password') ); + const kafkaAuthPasswordSecretInput = useSecretInput( + kafkaOutput?.secrets?.password, + kafkaAuthMethodInput.value === kafkaAuthType.Userpass ? validateKafkaPasswordSecret : undefined, + isDisabled('password') + ); + const kafkaSslCertificateAuthoritiesInput = useComboInput( 'kafkaSslCertificateAuthoritiesComboBox', kafkaOutput?.ssl?.certificate_authorities ?? [], @@ -326,6 +368,12 @@ export function useOutputForm(onSucess: () => void, output?: Output) { isSSLEditable ); + const kafkaSslKeySecretInput = useSecretInput( + kafkaOutput?.ssl?.certificate, + kafkaAuthMethodInput.value === kafkaAuthType.Ssl ? validateSSLKeySecret : undefined, + isSSLEditable + ); + const kafkaVerificationModeInput = useInput( kafkaOutput?.ssl?.verification_mode ?? kafkaVerificationModes.Full, undefined, @@ -443,6 +491,7 @@ export function useOutputForm(onSucess: () => void, output?: Output) { caTrustedFingerprintInput, sslCertificateInput, sslKeyInput, + sslKeySecretInput, sslCertificateAuthoritiesInput, proxyIdInput, loadBalanceEnabledInput, @@ -456,6 +505,7 @@ export function useOutputForm(onSucess: () => void, output?: Output) { kafkaConnectionTypeInput, kafkaAuthUsernameInput, kafkaAuthPasswordInput, + kafkaAuthPasswordSecretInput, kafkaSaslMechanismInput, kafkaPartitionTypeInput, kafkaPartitionTypeRandomInput, @@ -473,6 +523,7 @@ export function useOutputForm(onSucess: () => void, output?: Output) { kafkaSslCertificateAuthoritiesInput, kafkaSslCertificateInput, kafkaSslKeyInput, + kafkaSslKeySecretInput, kafkaDefaultTopicInput, kafkaTopicsInput, }; @@ -484,10 +535,12 @@ export function useOutputForm(onSucess: () => void, output?: Output) { const elasticsearchUrlsValid = elasticsearchUrlInput.validate(); const kafkaHostsValid = kafkaHostsInput.validate(); const kafkaUsernameValid = kafkaAuthUsernameInput.validate(); - const kafkaPasswordValid = kafkaAuthPasswordInput.validate(); + const kafkaPasswordPlainValid = kafkaAuthPasswordInput.validate(); + const kafkaPasswordSecretValid = kafkaAuthPasswordSecretInput.validate(); const kafkaClientIDValid = kafkaClientIdInput.validate(); const kafkaSslCertificateValid = kafkaSslCertificateInput.validate(); - const kafkaSslKeyValid = kafkaSslKeyInput.validate(); + const kafkaSslKeyPlainValid = kafkaSslKeyInput.validate(); + const kafkaSslKeySecretValid = kafkaSslKeySecretInput.validate(); const kafkaDefaultTopicValid = kafkaDefaultTopicInput.validate(); const kafkaTopicsValid = kafkaTopicsInput.validate(); const kafkaHeadersValid = kafkaHeadersInput.validate(); @@ -496,10 +549,19 @@ export function useOutputForm(onSucess: () => void, output?: Output) { const caTrustedFingerprintValid = caTrustedFingerprintInput.validate(); const sslCertificateValid = sslCertificateInput.validate(); const sslKeyValid = sslKeyInput.validate(); + const sslKeySecretValid = sslKeySecretInput.validate(); const diskQueuePathValid = diskQueuePathInput.validate(); const partitioningRandomGroupEventsValid = kafkaPartitionTypeRandomInput.validate(); const partitioningRoundRobinGroupEventsValid = kafkaPartitionTypeRoundRobinInput.validate(); + const kafkaSslKeyValid = kafkaSslKeyInput.value + ? kafkaSslKeyPlainValid + : kafkaSslKeySecretValid; + + const kafkaPasswordValid = kafkaAuthPasswordInput.value + ? kafkaPasswordPlainValid + : kafkaPasswordSecretValid; + if (isLogstash) { // validate logstash return ( @@ -507,7 +569,7 @@ export function useOutputForm(onSucess: () => void, output?: Output) { additionalYamlConfigValid && nameInputValid && sslCertificateValid && - sslKeyValid + ((sslKeyInput.value && sslKeyValid) || (sslKeySecretInput.value && sslKeySecretValid)) ); } if (isKafka) { @@ -543,9 +605,11 @@ export function useOutputForm(onSucess: () => void, output?: Output) { kafkaHostsInput, kafkaAuthUsernameInput, kafkaAuthPasswordInput, + kafkaAuthPasswordSecretInput, kafkaClientIdInput, kafkaSslCertificateInput, kafkaSslKeyInput, + kafkaSslKeySecretInput, kafkaDefaultTopicInput, kafkaTopicsInput, kafkaHeadersInput, @@ -554,6 +618,7 @@ export function useOutputForm(onSucess: () => void, output?: Output) { caTrustedFingerprintInput, sslCertificateInput, sslKeyInput, + sslKeySecretInput, diskQueuePathInput, kafkaPartitionTypeRandomInput, kafkaPartitionTypeRoundRobinInput, @@ -623,6 +688,13 @@ export function useOutputForm(onSucess: () => void, output?: Output) { (val) => val !== '' ).length; + const maybeSecrets = extractKafkaOutputSecrets({ + kafkaSslKeyInput, + kafkaSslKeySecretInput, + kafkaAuthPasswordInput, + kafkaAuthPasswordSecretInput, + }); + return { name: nameInput.value, type: outputType.Kafka, @@ -721,6 +793,7 @@ export function useOutputForm(onSucess: () => void, output?: Output) { ), required_acks: parseIntegerIfStringDefined(kafkaBrokerAckReliabilityInput.value), ...shipperParams, + ...(maybeSecrets ? { secrets: maybeSecrets } : {}), } as KafkaOutput; case outputType.Logstash: return { @@ -732,11 +805,19 @@ export function useOutputForm(onSucess: () => void, output?: Output) { config_yaml: additionalYamlConfigInput.value, ssl: { certificate: sslCertificateInput.value, - key: sslKeyInput.value, + key: sslKeyInput.value || undefined, certificate_authorities: sslCertificateAuthoritiesInput.value.filter( (val) => val !== '' ), }, + ...(!sslKeyInput.value && + sslKeySecretInput.value && { + secrets: { + ssl: { + key: sslKeySecretInput.value, + }, + }, + }), proxy_id: proxyIdValue, ...shipperParams, } as NewLogstashOutput; @@ -812,7 +893,8 @@ export function useOutputForm(onSucess: () => void, output?: Output) { additionalYamlConfigInput.value, kafkaAuthMethodInput.value, kafkaSslCertificateInput.value, - kafkaSslKeyInput.value, + kafkaSslKeyInput, + kafkaSslKeySecretInput, kafkaVerificationModeInput.value, kafkaClientIdInput.value, kafkaVersionInput.value, @@ -821,7 +903,8 @@ export function useOutputForm(onSucess: () => void, output?: Output) { kafkaCompressionLevelInput.value, kafkaConnectionTypeInput.value, kafkaAuthUsernameInput.value, - kafkaAuthPasswordInput.value, + kafkaAuthPasswordInput, + kafkaAuthPasswordSecretInput, kafkaSaslMechanismInput.value, kafkaPartitionTypeInput.value, kafkaPartitionTypeRandomInput.value, @@ -836,6 +919,7 @@ export function useOutputForm(onSucess: () => void, output?: Output) { logstashHostsInput.value, sslCertificateInput.value, sslKeyInput.value, + sslKeySecretInput.value, sslCertificateAuthoritiesInput.value, elasticsearchUrlInput.value, caTrustedFingerprintInput.value, diff --git a/x-pack/plugins/fleet/public/hooks/use_input.ts b/x-pack/plugins/fleet/public/hooks/use_input.ts index dbc60eafda833..42295aebd9f49 100644 --- a/x-pack/plugins/fleet/public/hooks/use_input.ts +++ b/x-pack/plugins/fleet/public/hooks/use_input.ts @@ -84,6 +84,72 @@ export function useInput( }; } +type MaybeSecret = string | { id: string } | undefined; + +export function useSecretInput( + initialValue: MaybeSecret, + validate?: (value: MaybeSecret) => string[] | undefined, + disabled: boolean = false +) { + const [value, setValue] = useState(initialValue); + const [errors, setErrors] = useState(); + const [hasChanged, setHasChanged] = useState(false); + + const onChange = useCallback( + (e: React.ChangeEvent) => { + const newValue = e.target.value; + setValue(newValue); + if (errors && validate && validate(newValue) === undefined) { + setErrors(undefined); + } + }, + [errors, validate] + ); + + useEffect(() => { + if (hasChanged) { + return; + } + if (value !== initialValue) { + setHasChanged(true); + } + }, [hasChanged, value, initialValue]); + + const isInvalid = errors !== undefined; + + return { + value, + errors, + props: { + onChange, + value: typeof value === 'string' ? value : '', + isInvalid, + disabled, + }, + formRowProps: { + error: errors, + isInvalid, + initialValue, + clear: () => { + setValue(''); + }, + }, + cancelEdit: () => { + setValue(initialValue || ''); + }, + validate: () => { + if (validate) { + const newErrors = validate(value); + setErrors(newErrors); + return newErrors === undefined; + } + + return true; + }, + setValue, + hasChanged, + }; +} export function useRadioInput(defaultValue: string, disabled = false) { const [value, setValue] = useState(defaultValue); const [hasChanged, setHasChanged] = useState(false); diff --git a/x-pack/plugins/fleet/server/services/secrets.ts b/x-pack/plugins/fleet/server/services/secrets.ts index fcb6a0fdc2fe6..36a88b4a7a4c1 100644 --- a/x-pack/plugins/fleet/server/services/secrets.ts +++ b/x-pack/plugins/fleet/server/services/secrets.ts @@ -566,7 +566,7 @@ export async function isSecretStorageEnabled( const settings = await settingsService.getSettingsOrUndefined(soClient); if (settings && settings.secret_storage_requirements_met) { - logger.debug('Secrets storage already met, turned on is settings'); + logger.debug('Secrets storage requirements already met, turned on in settings'); return true; } From 980162b0b0138fb572a32c4f3901da3df7f53017 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Efe=20G=C3=BCrkan=20YALAMAN?= Date: Wed, 25 Oct 2023 14:02:28 +0200 Subject: [PATCH 17/24] [Enterprise Search]Disable syncs for native connectors when EnterpriseSearch is down (#169671) ## Summary Disable syncs when Enterprise Search is down for native connectors. Screenshot 2023-10-24 at 17 15 00 Screenshot 2023-10-24 at 17 15 07 Screenshot 2023-10-24 at 17 15 32 Screenshot 2023-10-24 at 17 15 20 ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [x] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) --- .../header_actions/syncs_context_menu.tsx | 20 ++++++++---- .../native_connector_configuration.tsx | 32 +++++++++++++++++++ 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/header_actions/syncs_context_menu.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/header_actions/syncs_context_menu.tsx index 96efd9d7d8447..bbe8a039a3ef7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/header_actions/syncs_context_menu.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/components/header_actions/syncs_context_menu.tsx @@ -23,6 +23,7 @@ import { import { i18n } from '@kbn/i18n'; import { Status } from '../../../../../../../common/types/api'; +import { HttpLogic } from '../../../../../shared/http'; import { KibanaLogic } from '../../../../../shared/kibana'; import { CancelSyncsApiLogic } from '../../../../api/connector/cancel_syncs_api_logic'; import { IngestionStatus } from '../../../../types'; @@ -30,8 +31,9 @@ import { CancelSyncsLogic } from '../../connector/cancel_syncs_logic'; import { IndexViewLogic } from '../../index_view_logic'; export const SyncsContextMenu: React.FC = () => { - const { productFeatures } = useValues(KibanaLogic); + const { config, productFeatures } = useValues(KibanaLogic); const { + connector, hasDocumentLevelSecurityFeature, hasIncrementalSyncFeature, ingestionMethod, @@ -43,7 +45,7 @@ export const SyncsContextMenu: React.FC = () => { const { cancelSyncs } = useActions(CancelSyncsLogic); const { status } = useValues(CancelSyncsApiLogic); const { startSync, startIncrementalSync, startAccessControlSync } = useActions(IndexViewLogic); - const { connector } = useValues(IndexViewLogic); + const { errorConnectingMessage } = useValues(HttpLogic); const [isPopoverOpen, setPopover] = useState(false); const togglePopover = () => setPopover(!isPopoverOpen); @@ -75,6 +77,13 @@ export const SyncsContextMenu: React.FC = () => { const shouldShowIncrementalSync = productFeatures.hasIncrementalSyncEnabled && hasIncrementalSyncFeature; + const isEnterpriseSearchNotAvailable = Boolean( + config.host && config.canDeployEntSearch && errorConnectingMessage + ); + const isSyncsDisabled = + (connector?.is_native && isEnterpriseSearchNotAvailable) || + ingestionStatus === IngestionStatus.INCOMPLETE; + const panels: EuiContextMenuProps['panels'] = [ { id: 0, @@ -86,7 +95,7 @@ export const SyncsContextMenu: React.FC = () => { // @ts-ignore - data-* attributes are applied but doesn't exist on types 'data-telemetry-id': `entSearchContent-${ingestionMethod}-header-sync-startSync`, 'data-test-subj': `entSearchContent-${ingestionMethod}-header-sync-startSync`, - disabled: ingestionStatus === IngestionStatus.INCOMPLETE, + disabled: isSyncsDisabled, icon: 'play', name: i18n.translate('xpack.enterpriseSearch.index.header.more.fullSync', { defaultMessage: 'Full Content', @@ -105,7 +114,7 @@ export const SyncsContextMenu: React.FC = () => { 'entSearchContent-${ingestionMethod}-header-sync-more-incrementalSync', 'data-test-subj': 'entSearchContent-${ingestionMethod}-header-sync-more-incrementalSync', - disabled: ingestionStatus === IngestionStatus.INCOMPLETE, + disabled: isSyncsDisabled, icon: 'play', name: i18n.translate('xpack.enterpriseSearch.index.header.more.incrementalSync', { defaultMessage: 'Incremental Content', @@ -126,8 +135,7 @@ export const SyncsContextMenu: React.FC = () => { 'data-test-subj': 'entSearchContent-${ingestionMethod}-header-sync-more-accessControlSync', disabled: Boolean( - ingestionStatus === IngestionStatus.INCOMPLETE || - connector?.configuration.use_document_level_security?.value + isSyncsDisabled || !connector?.configuration.use_document_level_security?.value ), icon: 'play', name: i18n.translate('xpack.enterpriseSearch.index.header.more.accessControlSync', { diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration.tsx index 235ec644d3399..df4155cb28d65 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_configuration.tsx @@ -10,6 +10,7 @@ import React from 'react'; import { useValues } from 'kea'; import { + EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiIcon, @@ -25,7 +26,9 @@ import { i18n } from '@kbn/i18n'; import { BetaConnectorCallout } from '../../../../../shared/beta/beta_connector_callout'; import { docLinks } from '../../../../../shared/doc_links'; +import { HttpLogic } from '../../../../../shared/http'; import { CONNECTOR_ICONS } from '../../../../../shared/icons/connector_icons'; +import { KibanaLogic } from '../../../../../shared/kibana'; import { hasConfiguredConfiguration } from '../../../../utils/has_configured_configuration'; import { isConnectorIndex } from '../../../../utils/indices'; @@ -40,6 +43,8 @@ import { ResearchConfiguration } from './research_configuration'; export const NativeConnectorConfiguration: React.FC = () => { const { index } = useValues(IndexViewLogic); + const { config } = useValues(KibanaLogic); + const { errorConnectingMessage } = useValues(HttpLogic); if (!isConnectorIndex(index)) { return <>; @@ -95,6 +100,33 @@ export const NativeConnectorConfiguration: React.FC = () => {
+ {config.host && config.canDeployEntSearch && errorConnectingMessage && ( + <> + +

+ {i18n.translate( + 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.entSearchWarning.text', + { + defaultMessage: + 'Native connectors require a running Enterprise Search instance to sync content from source.', + } + )} +

+
+ + + + )} Date: Wed, 25 Oct 2023 14:13:41 +0200 Subject: [PATCH 18/24] [EDR Workflows][E2E] Recreate agent on createEndpointHost task fail (#169092) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restart vagrant vm on error during `beforeAll` task `createEndpointHost` Defend Workflows Cypress suite ran 300 times through flaky test runner: 1. 100x https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3699 2. 50x https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3707 3. 50x https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3708 4. 50x https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3709 5. 50x https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3710 Flaky test runner runs with `createEndpointHost` task failure with successful recovery: 1. https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3710#018b62fd-9ae9-4988-b1e0-ab0f04d8efdc 2. https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3710#018b62fd-9ae6-4340-992b-1474ee0f114b 3. https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3708#018b62fd-578e-4817-ae1c-8c58e8774eec 4. https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3708#018b62fd-5787-4245-85a6-cb446e42bc73 5. https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3707#018b62fc-fc17-407e-88de-d0b43b6b1d44 (failed due to unrelated issue) 6. https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3699#018b61d9-d2c3-430c-b3e3-72b9fbb22d24 7. https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3699#018b61d9-d2c6-4315-b828-b3218a70f209 8. https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3699#018b61d9-d2c7-4ff7-9a70-7354f90179e0 9. https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3699#018b61d9-d2d7-418f-b043-049e5effb26f 10. https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3699#018b61d9-d2da-47cc-b4ea-a4d4de3ba0a0 New errors not spotted before that got to do with env set up: 1. `vagrant up` failed: 1.1 https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3708#018b62fd-5787-4245-85a6-cb446e42bc73 1.2 https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3699#018b61d9-d2d0-4a52-87d9-34caa8927465 2. `CypressError: `cy.task('indexFleetEndpointPolicy')` timed out after waiting `60000ms`.: 2.1 https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3707#018b62fc-fc04-40d4-b155-46f094681edb 2.2 https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3699#018b61d9-d2c9-4ebb-9174-eb9d79d04d02 2.3 https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/3699#018b61d9-d2dc-438f-94b0-9f94ae95701c Closes: https://github.com/elastic/kibana/issues/168284 https://github.com/elastic/kibana/issues/169343 https://github.com/elastic/kibana/issues/169468 https://github.com/elastic/kibana/issues/169469 https://github.com/elastic/kibana/issues/169467 https://github.com/elastic/kibana/issues/169465 https://github.com/elastic/kibana/issues/169466 https://github.com/elastic/kibana/issues/169157 https://github.com/elastic/kibana/issues/168719 https://github.com/elastic/kibana/issues/168427 https://github.com/elastic/kibana/issues/168359 https://github.com/elastic/kibana/issues/168340 https://github.com/elastic/kibana/issues/169689 --------- Co-authored-by: Patryk Kopyciński --- .buildkite/pipelines/flaky_tests/pipeline.ts | 3 +- .../automated_response_actions.cy.ts | 6 +-- .../cypress/e2e/endpoint_list/endpoints.cy.ts | 3 +- .../cypress/support/data_loaders.ts | 41 +++++++++++++++---- .../cypress/tasks/create_endpoint_host.ts | 2 +- .../endpoint/common/endpoint_host_services.ts | 7 +++- .../scripts/endpoint/common/fleet_services.ts | 17 ++++++-- 7 files changed, 57 insertions(+), 22 deletions(-) diff --git a/.buildkite/pipelines/flaky_tests/pipeline.ts b/.buildkite/pipelines/flaky_tests/pipeline.ts index 89505065b5809..1d12b2c840744 100644 --- a/.buildkite/pipelines/flaky_tests/pipeline.ts +++ b/.buildkite/pipelines/flaky_tests/pipeline.ts @@ -162,10 +162,11 @@ for (const testSuite of testSuites) { `Group configuration was not found in groups.json for the following cypress suite: {${suiteName}}.` ); } + const agentQueue = suiteName.includes('defend_workflows') ? 'n2-4-virt' : 'n2-4-spot'; steps.push({ command: `.buildkite/scripts/steps/functional/${suiteName}.sh`, label: group.name, - agents: { queue: 'n2-4-spot' }, + agents: { queue: agentQueue }, depends_on: 'build', parallelism: testSuite.count, concurrency, diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/automated_response_actions.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/automated_response_actions.cy.ts index f7257060e4ca9..b94f389958f9f 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/automated_response_actions.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/automated_response_actions.cy.ts @@ -20,8 +20,7 @@ import { createEndpointHost } from '../../tasks/create_endpoint_host'; import { deleteAllLoadedEndpointData } from '../../tasks/delete_all_endpoint_data'; import { enableAllPolicyProtections } from '../../tasks/endpoint_policy'; -// FLAKY: https://github.com/elastic/kibana/issues/168340 -describe.skip( +describe( 'Automated Response Actions', { tags: [ @@ -76,8 +75,7 @@ describe.skip( disableExpandableFlyoutAdvancedSettings(); }); - // FLAKY: https://github.com/elastic/kibana/issues/168427 - describe.skip('From alerts', () => { + describe('From alerts', () => { let ruleId: string; let ruleName: string; diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_list/endpoints.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_list/endpoints.cy.ts index b7d9244040aa0..2baf3b583aed4 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_list/endpoints.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_list/endpoints.cy.ts @@ -32,8 +32,7 @@ import { createEndpointHost } from '../../tasks/create_endpoint_host'; import { deleteAllLoadedEndpointData } from '../../tasks/delete_all_endpoint_data'; import { enableAllPolicyProtections } from '../../tasks/endpoint_policy'; -// FLAKY: https://github.com/elastic/kibana/issues/168284 -describe.skip('Endpoints page', { tags: ['@ess', '@serverless'] }, () => { +describe('Endpoints page', { tags: ['@ess', '@serverless'] }, () => { let indexedPolicy: IndexedFleetEndpointPolicyResponse; let policy: PolicyData; let createdHost: CreateAndEnrollEndpointHostResponse; diff --git a/x-pack/plugins/security_solution/public/management/cypress/support/data_loaders.ts b/x-pack/plugins/security_solution/public/management/cypress/support/data_loaders.ts index 164afe89086d1..fcead968801af 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/support/data_loaders.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/support/data_loaders.ts @@ -332,16 +332,39 @@ export const dataLoadersForRealEndpoints = ( options: Omit ): Promise => { const { kbnClient, log } = await stackServicesPromise; - return createAndEnrollEndpointHost({ - useClosestVersionMatch: true, - ...options, - log, - kbnClient, - }).then((newHost) => { - return waitForEndpointToStreamData(kbnClient, newHost.agentId, 360000).then(() => { + + let retryAttempt = 0; + const attemptCreateEndpointHost = async (): Promise => { + try { + log.info(`Creating endpoint host, attempt ${retryAttempt}`); + const newHost = await createAndEnrollEndpointHost({ + useClosestVersionMatch: true, + ...options, + log, + kbnClient, + }); + await waitForEndpointToStreamData(kbnClient, newHost.agentId, 360000); return newHost; - }); - }); + } catch (err) { + log.info(`Caught error when setting up the agent: ${err}`); + if (retryAttempt === 0 && err.agentId) { + retryAttempt++; + await destroyEndpointHost(kbnClient, { + hostname: err.hostname || '', // No hostname in CI env for vagrant + agentId: err.agentId, + }); + log.info(`Deleted endpoint host ${err.agentId} and retrying`); + return attemptCreateEndpointHost(); + } else { + log.info( + `${retryAttempt} attempts of creating endpoint host failed, reason for the last failure was ${err}` + ); + throw err; + } + } + }; + + return attemptCreateEndpointHost(); }, destroyEndpointHost: async ( diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/create_endpoint_host.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/create_endpoint_host.ts index 7db2ad37317fd..8b75c7e1b2af5 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/tasks/create_endpoint_host.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/create_endpoint_host.ts @@ -17,6 +17,6 @@ export const createEndpointHost = ( { agentPolicyId, }, - { timeout: timeout ?? 900000 } // 15 minutes, since setup can take 10 minutes and more. Task will time out if is not resolved within this time. + { timeout: timeout ?? 30 * 60 * 1000 } ); }; diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/endpoint_host_services.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/endpoint_host_services.ts index ef38b120c38bb..b716b9299ba02 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/endpoint_host_services.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/endpoint_host_services.ts @@ -234,7 +234,7 @@ const createMultipassVm = async ({ }; }; -const deleteMultipassVm = async (vmName: string): Promise => { +export const deleteMultipassVm = async (vmName: string): Promise => { if (process.env.CI) { await execa.command(`vagrant destroy -f`, { env: { @@ -339,7 +339,10 @@ const enrollHostWithFleet = async ({ ]); } log.info(`Waiting for Agent to check-in with Fleet`); - const agent = await waitForHostToEnroll(kbnClient, vmName, 240000); + + const agent = await waitForHostToEnroll(kbnClient, vmName, 8 * 60 * 1000); + + log.info(`Agent enrolled with Fleet, status: `, agent.status); return { agentId: agent.id, diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_services.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_services.ts index a95b9f03caf2e..e254b8d3a1570 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_services.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_services.ts @@ -147,15 +147,19 @@ export const waitForHostToEnroll = async ( return elapsedTime > timeoutMs; }; let found: Agent | undefined; + let agentId: string | undefined; while (!found && !hasTimedOut()) { found = await retryOnError( async () => fetchFleetAgents(kbnClient, { perPage: 1, - kuery: `(local_metadata.host.hostname.keyword : "${hostname}") and (status:online)`, + kuery: `(local_metadata.host.hostname.keyword : "${hostname}")`, showInactive: false, - }).then((response) => response.items[0]), + }).then((response) => { + agentId = response.items[0]?.id; + return response.items.filter((agent) => agent.status === 'online')[0]; + }), RETRYABLE_TRANSIENT_ERRORS ); @@ -166,7 +170,14 @@ export const waitForHostToEnroll = async ( } if (!found) { - throw new Error(`Timed out waiting for host [${hostname}] to show up in Fleet`); + throw Object.assign( + new Error( + `Timed out waiting for host [${hostname}] to show up in Fleet in ${ + timeoutMs / 60 / 1000 + } seconds` + ), + { agentId, hostname } + ); } return found; From f378b87cad0d308a29af0659e963adf651aedfbe Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Wed, 25 Oct 2023 14:43:20 +0200 Subject: [PATCH 19/24] [Serverless] Improve fleet and integrations serverless breadcrumbs (#169772) ## Summary This fixes deeper context breadcrumbs in serverless navigation for fleet and integration apps. This builds on top of https://github.com/elastic/kibana/pull/169513 where we added merging of navigational project breadcrumbs with deeper context breadcrumbs set by `chrome.setBreadcrumbs`. The merging is based on `deepLinkId`, so we're adding it to base breadcrumbs. The `deepLinkId` is type checked. Example Before/After: Before: ![Screenshot 2023-10-25 at 12 05 33](https://github.com/elastic/kibana/assets/7784120/4a6a0bab-1cef-4b24-8349-246b9612563e) ![Screenshot 2023-10-25 at 12 05 37](https://github.com/elastic/kibana/assets/7784120/63435c3c-1397-4b41-8d46-3d0e9bd32515) After: ![Screenshot 2023-10-25 at 12 06 10](https://github.com/elastic/kibana/assets/7784120/a7519fdd-b21a-40e7-a774-d867bb4e79ec) ![Screenshot 2023-10-25 at 12 06 14](https://github.com/elastic/kibana/assets/7784120/1e99e005-1317-4c62-af1e-c445c9038fc4) --- .../applications/fleet/hooks/use_breadcrumbs.tsx | 2 ++ .../integrations/hooks/use_breadcrumbs.tsx | 1 + .../test_suites/observability/navigation.ts | 15 +++++++++++++++ 3 files changed, 18 insertions(+) diff --git a/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx index 8208616ffd028..49af462dc96f5 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx @@ -24,6 +24,7 @@ const BASE_BREADCRUMB: Breadcrumb = { text: i18n.translate('xpack.fleet.breadcrumbs.appTitle', { defaultMessage: 'Fleet', }), + deepLinkId: 'fleet', }; const INTEGRATIONS_BASE_BREADCRUMB: Breadcrumb = { @@ -32,6 +33,7 @@ const INTEGRATIONS_BASE_BREADCRUMB: Breadcrumb = { defaultMessage: 'Integrations', }), useIntegrationsBasePath: true, + deepLinkId: 'integrations', }; const breadcrumbGetters: { diff --git a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_breadcrumbs.tsx b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_breadcrumbs.tsx index 28f41fcabfcba..967590c36ce07 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_breadcrumbs.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_breadcrumbs.tsx @@ -19,6 +19,7 @@ const BASE_BREADCRUMB: ChromeBreadcrumb = { text: i18n.translate('xpack.fleet.breadcrumbs.integrationsAppTitle', { defaultMessage: 'Integrations', }), + deepLinkId: 'integrations', }; const breadcrumbGetters: { diff --git a/x-pack/test_serverless/functional/test_suites/observability/navigation.ts b/x-pack/test_serverless/functional/test_suites/observability/navigation.ts index 012c7811c5a80..c6c6e74edf62b 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/navigation.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/navigation.ts @@ -128,5 +128,20 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { }); await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts(['Cases', 'Settings']); }); + + it('navigates to integrations', async () => { + await svlCommonNavigation.sidenav.openSection('project_settings_project_nav'); + await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'integrations' }); + await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts([ + 'Integrations', + 'Browse integrations', + ]); + }); + + it('navigates to fleet', async () => { + await svlCommonNavigation.sidenav.openSection('project_settings_project_nav'); + await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'fleet' }); + await svlCommonNavigation.breadcrumbs.expectBreadcrumbTexts(['Fleet', 'Agents']); + }); }); } From fe22ff0e412a07c8c3f82e909b56986c3ef0d6bc Mon Sep 17 00:00:00 2001 From: Saarika Bhasi <55930906+saarikabhasi@users.noreply.github.com> Date: Wed, 25 Oct 2023 08:58:15 -0400 Subject: [PATCH 20/24] [Search experience] Fix links is WS gated form (#169699) ## Summary 1. Updates Workplace search gated form links: - Blogs url - https://www.elastic.co/blog/evolution-workplace-search-private-data-elasticsearch - Terms of service - https://www.elastic.co/legal/elastic-cloud-account-terms - Contact you- https://www.elastic.co/legal/privacy-statement#how-we-use-the-information - Privacy statement - https://www.elastic.co/legal/privacy-statement/ 2. Assign respective variable in docs for the urls ## Screen recording https://github.com/elastic/kibana/assets/55930906/941ac635-7caa-4a60-905b-45b3610ed9fa --- packages/kbn-doc-links/src/get_doc_links.ts | 4 ++++ packages/kbn-doc-links/src/types.ts | 4 ++++ .../applications/shared/doc_links/doc_links.ts | 12 ++++++++++++ .../workplace_search/views/overview/gated_form.tsx | 8 +++++--- .../views/overview/gated_form_page.tsx | 3 ++- 5 files changed, 27 insertions(+), 4 deletions(-) diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index b8d46bad40cc4..16b58be6c6c03 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -217,6 +217,7 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => { dropbox: `${WORKPLACE_SEARCH_DOCS}workplace-search-dropbox-connector.html`, externalSharePointOnline: `${WORKPLACE_SEARCH_DOCS}sharepoint-online-external.html`, externalIdentities: `${WORKPLACE_SEARCH_DOCS}workplace-search-external-identities-api.html`, + gatedFormBlog: `${ELASTIC_WEBSITE_URL}blog/evolution-workplace-search-private-data-elasticsearch`, gettingStarted: `${WORKPLACE_SEARCH_DOCS}workplace-search-getting-started.html`, gitHub: `${WORKPLACE_SEARCH_DOCS}workplace-search-github-connector.html`, gmail: `${WORKPLACE_SEARCH_DOCS}workplace-search-gmail-connector.html`, @@ -815,6 +816,9 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => { }, legal: { privacyStatement: `${ELASTIC_WEBSITE_URL}legal/product-privacy-statement`, + generalPrivacyStatement: `${ELASTIC_WEBSITE_URL}legal/privacy-statement`, + termsOfService: `${ELASTIC_WEBSITE_URL}legal/elastic-cloud-account-terms`, + dataUse: `${ELASTIC_WEBSITE_URL}legal/privacy-statement#how-we-use-the-information`, }, kibanaUpgradeSavedObjects: { resolveMigrationFailures: `${KIBANA_DOCS}resolve-migrations-failures.html`, diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts index adc7f13c6c612..703af63c1027c 100644 --- a/packages/kbn-doc-links/src/types.ts +++ b/packages/kbn-doc-links/src/types.ts @@ -198,6 +198,7 @@ export interface DocLinks { readonly dropbox: string; readonly externalSharePointOnline: string; readonly externalIdentities: string; + readonly gatedFormBlog: string; readonly gitHub: string; readonly gettingStarted: string; readonly gmail: string; @@ -572,6 +573,9 @@ export interface DocLinks { }; readonly legal: { readonly privacyStatement: string; + readonly generalPrivacyStatement: string; + readonly termsOfService: string; + readonly dataUse: string; }; readonly kibanaUpgradeSavedObjects: { readonly resolveMigrationFailures: string; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts b/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts index 6974f956e2bc9..82a4eedcd9c34 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts @@ -151,6 +151,10 @@ class DocLinks { public workplaceSearchDropbox: string; public workplaceSearchExternalIdentities: string; public workplaceSearchExternalSharePointOnline: string; + public workplaceSearchGatedFormBlog: string; + public workplaceSearchGatedFormDataUse: string; + public workplaceSearchGatedFormPrivacyStatement: string; + public workplaceSearchGatedFormTermsOfService: string; public workplaceSearchGettingStarted: string; public workplaceSearchGitHub: string; public workplaceSearchGmail: string; @@ -318,6 +322,10 @@ class DocLinks { this.workplaceSearchDropbox = ''; this.workplaceSearchExternalSharePointOnline = ''; this.workplaceSearchExternalIdentities = ''; + this.workplaceSearchGatedFormBlog = ''; + this.workplaceSearchGatedFormDataUse = ''; + this.workplaceSearchGatedFormPrivacyStatement = ''; + this.workplaceSearchGatedFormTermsOfService = ''; this.workplaceSearchGettingStarted = ''; this.workplaceSearchGitHub = ''; this.workplaceSearchGmail = ''; @@ -475,6 +483,7 @@ class DocLinks { this.syncRules = docLinks.links.enterpriseSearch.syncRules; this.trainedModels = docLinks.links.enterpriseSearch.trainedModels; this.textEmbedding = docLinks.links.enterpriseSearch.textEmbedding; + this.workplaceSearchGatedFormBlog = docLinks.links.workplaceSearch.gatedFormBlog; this.workplaceSearchApiKeys = docLinks.links.workplaceSearch.apiKeys; this.workplaceSearchBox = docLinks.links.workplaceSearch.box; this.workplaceSearchConfluenceCloud = docLinks.links.workplaceSearch.confluenceCloud; @@ -492,6 +501,9 @@ class DocLinks { this.workplaceSearchExternalSharePointOnline = docLinks.links.workplaceSearch.externalSharePointOnline; this.workplaceSearchExternalIdentities = docLinks.links.workplaceSearch.externalIdentities; + this.workplaceSearchGatedFormDataUse = docLinks.links.legal.dataUse; + this.workplaceSearchGatedFormPrivacyStatement = docLinks.links.legal.generalPrivacyStatement; + this.workplaceSearchGatedFormTermsOfService = docLinks.links.legal.termsOfService; this.workplaceSearchGettingStarted = docLinks.links.workplaceSearch.gettingStarted; this.workplaceSearchGitHub = docLinks.links.workplaceSearch.gitHub; this.workplaceSearchGmail = docLinks.links.workplaceSearch.gmail; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/gated_form.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/gated_form.tsx index 70a538d212d77..2ad67febaa3da 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/gated_form.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/gated_form.tsx @@ -30,6 +30,8 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; +import { docLinks } from '../../../shared/doc_links'; + import { WorkplaceSearchGateLogic } from './gated_form_logic'; const getFeature = (id: string) => { @@ -593,7 +595,7 @@ export const WorkplaceSearchGate: React.FC = () => { details or to opt-out at any time." values={{ contact: ( - + { ), privacyStatementLink: ( - + { ), termsOfService: ( - + = ({ isLoading blogUrl: ( From 4461f5b95ab97eea0bfba3041fde9d171498a96f Mon Sep 17 00:00:00 2001 From: Mark Hopkin Date: Wed, 25 Oct 2023 14:33:28 +0100 Subject: [PATCH 21/24] [Fleet] Fix flaky output secrets test (#169792) ## Summary Closes #169744 Replaced the search calls with get by ID calls to prevent refresh race condition --- .../fleet_api_integration/apis/outputs/crud.ts | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/x-pack/test/fleet_api_integration/apis/outputs/crud.ts b/x-pack/test/fleet_api_integration/apis/outputs/crud.ts index 686d29c9cdf22..5f6558df992ca 100644 --- a/x-pack/test/fleet_api_integration/apis/outputs/crud.ts +++ b/x-pack/test/fleet_api_integration/apis/outputs/crud.ts @@ -20,16 +20,6 @@ export default function (providerContext: FtrProviderContext) { let pkgVersion: string; - const getSecrets = async (ids?: string[]) => { - const query = ids ? { terms: { _id: ids } } : { match_all: {} }; - return es.search({ - index: '.fleet-secrets', - body: { - query, - }, - }); - }; - const getSecretById = (id: string) => { return es.get({ index: '.fleet-secrets', @@ -1112,9 +1102,9 @@ export default function (providerContext: FtrProviderContext) { .expect(200); const secretId = res.body.item.secrets.ssl.key.id; - const searchRes = await getSecrets([secretId]); + const secret = await getSecretById(secretId); // @ts-ignore _source unknown type - expect(searchRes.hits.hits[0]._source.value).to.equal('KEY'); + expect(secret._source.value).to.equal('KEY'); }); it('should create ssl.password secret correctly', async function () { @@ -1138,9 +1128,9 @@ export default function (providerContext: FtrProviderContext) { }); const secretId = res.body.item.secrets.password.id; - const searchRes = await getSecrets([secretId]); + const secret = await getSecretById(secretId); // @ts-ignore _source unknown type - expect(searchRes.hits.hits[0]._source.value).to.equal('pass'); + expect(secret._source.value).to.equal('pass'); }); }); From 70dff2ac3eac810febe61cb3a5214472962ffd1d Mon Sep 17 00:00:00 2001 From: Philippe Oberti Date: Wed, 25 Oct 2023 08:35:19 -0500 Subject: [PATCH 22/24] [Security Solution] remove code related to alert details page (#169172) --- .../common/experimental_features.ts | 5 - .../common/types/timeline/index.ts | 1 - .../common/utils/alert_detail_path.test.ts | 56 - .../common/components/hover_actions/index.tsx | 5 +- .../components/link_to/__mocks__/index.ts | 1 - .../public/common/components/link_to/index.ts | 1 - .../components/link_to/redirect_to_alerts.tsx | 19 - .../breadcrumbs/trailing_breadcrumbs.ts | 3 - .../cases/use_get_related_cases_by_event.ts | 53 - .../public/common/utils/route/types.ts | 7 - .../alert_context_menu.test.tsx | 39 - .../timeline_actions/alert_context_menu.tsx | 16 +- .../use_open_alert_details.tsx | 49 - .../__mocks__/alert_details_response.ts | 2020 ----------------- .../pages/alert_details/__mocks__/index.ts | 8 - .../alert_details/components/error_page.tsx | 33 - .../pages/alert_details/components/header.tsx | 33 - .../alert_details/components/loading_page.tsx | 21 - .../pages/alert_details/index.test.tsx | 144 -- .../detections/pages/alert_details/index.tsx | 96 - .../alert_render_panel.test.tsx | 57 - .../summary/alert_renderer_panel/index.tsx | 56 - .../summary/cases_panel/cases_panel.test.tsx | 223 -- .../cases_panel/cases_panel_actions.tsx | 102 - .../tabs/summary/cases_panel/index.tsx | 196 -- .../tabs/summary/cases_panel/related_case.tsx | 105 - .../summary/host_panel/host_panel.test.tsx | 155 -- .../summary/host_panel/host_panel_actions.tsx | 99 - .../tabs/summary/host_panel/index.tsx | 195 -- .../alert_details/tabs/summary/index.tsx | 93 - .../tabs/summary/rule_panel/index.tsx | 158 -- .../summary/rule_panel/rule_panel.test.tsx | 55 - .../summary/rule_panel/rule_panel_actions.tsx | 78 - .../alert_details/tabs/summary/translation.ts | 239 -- .../tabs/summary/user_panel/index.tsx | 160 -- .../summary/user_panel/user_panel.test.tsx | 112 - .../summary/user_panel/user_panel_actions.tsx | 99 - .../alert_details/tabs/summary/wrappers.tsx | 70 - .../pages/alert_details/translations.ts | 51 - .../detections/pages/alert_details/types.ts | 14 - .../pages/alert_details/utils/breadcrumbs.ts | 53 - .../utils/get_timeline_event_data.ts | 13 - .../pages/alert_details/utils/navigation.ts | 23 - .../event_details/expandable_event.tsx | 26 - .../side_panel/event_details/translations.ts | 14 - .../components/timeline/body/index.test.tsx | 13 - .../translations/translations/fr-FR.json | 40 - .../translations/translations/ja-JP.json | 40 - .../translations/translations/zh-CN.json | 40 - .../test/security_solution_cypress/config.ts | 1 - .../e2e/entity_analytics/enrichments.cy.ts | 12 +- .../explore/cases/attach_alert_to_case.cy.ts | 12 +- .../investigations/alerts/navigation.cy.ts | 70 - .../cypress/screens/alerts.ts | 32 +- .../cypress/screens/alerts_details.ts | 14 - .../cypress/tasks/alerts.ts | 23 - 56 files changed, 18 insertions(+), 5335 deletions(-) delete mode 100644 x-pack/plugins/security_solution/common/utils/alert_detail_path.test.ts delete mode 100644 x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_alerts.tsx delete mode 100644 x-pack/plugins/security_solution/public/common/containers/cases/use_get_related_cases_by_event.ts delete mode 100644 x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_open_alert_details.tsx delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/alert_details/__mocks__/alert_details_response.ts delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/alert_details/__mocks__/index.ts delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/alert_details/components/error_page.tsx delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/alert_details/components/header.tsx delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/alert_details/components/loading_page.tsx delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/alert_details/index.test.tsx delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/alert_details/index.tsx delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/alert_renderer_panel/alert_render_panel.test.tsx delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/alert_renderer_panel/index.tsx delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/cases_panel/cases_panel.test.tsx delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/cases_panel/cases_panel_actions.tsx delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/cases_panel/index.tsx delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/cases_panel/related_case.tsx delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/host_panel/host_panel.test.tsx delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/host_panel/host_panel_actions.tsx delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/host_panel/index.tsx delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/index.tsx delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/rule_panel/index.tsx delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/rule_panel/rule_panel.test.tsx delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/rule_panel/rule_panel_actions.tsx delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/translation.ts delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/user_panel/index.tsx delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/user_panel/user_panel.test.tsx delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/user_panel/user_panel_actions.tsx delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/wrappers.tsx delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/alert_details/translations.ts delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/alert_details/types.ts delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/alert_details/utils/breadcrumbs.ts delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/alert_details/utils/get_timeline_event_data.ts delete mode 100644 x-pack/plugins/security_solution/public/detections/pages/alert_details/utils/navigation.ts delete mode 100644 x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/navigation.cy.ts diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index 90346011c23e5..e2756d1955e30 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -61,11 +61,6 @@ export const allowedExperimentalValues = Object.freeze({ */ endpointResponseActionsEnabled: true, - /** - * Enables the alert details page currently only accessible via the alert details flyout and alert table context menu - */ - alertDetailsPageEnabled: false, - /** * Enables the `upload` endpoint response action (v8.9) */ diff --git a/x-pack/plugins/security_solution/common/types/timeline/index.ts b/x-pack/plugins/security_solution/common/types/timeline/index.ts index 0372765db9873..e9d482344a28e 100644 --- a/x-pack/plugins/security_solution/common/types/timeline/index.ts +++ b/x-pack/plugins/security_solution/common/types/timeline/index.ts @@ -48,7 +48,6 @@ export enum TimelineId { active = 'timeline-1', casePage = 'timeline-case', test = 'timeline-test', // Reserved for testing purposes - detectionsAlertDetailsPage = 'detections-alert-details-page', } export type TimelineEventsType = 'all' | 'raw' | 'alert' | 'signal' | 'custom' | 'eql'; diff --git a/x-pack/plugins/security_solution/common/utils/alert_detail_path.test.ts b/x-pack/plugins/security_solution/common/utils/alert_detail_path.test.ts deleted file mode 100644 index be827e082db14..0000000000000 --- a/x-pack/plugins/security_solution/common/utils/alert_detail_path.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { buildAlertDetailPath, getAlertDetailsUrl } from './alert_detail_path'; - -describe('alert_detail_path', () => { - const defaultArguments = { - alertId: 'testId', - index: 'testIndex', - timestamp: '2023-04-18T00:00:00.000Z', - }; - describe('buildAlertDetailPath', () => { - it('builds the alert detail path as expected', () => { - expect(buildAlertDetailPath(defaultArguments)).toMatchInlineSnapshot( - `"/alerts/redirect/testId?index=testIndex×tamp=2023-04-18T00:00:00.000Z"` - ); - }); - }); - describe('getAlertDetailsUrl', () => { - it('builds the alert detail path without a space id', () => { - expect( - getAlertDetailsUrl({ - ...defaultArguments, - basePath: 'http://somebasepath.com', - }) - ).toMatchInlineSnapshot( - `"http://somebasepath.com/app/security/alerts/redirect/testId?index=testIndex×tamp=2023-04-18T00:00:00.000Z"` - ); - }); - - it('builds the alert detail path with a space id', () => { - expect( - getAlertDetailsUrl({ - ...defaultArguments, - basePath: 'http://somebasepath.com', - spaceId: 'test-space', - }) - ).toMatchInlineSnapshot( - `"http://somebasepath.com/s/test-space/app/security/alerts/redirect/testId?index=testIndex×tamp=2023-04-18T00:00:00.000Z"` - ); - }); - - it('does not build the alert detail path without a basePath', () => { - expect( - getAlertDetailsUrl({ - ...defaultArguments, - spaceId: 'test-space', - }) - ).toBe(undefined); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/components/hover_actions/index.tsx b/x-pack/plugins/security_solution/public/common/components/hover_actions/index.tsx index 16ed7f95b87c6..6aab4a0afbe68 100644 --- a/x-pack/plugins/security_solution/public/common/components/hover_actions/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/hover_actions/index.tsx @@ -217,13 +217,12 @@ export const HoverActions: React.FC = React.memo( const isCaseView = scopeId === TimelineId.casePage; const isTimelineView = scopeId === TimelineId.active; - const isAlertDetailsView = scopeId === TimelineId.detectionsAlertDetailsPage; // TODO Provide a list of disabled/enabled actions as props const isEntityAnalyticsPage = scopeId === SecurityPageName.entityAnalytics; const hideFilters = useMemo( - () => (isAlertDetailsView || isEntityAnalyticsPage) && !isTimelineView, - [isTimelineView, isAlertDetailsView, isEntityAnalyticsPage] + () => isEntityAnalyticsPage && !isTimelineView, + [isTimelineView, isEntityAnalyticsPage] ); const hiddenActionsCount = useMemo(() => { diff --git a/x-pack/plugins/security_solution/public/common/components/link_to/__mocks__/index.ts b/x-pack/plugins/security_solution/public/common/components/link_to/__mocks__/index.ts index b7bfb751e4fa8..52ed72dc1a2bd 100644 --- a/x-pack/plugins/security_solution/public/common/components/link_to/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/public/common/components/link_to/__mocks__/index.ts @@ -12,7 +12,6 @@ export { getAppLandingUrl } from '../redirect_to_landing'; export { getHostDetailsUrl, getHostsUrl } from '../redirect_to_hosts'; export { getNetworkUrl, getNetworkDetailsUrl } from '../redirect_to_network'; export { getTimelineTabsUrl, getTimelineUrl } from '../redirect_to_timelines'; -export { getAlertDetailsUrl, getAlertDetailsTabUrl } from '../redirect_to_alerts'; export { getCaseDetailsUrl, getCaseUrl, diff --git a/x-pack/plugins/security_solution/public/common/components/link_to/index.ts b/x-pack/plugins/security_solution/public/common/components/link_to/index.ts index 5ef5280df7fa0..abb3e9d7b85ff 100644 --- a/x-pack/plugins/security_solution/public/common/components/link_to/index.ts +++ b/x-pack/plugins/security_solution/public/common/components/link_to/index.ts @@ -15,7 +15,6 @@ import { import { useAppUrl } from '../../lib/kibana/hooks'; import type { SecurityPageName } from '../../../app/types'; -export { getAlertDetailsUrl, getAlertDetailsTabUrl } from './redirect_to_alerts'; export { getDetectionEngineUrl, getRuleDetailsUrl } from './redirect_to_detection_engine'; export { getHostDetailsUrl, getTabsOnHostDetailsUrl, getHostsUrl } from './redirect_to_hosts'; export { getKubernetesUrl, getKubernetesDetailsUrl } from './redirect_to_kubernetes'; diff --git a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_alerts.tsx b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_alerts.tsx deleted file mode 100644 index d29530f2cdfca..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_alerts.tsx +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { ALERTS_PATH } from '../../../../common/constants'; -import type { AlertDetailRouteType } from '../../../detections/pages/alert_details/types'; -import { appendSearch } from './helpers'; - -export const getAlertDetailsUrl = (alertId: string, search?: string) => - `/${alertId}/summary${appendSearch(search)}`; - -export const getAlertDetailsTabUrl = ( - detailName: string, - tabName: AlertDetailRouteType, - search?: string -) => `${ALERTS_PATH}/${detailName}/${tabName}${appendSearch(search)}`; diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/trailing_breadcrumbs.ts b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/trailing_breadcrumbs.ts index 5c45da1bb1ff2..7da3500775e48 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/trailing_breadcrumbs.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/trailing_breadcrumbs.ts @@ -15,7 +15,6 @@ import { getTrailingBreadcrumbs as geExceptionsBreadcrumbs } from '../../../../e import { getTrailingBreadcrumbs as getCSPBreadcrumbs } from '../../../../cloud_security_posture/breadcrumbs'; import { getTrailingBreadcrumbs as getUsersBreadcrumbs } from '../../../../explore/users/pages/details/breadcrumbs'; import { getTrailingBreadcrumbs as getKubernetesBreadcrumbs } from '../../../../kubernetes/pages/utils/breadcrumbs'; -import { getTrailingBreadcrumbs as getAlertDetailBreadcrumbs } from '../../../../detections/pages/alert_details/utils/breadcrumbs'; import { getTrailingBreadcrumbs as getDashboardBreadcrumbs } from '../../../../dashboards/pages/breadcrumbs'; export const getTrailingBreadcrumbs: GetTrailingBreadcrumbs = ( @@ -37,8 +36,6 @@ export const getTrailingBreadcrumbs: GetTrailingBreadcrumbs = ( return geExceptionsBreadcrumbs(spyState, getSecuritySolutionUrl); case SecurityPageName.kubernetes: return getKubernetesBreadcrumbs(spyState, getSecuritySolutionUrl); - case SecurityPageName.alerts: - return getAlertDetailBreadcrumbs(spyState, getSecuritySolutionUrl); case SecurityPageName.cloudSecurityPostureBenchmarks: return getCSPBreadcrumbs(spyState, getSecuritySolutionUrl); case SecurityPageName.dashboards: diff --git a/x-pack/plugins/security_solution/public/common/containers/cases/use_get_related_cases_by_event.ts b/x-pack/plugins/security_solution/public/common/containers/cases/use_get_related_cases_by_event.ts deleted file mode 100644 index fe20d4670f3a7..0000000000000 --- a/x-pack/plugins/security_solution/public/common/containers/cases/use_get_related_cases_by_event.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { useCallback, useState, useEffect } from 'react'; -import type { RelatedCase } from '@kbn/cases-plugin/common'; -import { useKibana, useToasts } from '../../lib/kibana'; -import { CASES_ERROR_TOAST } from '../../components/event_details/insights/translations'; -import { APP_ID } from '../../../../common/constants'; - -export const useGetRelatedCasesByEvent = (eventId: string) => { - const { - services: { cases }, - } = useKibana(); - const toasts = useToasts(); - - const [relatedCases, setRelatedCases] = useState(undefined); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - - const getRelatedCases = useCallback(async () => { - setLoading(true); - let relatedCasesResponse: RelatedCase[] = []; - try { - if (eventId) { - relatedCasesResponse = - (await cases.api.getRelatedCases(eventId, { - owner: APP_ID, - })) ?? []; - } - } catch (err) { - setError(err); - toasts.addWarning(CASES_ERROR_TOAST(err)); - } finally { - setRelatedCases(relatedCasesResponse); - setLoading(false); - } - }, [eventId, cases.api, toasts]); - - useEffect(() => { - getRelatedCases(); - }, [eventId, getRelatedCases]); - - return { - loading, - error, - relatedCases, - refetchRelatedCases: getRelatedCases, - }; -}; diff --git a/x-pack/plugins/security_solution/public/common/utils/route/types.ts b/x-pack/plugins/security_solution/public/common/utils/route/types.ts index 6e10a6f0d0c86..78d8c9bc39c97 100644 --- a/x-pack/plugins/security_solution/public/common/utils/route/types.ts +++ b/x-pack/plugins/security_solution/public/common/utils/route/types.ts @@ -11,7 +11,6 @@ import type React from 'react'; import type { AllRulesTabs } from '../../../detection_engine/rule_management_ui/components/rules_table/rules_table_toolbar'; import type { HostsTableType } from '../../../explore/hosts/store/model'; import type { NetworkRouteType } from '../../../explore/network/pages/navigation/types'; -import type { AlertDetailRouteType } from '../../../detections/pages/alert_details/types'; import type { AdministrationSubTab as AdministrationType } from '../../../management/types'; import type { FlowTarget } from '../../../../common/search_strategy'; import type { UsersTableType } from '../../../explore/users/store/model'; @@ -32,7 +31,6 @@ export type RouteSpyState = | GenericRouteSpyState | GenericRouteSpyState | GenericRouteSpyState - | GenericRouteSpyState | GenericRouteSpyState | GenericRouteSpyState | GenericRouteSpyState @@ -52,15 +50,10 @@ export type RouteSpyState = export type HostRouteSpyState = GenericRouteSpyState; export type UsersRouteSpyState = GenericRouteSpyState; export type NetworkRouteSpyState = GenericRouteSpyState; -export type AlertDetailRouteSpyState = GenericRouteSpyState< - SecurityPageName.alerts, - AlertDetailRouteType ->; export type AdministrationRouteSpyState = GenericRouteSpyState< SecurityPageName.administration, AdministrationType >; -export type DashboardsRouteSpyState = GenericRouteSpyState; export type RouteSpyAction = | { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.test.tsx index b180856da2b29..2b887808696bd 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.test.tsx @@ -99,7 +99,6 @@ const markAsOpenButton = '[data-test-subj="open-alert-status"]'; const markAsAcknowledgedButton = '[data-test-subj="acknowledged-alert-status"]'; const markAsClosedButton = '[data-test-subj="close-alert-status"]'; const addEndpointEventFilterButton = '[data-test-subj="add-event-filter-menu-item"]'; -const openAlertDetailsPageButton = '[data-test-subj="open-alert-details-page-menu-item"]'; const applyAlertTagsButton = '[data-test-subj="alert-tags-context-menu-item"]'; describe('Alert table context menu', () => { @@ -289,44 +288,6 @@ describe('Alert table context menu', () => { }); }); - describe('Open alert details action', () => { - test('it does not render the open alert details page action if kibana.alert.rule.uuid is not set', () => { - const nonAlertProps = { - ...props, - ecsRowData: { - ...ecsRowData, - kibana: { - alert: { - workflow_status: ['open'], - rule: { - parameters: {}, - uuid: [], - }, - }, - }, - }, - }; - - const wrapper = mount(, { - wrappingComponent: TestProviders, - }); - - wrapper.find(actionMenuButton).simulate('click'); - - expect(wrapper.find(openAlertDetailsPageButton).first().exists()).toEqual(false); - }); - - test('it renders the open alert details action button', () => { - const wrapper = mount(, { - wrappingComponent: TestProviders, - }); - - wrapper.find(actionMenuButton).simulate('click'); - - expect(wrapper.find(openAlertDetailsPageButton).first().exists()).toEqual(true); - }); - }); - describe('Apply alert tags action', () => { test('it renders the apply alert tags action button', () => { const wrapper = mount(, { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx index a05c351f3d22d..a04d8d197da1e 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx @@ -20,7 +20,6 @@ import { DEFAULT_ACTION_BUTTON_WIDTH } from '../../../../common/components/heade import { isActiveTimeline } from '../../../../helpers'; import { useOsqueryContextActionItem } from '../../osquery/use_osquery_context_action_item'; import { OsqueryFlyout } from '../../osquery/osquery_flyout'; -import { useRouteSpy } from '../../../../common/utils/route/use_route_spy'; import { buildGetAlertByIdQuery } from '../../../../detection_engine/rule_exceptions/utils/helpers'; import { useUserPrivileges } from '../../../../common/components/user_privileges'; import { EventsTdContent } from '../../../../timelines/components/timeline/styles'; @@ -44,7 +43,6 @@ import { useEventFilterAction } from './use_event_filter_action'; import { useAddToCaseActions } from './use_add_to_case_actions'; import { isAlertFromEndpointAlert } from '../../../../common/utils/endpoint_alert_check'; import type { Rule } from '../../../../detection_engine/rule_management/logic/types'; -import { useOpenAlertDetailsAction } from './use_open_alert_details'; import type { AlertTableContextMenuItem } from '../types'; import { useAlertTagsActions } from './use_alert_tags_actions'; @@ -73,7 +71,6 @@ const AlertContextMenuComponent: React.FC { const [isPopoverOpen, setPopover] = useState(false); const [isOsqueryFlyoutOpen, setOsqueryFlyoutOpen] = useState(false); - const [routeProps] = useRouteSpy(); const onMenuItemClick = useCallback(() => { setPopover(false); @@ -145,14 +142,11 @@ const AlertContextMenuComponent: React.FC { if (isActiveTimeline(scopeId ?? '')) { refetchQuery([timelineQuery]); - if (routeProps.pageName === 'alerts') { - refetchQuery(globalQuery); - } } else { refetchQuery(globalQuery); if (refetch) refetch(); } - }, [scopeId, globalQuery, timelineQuery, routeProps, refetch]); + }, [scopeId, globalQuery, timelineQuery, refetch]); const ruleIndex = ecsRowData['kibana.alert.rule.parameters']?.index ?? ecsRowData?.signal?.rule?.index; @@ -216,12 +210,6 @@ const AlertContextMenuComponent: React.FC void; - alertId: string | null; -} - -export const ACTION_OPEN_ALERT_DETAILS_PAGE = i18n.translate( - 'xpack.securitySolution.detectionEngine.alerts.actions.openAlertDetails', - { - defaultMessage: 'Open alert details page', - } -); - -export const useOpenAlertDetailsAction = ({ ruleId, closePopover, alertId }: Props) => { - const isAlertDetailsPageEnabled = useIsExperimentalFeatureEnabled('alertDetailsPageEnabled'); - const alertDetailsActionItems: AlertTableContextMenuItem[] = []; - const { onClick } = useGetSecuritySolutionLinkProps()({ - deepLinkId: SecurityPageName.alerts, - path: alertId ? getAlertDetailsUrl(alertId) : '', - }); - - // We check ruleId to confirm this is an alert, as this page does not support events as of 8.6 - if (ruleId && alertId && isAlertDetailsPageEnabled) { - alertDetailsActionItems.push({ - key: 'open-alert-details-item', - 'data-test-subj': 'open-alert-details-page-menu-item', - onClick, - name: ACTION_OPEN_ALERT_DETAILS_PAGE, - }); - } - - return { - alertDetailsActionItems, - }; -}; diff --git a/x-pack/plugins/security_solution/public/detections/pages/alert_details/__mocks__/alert_details_response.ts b/x-pack/plugins/security_solution/public/detections/pages/alert_details/__mocks__/alert_details_response.ts deleted file mode 100644 index 5c4e985abf717..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/alert_details/__mocks__/alert_details_response.ts +++ /dev/null @@ -1,2020 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; - -// This data was generated using the endpoint test alert generator -export const getMockAlertDetailsFieldsResponse = () => ({ - _index: '.internal.alerts-security.alerts-default-000001', - _id: 'f6aa8643ecee466753c45308ea8dc72aba0a44e1faac5f6183fd2ad6666c1325', - _score: 1, - fields: { - 'kibana.alert.severity': ['medium'], - 'process.hash.md5': ['fake md5'], - 'kibana.alert.rule.updated_by': ['elastic'], - 'signal.ancestors.depth': [0], - 'event.category': ['malware'], - 'kibana.alert.rule.rule_name_override': ['message'], - 'Endpoint.capabilities': ['isolation', 'kill_process', 'suspend_process', 'running_processes'], - 'process.parent.pid': [1], - 'process.hash.sha256': ['fake sha256'], - 'host.hostname': ['Host-4cfuh42w7g'], - 'kibana.alert.rule.tags': ['Elastic', 'Endpoint Security'], - 'host.mac': ['f2-32-1b-dc-ec-80'], - 'elastic.agent.id': ['d08ed3f8-9852-4d0c-a5b1-b48060705369'], - 'dll.hash.sha256': ['8ad40c90a611d36eb8f9eb24fa04f7dbca713db383ff55a03aa0f382e92061a2'], - 'kibana.alert.ancestors.depth': [0], - 'signal.rule.enabled': ['true'], - 'signal.rule.max_signals': [10000], - 'host.os.version': ['10.0'], - 'signal.rule.updated_at': ['2022-09-29T19:39:38.137Z'], - 'kibana.alert.risk_score': [47], - 'Endpoint.policy.applied.id': ['C2A9093E-E289-4C0A-AA44-8C32A414FA7A'], - 'kibana.alert.rule.severity_mapping.severity': ['low', 'medium', 'high', 'critical'], - 'event.agent_id_status': ['auth_metadata_missing'], - 'kibana.alert.original_event.id': ['7799e1d5-5dc1-4173-9d11-562496cd863b'], - 'kibana.alert.rule.risk_score_mapping.value': [''], - 'process.Ext.ancestry': ['kj0le842x0', '1r4s9i1br4'], - 'signal.original_event.code': ['memory_signature'], - 'kibana.alert.original_event.module': ['endpoint'], - 'kibana.alert.rule.interval': ['5m'], - 'kibana.alert.rule.type': ['query'], - 'signal.original_event.sequence': [1232], - 'Endpoint.state.isolation': [true], - 'host.architecture': ['x7n6yt4fol'], - 'kibana.alert.rule.immutable': ['true'], - 'kibana.alert.original_event.type': ['info'], - 'event.code': ['memory_signature'], - 'agent.id': ['d08ed3f8-9852-4d0c-a5b1-b48060705369'], - 'signal.original_event.module': ['endpoint'], - 'kibana.alert.rule.exceptions_list.list_id': ['endpoint_list'], - 'signal.rule.from': ['now-10m'], - 'kibana.alert.rule.exceptions_list.type': ['endpoint'], - 'process.group_leader.entity_id': ['b74mw1jkrm'], - 'dll.Ext.malware_classification.version': ['3.0.0'], - 'kibana.alert.rule.enabled': ['true'], - 'kibana.alert.rule.version': ['100'], - 'kibana.alert.ancestors.type': ['event'], - 'process.entry_leader.name': ['fake entry'], - 'dll.Ext.compile_time': [1534424710], - 'signal.ancestors.index': ['.ds-logs-endpoint.alerts-default-2022.09.29-000001'], - 'dll.Ext.malware_classification.score': [0], - 'process.entity_id': ['d3v4to81q9'], - 'host.ip': ['10.184.3.36', '10.170.218.86'], - 'agent.type': ['endpoint'], - 'signal.original_event.category': ['malware'], - 'signal.original_event.id': ['7799e1d5-5dc1-4173-9d11-562496cd863b'], - 'process.uptime': [0], - 'Endpoint.policy.applied.name': ['With Eventing'], - 'host.id': ['04794e4e-59cb-4c4a-a8ee-3e6c5b65743c'], - 'process.Ext.code_signature.subject_name': ['bad signer'], - 'process.Ext.token.integrity_level_name': ['high'], - 'signal.original_event.type': ['info'], - 'kibana.alert.rule.max_signals': [10000], - 'signal.rule.author': ['Elastic'], - 'kibana.alert.rule.risk_score': [47], - 'dll.Ext.malware_classification.identifier': ['Whitelisted'], - 'dll.Ext.mapped_address': [5362483200], - 'signal.original_event.dataset': ['endpoint'], - 'kibana.alert.rule.consumer': ['siem'], - 'kibana.alert.rule.indices': ['logs-endpoint.alerts-*'], - 'kibana.alert.rule.category': ['Custom Query Rule'], - 'host.os.Ext.variant': ['Windows Server'], - 'event.ingested': ['2022-09-29T19:37:00.000Z'], - 'event.action': ['start'], - 'signal.rule.updated_by': ['elastic'], - '@timestamp': ['2022-09-29T19:40:26.051Z'], - 'kibana.alert.original_event.action': ['start'], - 'host.os.platform': ['Windows'], - 'process.session_leader.entity_id': ['b74mw1jkrm'], - 'kibana.alert.rule.severity': ['medium'], - 'kibana.alert.original_event.agent_id_status': ['auth_metadata_missing'], - 'Endpoint.status': ['enrolled'], - 'data_stream.dataset': ['endpoint.alerts'], - 'signal.rule.timestamp_override': ['event.ingested'], - 'kibana.alert.rule.execution.uuid': ['abf39d36-0f1c-4bf9-ae42-1039285380b5'], - 'kibana.alert.uuid': ['f6aa8643ecee466753c45308ea8dc72aba0a44e1faac5f6183fd2ad6666c1325'], - 'kibana.version': ['8.6.0'], - 'process.hash.sha1': ['fake sha1'], - 'event.id': ['7799e1d5-5dc1-4173-9d11-562496cd863b'], - 'process.entry_leader.pid': [865], - 'signal.rule.license': ['Elastic License v2'], - 'signal.ancestors.type': ['event'], - 'kibana.alert.rule.rule_id': ['9a1a2dae-0b5f-4c3d-8305-a268d404c306'], - 'process.session_leader.pid': [745], - 'signal.rule.type': ['query'], - 'Endpoint.policy.applied.version': [5], - 'dll.hash.md5': ['1f2d082566b0fc5f2c238a5180db7451'], - 'kibana.alert.ancestors.id': ['7L3AioMBWJvcpv7vlX2O'], - 'user.name': ['root'], - 'source.ip': ['10.184.3.46'], - 'signal.rule.rule_name_override': ['message'], - 'process.group_leader.name': ['fake leader'], - 'host.os.full': ['Windows Server 2016'], - 'kibana.alert.original_event.code': ['memory_signature'], - 'kibana.alert.rule.risk_score_mapping.field': ['event.risk_score'], - 'kibana.alert.rule.description': [ - 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', - ], - 'process.pid': [2], - 'kibana.alert.rule.producer': ['siem'], - 'kibana.alert.rule.to': ['now'], - 'signal.rule.interval': ['5m'], - 'signal.rule.created_by': ['elastic'], - 'kibana.alert.rule.created_by': ['elastic'], - 'kibana.alert.rule.timestamp_override': ['event.ingested'], - 'kibana.alert.original_event.ingested': ['2022-09-29T19:37:00.000Z'], - 'signal.rule.id': ['738e91f2-402e-11ed-be15-7be3bb26d7b2'], - 'process.parent.entity_id': ['kj0le842x0'], - 'signal.rule.risk_score': [47], - 'signal.reason': [ - 'malware event with process explorer.exe, on Host-4cfuh42w7g created medium alert Endpoint Security.', - ], - 'host.os.name': ['Windows'], - 'kibana.alert.rule.name': ['Endpoint Security'], - 'host.name': ['Host-4cfuh42w7g'], - 'signal.status': ['open'], - 'event.kind': ['signal'], - 'kibana.alert.rule.severity_mapping.value': ['21', '47', '73', '99'], - 'signal.rule.tags': ['Elastic', 'Endpoint Security'], - 'signal.rule.created_at': ['2022-09-29T19:39:38.137Z'], - 'kibana.alert.workflow_status': ['open'], - 'Endpoint.policy.applied.status': ['warning'], - 'kibana.alert.rule.uuid': ['738e91f2-402e-11ed-be15-7be3bb26d7b2'], - 'kibana.alert.original_event.category': ['malware'], - 'dll.Ext.malware_classification.threshold': [0], - 'kibana.alert.reason': [ - 'malware event with process explorer.exe, on Host-4cfuh42w7g created medium alert Endpoint Security.', - ], - 'dll.pe.architecture': ['x64'], - 'data_stream.type': ['logs'], - 'signal.original_time': ['2022-10-09T07:14:42.194Z'], - 'signal.ancestors.id': ['7L3AioMBWJvcpv7vlX2O'], - 'process.name': ['explorer.exe'], - 'ecs.version': ['1.6.0'], - 'signal.rule.severity': ['medium'], - 'kibana.alert.ancestors.index': ['.ds-logs-endpoint.alerts-default-2022.09.29-000001'], - 'Endpoint.configuration.isolation': [true], - 'Memory_protection.feature': ['signature'], - 'dll.code_signature.trusted': [true], - 'process.Ext.code_signature.trusted': [false], - 'kibana.alert.depth': [1], - 'agent.version': ['8.6.0'], - 'kibana.alert.rule.risk_score_mapping.operator': ['equals'], - 'host.os.family': ['windows'], - 'kibana.alert.rule.from': ['now-10m'], - 'Memory_protection.self_injection': [true], - 'process.start': ['2022-10-09T07:14:42.194Z'], - 'kibana.alert.rule.parameters': [ - { - severity_mapping: [ - { - severity: 'low', - field: 'event.severity', - value: '21', - operator: 'equals', - }, - { - severity: 'medium', - field: 'event.severity', - value: '47', - operator: 'equals', - }, - { - severity: 'high', - field: 'event.severity', - value: '73', - operator: 'equals', - }, - { - severity: 'critical', - field: 'event.severity', - value: '99', - operator: 'equals', - }, - ], - references: [], - description: - 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', - language: 'kuery', - type: 'query', - rule_name_override: 'message', - exceptions_list: [ - { - list_id: 'endpoint_list', - namespace_type: 'agnostic', - id: 'endpoint_list', - type: 'endpoint', - }, - ], - timestamp_override: 'event.ingested', - from: 'now-10m', - severity: 'medium', - max_signals: 10000, - risk_score: 47, - risk_score_mapping: [ - { - field: 'event.risk_score', - value: '', - operator: 'equals', - }, - ], - author: ['Elastic'], - query: 'event.kind:alert and event.module:(endpoint and not endgame)\n', - index: ['logs-endpoint.alerts-*'], - version: 100, - rule_id: '9a1a2dae-0b5f-4c3d-8305-a268d404c306', - license: 'Elastic License v2', - required_fields: [ - { - ecs: true, - name: 'event.kind', - type: 'keyword', - }, - { - ecs: true, - name: 'event.module', - type: 'keyword', - }, - ], - immutable: true, - related_integrations: [], - setup: '', - false_positives: [], - threat: [], - to: 'now', - }, - ], - 'signal.rule.version': ['100'], - 'signal.original_event.kind': ['alert'], - 'kibana.alert.status': ['active'], - 'kibana.alert.rule.severity_mapping.field': [ - 'event.severity', - 'event.severity', - 'event.severity', - 'event.severity', - ], - 'kibana.alert.original_event.dataset': ['endpoint'], - 'signal.depth': [1], - 'signal.rule.immutable': ['true'], - 'process.group_leader.pid': [116], - 'event.sequence': [1232], - 'kibana.alert.rule.rule_type_id': ['siem.queryRule'], - 'process.session_leader.name': ['fake session'], - 'signal.rule.name': ['Endpoint Security'], - 'signal.rule.rule_id': ['9a1a2dae-0b5f-4c3d-8305-a268d404c306'], - 'event.module': ['endpoint'], - 'dll.hash.sha1': ['ca85243c0af6a6471bdaa560685c51eefd6dbc0d'], - 'kibana.alert.rule.severity_mapping.operator': ['equals', 'equals', 'equals', 'equals'], - 'process.Ext.malware_signature.all_names': ['Windows.Trojan.FakeAgent'], - 'kibana.alert.rule.license': ['Elastic License v2'], - 'kibana.alert.original_event.kind': ['alert'], - 'process.executable': ['C:/fake/explorer.exe'], - 'kibana.alert.rule.updated_at': ['2022-09-29T19:39:38.137Z'], - 'signal.rule.description': [ - 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', - ], - 'dll.Ext.mapped_size': [0], - 'data_stream.namespace': ['default'], - 'kibana.alert.rule.author': ['Elastic'], - 'dll.code_signature.subject_name': ['Cybereason Inc'], - 'Endpoint.policy.applied.endpoint_policy_version': [3], - 'kibana.alert.original_event.sequence': [1232], - 'dll.path': ['C:\\Program Files\\Cybereason ActiveProbe\\AmSvc.exe'], - 'process.Ext.user': ['SYSTEM'], - 'signal.original_event.action': ['start'], - 'signal.rule.to': ['now'], - 'kibana.alert.rule.created_at': ['2022-09-29T19:39:38.137Z'], - 'process.Ext.malware_signature.identifier': ['diagnostic-malware-signature-v1-fake'], - 'kibana.alert.rule.exceptions_list.namespace_type': ['agnostic'], - 'event.type': ['info'], - 'kibana.space_ids': ['default'], - 'process.entry_leader.entity_id': ['b74mw1jkrm'], - 'kibana.alert.rule.exceptions_list.id': ['endpoint_list'], - 'event.dataset': ['endpoint'], - 'kibana.alert.original_time': ['2022-10-09T07:14:42.194Z'], - }, -}); - -export const getMockAlertDetailsTimelineResponse = () => [ - { - category: 'kibana', - field: 'kibana.alert.severity', - values: ['medium'], - originalValue: ['medium'], - isObjectArray: false, - }, - { - category: 'process', - field: 'process.hash.md5', - values: ['fake md5'], - originalValue: ['fake md5'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.updated_by', - values: ['elastic'], - originalValue: ['elastic'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.ancestors.depth', - values: ['0'], - originalValue: ['0'], - isObjectArray: false, - }, - { - category: 'event', - field: 'event.category', - values: ['malware'], - originalValue: ['malware'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.rule_name_override', - values: ['message'], - originalValue: ['message'], - isObjectArray: false, - }, - { - category: 'Endpoint', - field: 'Endpoint.capabilities', - values: ['isolation', 'kill_process', 'suspend_process', 'running_processes'], - originalValue: ['isolation', 'kill_process', 'suspend_process', 'running_processes'], - isObjectArray: false, - }, - { - category: 'process', - field: 'process.parent.pid', - values: ['1'], - originalValue: ['1'], - isObjectArray: false, - }, - { - category: 'process', - field: 'process.hash.sha256', - values: ['fake sha256'], - originalValue: ['fake sha256'], - isObjectArray: false, - }, - { - category: 'host', - field: 'host.hostname', - values: ['Host-4cfuh42w7g'], - originalValue: ['Host-4cfuh42w7g'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.tags', - values: ['Elastic', 'Endpoint Security'], - originalValue: ['Elastic', 'Endpoint Security'], - isObjectArray: false, - }, - { - category: 'host', - field: 'host.mac', - values: ['f2-32-1b-dc-ec-80'], - originalValue: ['f2-32-1b-dc-ec-80'], - isObjectArray: false, - }, - { - category: 'elastic', - field: 'elastic.agent.id', - values: ['d08ed3f8-9852-4d0c-a5b1-b48060705369'], - originalValue: ['d08ed3f8-9852-4d0c-a5b1-b48060705369'], - isObjectArray: false, - }, - { - category: 'dll', - field: 'dll.hash.sha256', - values: ['8ad40c90a611d36eb8f9eb24fa04f7dbca713db383ff55a03aa0f382e92061a2'], - originalValue: ['8ad40c90a611d36eb8f9eb24fa04f7dbca713db383ff55a03aa0f382e92061a2'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.ancestors.depth', - values: ['0'], - originalValue: ['0'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.rule.enabled', - values: ['true'], - originalValue: ['true'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.rule.max_signals', - values: ['10000'], - originalValue: ['10000'], - isObjectArray: false, - }, - { - category: 'host', - field: 'host.os.version', - values: ['10.0'], - originalValue: ['10.0'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.rule.updated_at', - values: ['2022-09-29T19:39:38.137Z'], - originalValue: ['2022-09-29T19:39:38.137Z'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.risk_score', - values: ['47'], - originalValue: ['47'], - isObjectArray: false, - }, - { - category: 'Endpoint', - field: 'Endpoint.policy.applied.id', - values: ['C2A9093E-E289-4C0A-AA44-8C32A414FA7A'], - originalValue: ['C2A9093E-E289-4C0A-AA44-8C32A414FA7A'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.severity_mapping.severity', - values: ['low', 'medium', 'high', 'critical'], - originalValue: ['low', 'medium', 'high', 'critical'], - isObjectArray: false, - }, - { - category: 'event', - field: 'event.agent_id_status', - values: ['auth_metadata_missing'], - originalValue: ['auth_metadata_missing'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.original_event.id', - values: ['7799e1d5-5dc1-4173-9d11-562496cd863b'], - originalValue: ['7799e1d5-5dc1-4173-9d11-562496cd863b'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.risk_score_mapping.value', - values: [''], - originalValue: [''], - isObjectArray: false, - }, - { - category: 'process', - field: 'process.Ext.ancestry', - values: ['kj0le842x0', '1r4s9i1br4'], - originalValue: ['kj0le842x0', '1r4s9i1br4'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.original_event.code', - values: ['memory_signature'], - originalValue: ['memory_signature'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.original_event.module', - values: ['endpoint'], - originalValue: ['endpoint'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.interval', - values: ['5m'], - originalValue: ['5m'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.type', - values: ['query'], - originalValue: ['query'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.original_event.sequence', - values: ['1232'], - originalValue: ['1232'], - isObjectArray: false, - }, - { - category: 'Endpoint', - field: 'Endpoint.state.isolation', - values: ['true'], - originalValue: ['true'], - isObjectArray: false, - }, - { - category: 'host', - field: 'host.architecture', - values: ['x7n6yt4fol'], - originalValue: ['x7n6yt4fol'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.immutable', - values: ['true'], - originalValue: ['true'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.original_event.type', - values: ['info'], - originalValue: ['info'], - isObjectArray: false, - }, - { - category: 'event', - field: 'event.code', - values: ['memory_signature'], - originalValue: ['memory_signature'], - isObjectArray: false, - }, - { - category: 'agent', - field: 'agent.id', - values: ['d08ed3f8-9852-4d0c-a5b1-b48060705369'], - originalValue: ['d08ed3f8-9852-4d0c-a5b1-b48060705369'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.original_event.module', - values: ['endpoint'], - originalValue: ['endpoint'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.exceptions_list.list_id', - values: ['endpoint_list'], - originalValue: ['endpoint_list'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.rule.from', - values: ['now-10m'], - originalValue: ['now-10m'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.exceptions_list.type', - values: ['endpoint'], - originalValue: ['endpoint'], - isObjectArray: false, - }, - { - category: 'process', - field: 'process.group_leader.entity_id', - values: ['b74mw1jkrm'], - originalValue: ['b74mw1jkrm'], - isObjectArray: false, - }, - { - category: 'dll', - field: 'dll.Ext.malware_classification.version', - values: ['3.0.0'], - originalValue: ['3.0.0'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.enabled', - values: ['true'], - originalValue: ['true'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.version', - values: ['100'], - originalValue: ['100'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.ancestors.type', - values: ['event'], - originalValue: ['event'], - isObjectArray: false, - }, - { - category: 'process', - field: 'process.entry_leader.name', - values: ['fake entry'], - originalValue: ['fake entry'], - isObjectArray: false, - }, - { - category: 'dll', - field: 'dll.Ext.compile_time', - values: ['1534424710'], - originalValue: ['1534424710'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.ancestors.index', - values: ['.ds-logs-endpoint.alerts-default-2022.09.29-000001'], - originalValue: ['.ds-logs-endpoint.alerts-default-2022.09.29-000001'], - isObjectArray: false, - }, - { - category: 'dll', - field: 'dll.Ext.malware_classification.score', - values: ['0'], - originalValue: ['0'], - isObjectArray: false, - }, - { - category: 'process', - field: 'process.entity_id', - values: ['d3v4to81q9'], - originalValue: ['d3v4to81q9'], - isObjectArray: false, - }, - { - category: 'host', - field: 'host.ip', - values: ['10.184.3.36', '10.170.218.86'], - originalValue: ['10.184.3.36', '10.170.218.86'], - isObjectArray: false, - }, - { - category: 'agent', - field: 'agent.type', - values: ['endpoint'], - originalValue: ['endpoint'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.original_event.category', - values: ['malware'], - originalValue: ['malware'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.original_event.id', - values: ['7799e1d5-5dc1-4173-9d11-562496cd863b'], - originalValue: ['7799e1d5-5dc1-4173-9d11-562496cd863b'], - isObjectArray: false, - }, - { - category: 'process', - field: 'process.uptime', - values: ['0'], - originalValue: ['0'], - isObjectArray: false, - }, - { - category: 'Endpoint', - field: 'Endpoint.policy.applied.name', - values: ['With Eventing'], - originalValue: ['With Eventing'], - isObjectArray: false, - }, - { - category: 'host', - field: 'host.id', - values: ['04794e4e-59cb-4c4a-a8ee-3e6c5b65743c'], - originalValue: ['04794e4e-59cb-4c4a-a8ee-3e6c5b65743c'], - isObjectArray: false, - }, - { - category: 'process', - field: 'process.Ext.code_signature.subject_name', - values: ['bad signer'], - originalValue: ['bad signer'], - isObjectArray: false, - }, - { - category: 'process', - field: 'process.Ext.token.integrity_level_name', - values: ['high'], - originalValue: ['high'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.original_event.type', - values: ['info'], - originalValue: ['info'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.max_signals', - values: ['10000'], - originalValue: ['10000'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.rule.author', - values: ['Elastic'], - originalValue: ['Elastic'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.risk_score', - values: ['47'], - originalValue: ['47'], - isObjectArray: false, - }, - { - category: 'dll', - field: 'dll.Ext.malware_classification.identifier', - values: ['Whitelisted'], - originalValue: ['Whitelisted'], - isObjectArray: false, - }, - { - category: 'dll', - field: 'dll.Ext.mapped_address', - values: ['5362483200'], - originalValue: ['5362483200'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.original_event.dataset', - values: ['endpoint'], - originalValue: ['endpoint'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.consumer', - values: ['siem'], - originalValue: ['siem'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.indices', - values: ['logs-endpoint.alerts-*'], - originalValue: ['logs-endpoint.alerts-*'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.category', - values: ['Custom Query Rule'], - originalValue: ['Custom Query Rule'], - isObjectArray: false, - }, - { - category: 'host', - field: 'host.os.Ext.variant', - values: ['Windows Server'], - originalValue: ['Windows Server'], - isObjectArray: false, - }, - { - category: 'event', - field: 'event.ingested', - values: ['2022-09-29T19:37:00.000Z'], - originalValue: ['2022-09-29T19:37:00.000Z'], - isObjectArray: false, - }, - { - category: 'event', - field: 'event.action', - values: ['start'], - originalValue: ['start'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.rule.updated_by', - values: ['elastic'], - originalValue: ['elastic'], - isObjectArray: false, - }, - { - category: 'base', - field: '@timestamp', - values: ['2022-09-29T19:40:26.051Z'], - originalValue: ['2022-09-29T19:40:26.051Z'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.original_event.action', - values: ['start'], - originalValue: ['start'], - isObjectArray: false, - }, - { - category: 'host', - field: 'host.os.platform', - values: ['Windows'], - originalValue: ['Windows'], - isObjectArray: false, - }, - { - category: 'process', - field: 'process.session_leader.entity_id', - values: ['b74mw1jkrm'], - originalValue: ['b74mw1jkrm'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.severity', - values: ['medium'], - originalValue: ['medium'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.original_event.agent_id_status', - values: ['auth_metadata_missing'], - originalValue: ['auth_metadata_missing'], - isObjectArray: false, - }, - { - category: 'Endpoint', - field: 'Endpoint.status', - values: ['enrolled'], - originalValue: ['enrolled'], - isObjectArray: false, - }, - { - category: 'data_stream', - field: 'data_stream.dataset', - values: ['endpoint.alerts'], - originalValue: ['endpoint.alerts'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.rule.timestamp_override', - values: ['event.ingested'], - originalValue: ['event.ingested'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.execution.uuid', - values: ['abf39d36-0f1c-4bf9-ae42-1039285380b5'], - originalValue: ['abf39d36-0f1c-4bf9-ae42-1039285380b5'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.uuid', - values: ['f6aa8643ecee466753c45308ea8dc72aba0a44e1faac5f6183fd2ad6666c1325'], - originalValue: ['f6aa8643ecee466753c45308ea8dc72aba0a44e1faac5f6183fd2ad6666c1325'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.version', - values: ['8.6.0'], - originalValue: ['8.6.0'], - isObjectArray: false, - }, - { - category: 'process', - field: 'process.hash.sha1', - values: ['fake sha1'], - originalValue: ['fake sha1'], - isObjectArray: false, - }, - { - category: 'event', - field: 'event.id', - values: ['7799e1d5-5dc1-4173-9d11-562496cd863b'], - originalValue: ['7799e1d5-5dc1-4173-9d11-562496cd863b'], - isObjectArray: false, - }, - { - category: 'process', - field: 'process.entry_leader.pid', - values: ['865'], - originalValue: ['865'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.rule.license', - values: ['Elastic License v2'], - originalValue: ['Elastic License v2'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.ancestors.type', - values: ['event'], - originalValue: ['event'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.rule_id', - values: ['9a1a2dae-0b5f-4c3d-8305-a268d404c306'], - originalValue: ['9a1a2dae-0b5f-4c3d-8305-a268d404c306'], - isObjectArray: false, - }, - { - category: 'process', - field: 'process.session_leader.pid', - values: ['745'], - originalValue: ['745'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.rule.type', - values: ['query'], - originalValue: ['query'], - isObjectArray: false, - }, - { - category: 'Endpoint', - field: 'Endpoint.policy.applied.version', - values: ['5'], - originalValue: ['5'], - isObjectArray: false, - }, - { - category: 'dll', - field: 'dll.hash.md5', - values: ['1f2d082566b0fc5f2c238a5180db7451'], - originalValue: ['1f2d082566b0fc5f2c238a5180db7451'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.ancestors.id', - values: ['7L3AioMBWJvcpv7vlX2O'], - originalValue: ['7L3AioMBWJvcpv7vlX2O'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.rule.rule_name_override', - values: ['message'], - originalValue: ['message'], - isObjectArray: false, - }, - { - category: 'process', - field: 'process.group_leader.name', - values: ['fake leader'], - originalValue: ['fake leader'], - isObjectArray: false, - }, - { - category: 'host', - field: 'host.os.full', - values: ['Windows Server 2016'], - originalValue: ['Windows Server 2016'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.original_event.code', - values: ['memory_signature'], - originalValue: ['memory_signature'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.risk_score_mapping.field', - values: ['event.risk_score'], - originalValue: ['event.risk_score'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.description', - values: [ - 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', - ], - originalValue: [ - 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', - ], - isObjectArray: false, - }, - { - category: 'process', - field: 'process.pid', - values: ['2'], - originalValue: ['2'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.producer', - values: ['siem'], - originalValue: ['siem'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.to', - values: ['now'], - originalValue: ['now'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.rule.interval', - values: ['5m'], - originalValue: ['5m'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.rule.created_by', - values: ['elastic'], - originalValue: ['elastic'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.created_by', - values: ['elastic'], - originalValue: ['elastic'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.timestamp_override', - values: ['event.ingested'], - originalValue: ['event.ingested'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.original_event.ingested', - values: ['2022-09-29T19:37:00.000Z'], - originalValue: ['2022-09-29T19:37:00.000Z'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.rule.id', - values: ['738e91f2-402e-11ed-be15-7be3bb26d7b2'], - originalValue: ['738e91f2-402e-11ed-be15-7be3bb26d7b2'], - isObjectArray: false, - }, - { - category: 'process', - field: 'process.parent.entity_id', - values: ['kj0le842x0'], - originalValue: ['kj0le842x0'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.rule.risk_score', - values: ['47'], - originalValue: ['47'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.reason', - values: [ - 'malware event with process explorer.exe, on Host-4cfuh42w7g created medium alert Endpoint Security.', - ], - originalValue: [ - 'malware event with process explorer.exe, on Host-4cfuh42w7g created medium alert Endpoint Security.', - ], - isObjectArray: false, - }, - { - category: 'host', - field: 'host.os.name', - values: ['Windows'], - originalValue: ['Windows'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.name', - values: ['Endpoint Security'], - originalValue: ['Endpoint Security'], - isObjectArray: false, - }, - { - category: 'host', - field: 'host.name', - values: ['Host-4cfuh42w7g'], - originalValue: ['Host-4cfuh42w7g'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.status', - values: ['open'], - originalValue: ['open'], - isObjectArray: false, - }, - { - category: 'event', - field: 'event.kind', - values: ['signal'], - originalValue: ['signal'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.severity_mapping.value', - values: ['21', '47', '73', '99'], - originalValue: ['21', '47', '73', '99'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.rule.tags', - values: ['Elastic', 'Endpoint Security'], - originalValue: ['Elastic', 'Endpoint Security'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.rule.created_at', - values: ['2022-09-29T19:39:38.137Z'], - originalValue: ['2022-09-29T19:39:38.137Z'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.workflow_status', - values: ['open'], - originalValue: ['open'], - isObjectArray: false, - }, - { - category: 'Endpoint', - field: 'Endpoint.policy.applied.status', - values: ['warning'], - originalValue: ['warning'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.uuid', - values: ['738e91f2-402e-11ed-be15-7be3bb26d7b2'], - originalValue: ['738e91f2-402e-11ed-be15-7be3bb26d7b2'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.original_event.category', - values: ['malware'], - originalValue: ['malware'], - isObjectArray: false, - }, - { - category: 'dll', - field: 'dll.Ext.malware_classification.threshold', - values: ['0'], - originalValue: ['0'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.reason', - values: [ - 'malware event with process explorer.exe, on Host-4cfuh42w7g created medium alert Endpoint Security.', - ], - originalValue: [ - 'malware event with process explorer.exe, on Host-4cfuh42w7g created medium alert Endpoint Security.', - ], - isObjectArray: false, - }, - { - category: 'dll', - field: 'dll.pe.architecture', - values: ['x64'], - originalValue: ['x64'], - isObjectArray: false, - }, - { - category: 'data_stream', - field: 'data_stream.type', - values: ['logs'], - originalValue: ['logs'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.original_time', - values: ['2022-10-09T07:14:42.194Z'], - originalValue: ['2022-10-09T07:14:42.194Z'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.ancestors.id', - values: ['7L3AioMBWJvcpv7vlX2O'], - originalValue: ['7L3AioMBWJvcpv7vlX2O'], - isObjectArray: false, - }, - { - category: 'process', - field: 'process.name', - values: ['explorer.exe'], - originalValue: ['explorer.exe'], - isObjectArray: false, - }, - { - category: 'ecs', - field: 'ecs.version', - values: ['1.6.0'], - originalValue: ['1.6.0'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.rule.severity', - values: ['medium'], - originalValue: ['medium'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.ancestors.index', - values: ['.ds-logs-endpoint.alerts-default-2022.09.29-000001'], - originalValue: ['.ds-logs-endpoint.alerts-default-2022.09.29-000001'], - isObjectArray: false, - }, - { - category: 'Endpoint', - field: 'Endpoint.configuration.isolation', - values: ['true'], - originalValue: ['true'], - isObjectArray: false, - }, - { - category: 'Memory_protection', - field: 'Memory_protection.feature', - values: ['signature'], - originalValue: ['signature'], - isObjectArray: false, - }, - { - category: 'dll', - field: 'dll.code_signature.trusted', - values: ['true'], - originalValue: ['true'], - isObjectArray: false, - }, - { - category: 'process', - field: 'process.Ext.code_signature.trusted', - values: ['false'], - originalValue: ['false'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.depth', - values: ['1'], - originalValue: ['1'], - isObjectArray: false, - }, - { - category: 'agent', - field: 'agent.version', - values: ['8.6.0'], - originalValue: ['8.6.0'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.risk_score_mapping.operator', - values: ['equals'], - originalValue: ['equals'], - isObjectArray: false, - }, - { - category: 'host', - field: 'host.os.family', - values: ['windows'], - originalValue: ['windows'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.from', - values: ['now-10m'], - originalValue: ['now-10m'], - isObjectArray: false, - }, - { - category: 'Memory_protection', - field: 'Memory_protection.self_injection', - values: ['true'], - originalValue: ['true'], - isObjectArray: false, - }, - { - category: 'process', - field: 'process.start', - values: ['2022-10-09T07:14:42.194Z'], - originalValue: ['2022-10-09T07:14:42.194Z'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.severity_mapping.severity', - values: ['low', 'medium', 'high', 'critical'], - originalValue: ['low', 'medium', 'high', 'critical'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.severity_mapping.field', - values: ['event.severity'], - originalValue: ['event.severity'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.severity_mapping.value', - values: ['21', '47', '73', '99'], - originalValue: ['21', '47', '73', '99'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.severity_mapping.operator', - values: ['equals'], - originalValue: ['equals'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.references', - values: [], - originalValue: [], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.description', - values: [ - 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', - ], - originalValue: [ - 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', - ], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.language', - values: ['kuery'], - originalValue: ['kuery'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.type', - values: ['query'], - originalValue: ['query'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.rule_name_override', - values: ['message'], - originalValue: ['message'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.exceptions_list.list_id', - values: ['endpoint_list'], - originalValue: ['endpoint_list'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.exceptions_list.namespace_type', - values: ['agnostic'], - originalValue: ['agnostic'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.exceptions_list.id', - values: ['endpoint_list'], - originalValue: ['endpoint_list'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.exceptions_list.type', - values: ['endpoint'], - originalValue: ['endpoint'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.timestamp_override', - values: ['event.ingested'], - originalValue: ['event.ingested'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.from', - values: ['now-10m'], - originalValue: ['now-10m'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.severity', - values: ['medium'], - originalValue: ['medium'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.max_signals', - values: ['10000'], - originalValue: ['10000'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.risk_score', - values: ['47'], - originalValue: ['47'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.risk_score_mapping.field', - values: ['event.risk_score'], - originalValue: ['event.risk_score'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.risk_score_mapping.value', - values: [''], - originalValue: [''], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.risk_score_mapping.operator', - values: ['equals'], - originalValue: ['equals'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.author', - values: ['Elastic'], - originalValue: ['Elastic'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.query', - values: ['event.kind:alert and event.module:(endpoint and not endgame)\n'], - originalValue: ['event.kind:alert and event.module:(endpoint and not endgame)\n'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.index', - values: ['logs-endpoint.alerts-*'], - originalValue: ['logs-endpoint.alerts-*'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.version', - values: ['100'], - originalValue: ['100'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.rule_id', - values: ['9a1a2dae-0b5f-4c3d-8305-a268d404c306'], - originalValue: ['9a1a2dae-0b5f-4c3d-8305-a268d404c306'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.license', - values: ['Elastic License v2'], - originalValue: ['Elastic License v2'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.required_fields.ecs', - values: ['true'], - originalValue: ['true'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.required_fields.name', - values: ['event.kind', 'event.module'], - originalValue: ['event.kind', 'event.module'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.required_fields.type', - values: ['keyword'], - originalValue: ['keyword'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.immutable', - values: ['true'], - originalValue: ['true'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.related_integrations', - values: [], - originalValue: [], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.setup', - values: [''], - originalValue: [''], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.false_positives', - values: [], - originalValue: [], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.threat', - values: [], - originalValue: [], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.parameters.to', - values: ['now'], - originalValue: ['now'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.rule.version', - values: ['100'], - originalValue: ['100'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.original_event.kind', - values: ['alert'], - originalValue: ['alert'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.status', - values: ['active'], - originalValue: ['active'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.severity_mapping.field', - values: ['event.severity', 'event.severity', 'event.severity', 'event.severity'], - originalValue: ['event.severity', 'event.severity', 'event.severity', 'event.severity'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.original_event.dataset', - values: ['endpoint'], - originalValue: ['endpoint'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.depth', - values: ['1'], - originalValue: ['1'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.rule.immutable', - values: ['true'], - originalValue: ['true'], - isObjectArray: false, - }, - { - category: 'process', - field: 'process.group_leader.pid', - values: ['116'], - originalValue: ['116'], - isObjectArray: false, - }, - { - category: 'event', - field: 'event.sequence', - values: ['1232'], - originalValue: ['1232'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.rule_type_id', - values: ['siem.queryRule'], - originalValue: ['siem.queryRule'], - isObjectArray: false, - }, - { - category: 'process', - field: 'process.session_leader.name', - values: ['fake session'], - originalValue: ['fake session'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.rule.name', - values: ['Endpoint Security'], - originalValue: ['Endpoint Security'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.rule.rule_id', - values: ['9a1a2dae-0b5f-4c3d-8305-a268d404c306'], - originalValue: ['9a1a2dae-0b5f-4c3d-8305-a268d404c306'], - isObjectArray: false, - }, - { - category: 'event', - field: 'event.module', - values: ['endpoint'], - originalValue: ['endpoint'], - isObjectArray: false, - }, - { - category: 'dll', - field: 'dll.hash.sha1', - values: ['ca85243c0af6a6471bdaa560685c51eefd6dbc0d'], - originalValue: ['ca85243c0af6a6471bdaa560685c51eefd6dbc0d'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.severity_mapping.operator', - values: ['equals', 'equals', 'equals', 'equals'], - originalValue: ['equals', 'equals', 'equals', 'equals'], - isObjectArray: false, - }, - { - category: 'process', - field: 'process.Ext.malware_signature.all_names', - values: ['Windows.Trojan.FakeAgent'], - originalValue: ['Windows.Trojan.FakeAgent'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.license', - values: ['Elastic License v2'], - originalValue: ['Elastic License v2'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.original_event.kind', - values: ['alert'], - originalValue: ['alert'], - isObjectArray: false, - }, - { - category: 'process', - field: 'process.executable', - values: ['C:/fake/explorer.exe'], - originalValue: ['C:/fake/explorer.exe'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.updated_at', - values: ['2022-09-29T19:39:38.137Z'], - originalValue: ['2022-09-29T19:39:38.137Z'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.rule.description', - values: [ - 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', - ], - originalValue: [ - 'Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.', - ], - isObjectArray: false, - }, - { - category: 'dll', - field: 'dll.Ext.mapped_size', - values: ['0'], - originalValue: ['0'], - isObjectArray: false, - }, - { - category: 'data_stream', - field: 'data_stream.namespace', - values: ['default'], - originalValue: ['default'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.author', - values: ['Elastic'], - originalValue: ['Elastic'], - isObjectArray: false, - }, - { - category: 'dll', - field: 'dll.code_signature.subject_name', - values: ['Cybereason Inc'], - originalValue: ['Cybereason Inc'], - isObjectArray: false, - }, - { - category: 'user', - field: 'user.name', - values: ['root'], - originalValue: ['root'], - isObjectArray: false, - }, - { - category: 'source', - field: 'source.ip', - values: ['10.184.3.46'], - originalValue: ['10.184.3.46'], - isObjectArray: false, - }, - { - category: 'Endpoint', - field: 'Endpoint.policy.applied.endpoint_policy_version', - values: ['3'], - originalValue: ['3'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.original_event.sequence', - values: ['1232'], - originalValue: ['1232'], - isObjectArray: false, - }, - { - category: 'dll', - field: 'dll.path', - values: ['C:\\Program Files\\Cybereason ActiveProbe\\AmSvc.exe'], - originalValue: ['C:\\Program Files\\Cybereason ActiveProbe\\AmSvc.exe'], - isObjectArray: false, - }, - { - category: 'process', - field: 'process.Ext.user', - values: ['SYSTEM'], - originalValue: ['SYSTEM'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.original_event.action', - values: ['start'], - originalValue: ['start'], - isObjectArray: false, - }, - { - category: 'signal', - field: 'signal.rule.to', - values: ['now'], - originalValue: ['now'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.created_at', - values: ['2022-09-29T19:39:38.137Z'], - originalValue: ['2022-09-29T19:39:38.137Z'], - isObjectArray: false, - }, - { - category: 'process', - field: 'process.Ext.malware_signature.identifier', - values: ['diagnostic-malware-signature-v1-fake'], - originalValue: ['diagnostic-malware-signature-v1-fake'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.exceptions_list.namespace_type', - values: ['agnostic'], - originalValue: ['agnostic'], - isObjectArray: false, - }, - { - category: 'event', - field: 'event.type', - values: ['info'], - originalValue: ['info'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.space_ids', - values: ['default'], - originalValue: ['default'], - isObjectArray: false, - }, - { - category: 'process', - field: 'process.entry_leader.entity_id', - values: ['b74mw1jkrm'], - originalValue: ['b74mw1jkrm'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.exceptions_list.id', - values: ['endpoint_list'], - originalValue: ['endpoint_list'], - isObjectArray: false, - }, - { - category: 'event', - field: 'event.dataset', - values: ['endpoint'], - originalValue: ['endpoint'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.original_time', - values: ['2022-10-09T07:14:42.194Z'], - originalValue: ['2022-10-09T07:14:42.194Z'], - isObjectArray: false, - }, - { - category: '_index', - field: '_index', - values: ['.internal.alerts-security.alerts-default-000001'], - originalValue: ['.internal.alerts-security.alerts-default-000001'], - isObjectArray: false, - }, - { - category: '_id', - field: '_id', - values: ['f6aa8643ecee466753c45308ea8dc72aba0a44e1faac5f6183fd2ad6666c1325'], - originalValue: ['f6aa8643ecee466753c45308ea8dc72aba0a44e1faac5f6183fd2ad6666c1325'], - isObjectArray: false, - }, - { - category: '_score', - field: '_score', - values: ['1'], - originalValue: ['1'], - isObjectArray: false, - }, -]; - -export const getMockAlertNestedDetailsTimelineResponse = (): Ecs => ({ - _id: 'f6aa8643ecee466753c45308ea8dc72aba0a44e1faac5f6183fd2ad6666c1325', - timestamp: '2022-09-29T19:40:26.051Z', - _index: '.internal.alerts-security.alerts-default-000001', - kibana: { - alert: { - rule: { - from: ['now-10m'], - name: ['Endpoint Security'], - to: ['now'], - uuid: ['738e91f2-402e-11ed-be15-7be3bb26d7b2'], - type: ['query'], - version: ['100'], - parameters: {}, - }, - workflow_status: ['open'], - original_time: ['2022-10-09T07:14:42.194Z'], - severity: ['medium'], - }, - }, - event: { - code: ['memory_signature'], - module: ['endpoint'], - action: ['start'], - category: ['malware'], - dataset: ['endpoint'], - id: ['7799e1d5-5dc1-4173-9d11-562496cd863b'], - kind: ['signal'], - type: ['info'], - }, - host: { - name: ['Host-4cfuh42w7g'], - os: { - family: ['windows'], - name: ['Windows'], - }, - id: ['04794e4e-59cb-4c4a-a8ee-3e6c5b65743c'], - ip: ['10.184.3.36', '10.170.218.86'], - }, - source: { - ip: ['10.184.3.46'], - }, - agent: { - type: ['endpoint'], - id: ['d08ed3f8-9852-4d0c-a5b1-b48060705369'], - }, - process: { - hash: { - md5: ['fake md5'], - sha1: ['fake sha1'], - sha256: ['fake sha256'], - }, - parent: { - pid: [1], - }, - pid: [2], - name: ['explorer.exe'], - entity_id: ['d3v4to81q9'], - executable: ['C:/fake/explorer.exe'], - entry_leader: { - entity_id: ['b74mw1jkrm'], - name: ['fake entry'], - pid: ['865'], - }, - session_leader: { - entity_id: ['b74mw1jkrm'], - name: ['fake session'], - pid: ['745'], - }, - group_leader: { - entity_id: ['b74mw1jkrm'], - name: ['fake leader'], - pid: ['116'], - }, - }, - user: { - name: ['root'], - }, -}); - -export const mockAlertDetailsFieldsResponse = getMockAlertDetailsFieldsResponse(); - -export const mockAlertDetailsTimelineResponse = getMockAlertDetailsTimelineResponse(); - -export const mockAlertNestedDetailsTimelineResponse = getMockAlertNestedDetailsTimelineResponse(); diff --git a/x-pack/plugins/security_solution/public/detections/pages/alert_details/__mocks__/index.ts b/x-pack/plugins/security_solution/public/detections/pages/alert_details/__mocks__/index.ts deleted file mode 100644 index 0771ffa5ccf9f..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/alert_details/__mocks__/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export * from './alert_details_response'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/alert_details/components/error_page.tsx b/x-pack/plugins/security_solution/public/detections/pages/alert_details/components/error_page.tsx deleted file mode 100644 index 35386ecf28dc2..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/alert_details/components/error_page.tsx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { memo } from 'react'; -import { EuiCode, EuiEmptyPrompt } from '@elastic/eui'; -import { ERROR_PAGE_TITLE, ERROR_PAGE_BODY } from '../translations'; - -export const AlertDetailsErrorPage = memo(({ eventId }: { eventId: string }) => { - return ( - {ERROR_PAGE_TITLE}} - body={ -
-

{ERROR_PAGE_BODY}

-

- {`_id: ${eventId}`} -

-
- } - /> - ); -}); - -AlertDetailsErrorPage.displayName = 'AlertDetailsErrorPage'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/alert_details/components/header.tsx b/x-pack/plugins/security_solution/public/detections/pages/alert_details/components/header.tsx deleted file mode 100644 index 67a45a0098266..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/alert_details/components/header.tsx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { PreferenceFormattedDate } from '../../../../common/components/formatted_date'; -import { HeaderPage } from '../../../../common/components/header_page'; -import { ALERT_DETAILS_TECHNICAL_PREVIEW } from '../translations'; - -interface AlertDetailsHeaderProps { - loading: boolean; - ruleName?: string; - timestamp?: string; -} - -export const AlertDetailsHeader = React.memo( - ({ loading, ruleName, timestamp }: AlertDetailsHeaderProps) => { - return ( - : ''} - title={ruleName} - /> - ); - } -); - -AlertDetailsHeader.displayName = 'AlertDetailsHeader'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/alert_details/components/loading_page.tsx b/x-pack/plugins/security_solution/public/detections/pages/alert_details/components/loading_page.tsx deleted file mode 100644 index ee24b2e636874..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/alert_details/components/loading_page.tsx +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { memo } from 'react'; -import { EuiEmptyPrompt, EuiLoadingSpinner } from '@elastic/eui'; -import { LOADING_PAGE_MESSAGE } from '../translations'; - -export const AlertDetailsLoadingPage = memo(({ eventId }: { eventId: string }) => ( - } - body={

{LOADING_PAGE_MESSAGE}

} - /> -)); - -AlertDetailsLoadingPage.displayName = 'AlertDetailsLoadingPage'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/alert_details/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/alert_details/index.test.tsx deleted file mode 100644 index bee3abe3bc156..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/alert_details/index.test.tsx +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { Router, useParams } from 'react-router-dom'; -import { render } from '@testing-library/react'; -import { AlertDetailsPage } from '.'; -import { TestProviders } from '../../../common/mock'; -import { - mockAlertDetailsFieldsResponse, - mockAlertDetailsTimelineResponse, - mockAlertNestedDetailsTimelineResponse, -} from './__mocks__'; -import { ALERT_RULE_NAME } from '@kbn/rule-data-utils'; -import { useTimelineEventsDetails } from '../../../timelines/containers/details'; - -// Node modules mocks -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: jest.fn(), -})); - -const mockDispatch = jest.fn(); -jest.mock('react-redux', () => ({ - ...jest.requireActual('react-redux'), - useDispatch: () => mockDispatch, -})); - -(useParams as jest.Mock).mockReturnValue(mockAlertDetailsFieldsResponse._id); - -// Internal Mocks -jest.mock('../../../timelines/containers/details'); -jest.mock('../../../timelines/store/timeline', () => ({ - ...jest.requireActual('../../../timelines/store/timeline'), - timelineActions: { - createTimeline: jest.fn().mockReturnValue('new-timeline'), - }, -})); - -jest.mock('../../../common/containers/sourcerer', () => { - const mockSourcererReturn = { - browserFields: {}, - loading: true, - indexPattern: {}, - selectedPatterns: [], - missingPatterns: [], - }; - return { - useSourcererDataView: jest.fn().mockReturnValue(mockSourcererReturn), - }; -}); - -type Action = 'PUSH' | 'POP' | 'REPLACE'; -const pop: Action = 'POP'; -const getMockHistory = () => ({ - length: 1, - location: { - pathname: `/alerts/${mockAlertDetailsFieldsResponse._id}/summary`, - search: '', - state: '', - hash: '', - }, - action: pop, - push: jest.fn(), - replace: jest.fn(), - go: jest.fn(), - goBack: jest.fn(), - goForward: jest.fn(), - block: jest.fn(), - createHref: jest.fn(), - listen: jest.fn(), -}); - -describe('Alert Details Page', () => { - it('should render the loading page', () => { - (useTimelineEventsDetails as jest.Mock).mockReturnValue([true, null, null, null, jest.fn()]); - const { getByTestId } = render( - - - - - - ); - - expect(getByTestId('alert-details-page-loading')).toBeVisible(); - }); - - it('should render the error page', () => { - (useTimelineEventsDetails as jest.Mock).mockReturnValue([false, null, null, null, jest.fn()]); - const { getByTestId } = render( - - - - - - ); - - expect(getByTestId('alert-details-page-error')).toBeVisible(); - }); - - it('should render the header', () => { - (useTimelineEventsDetails as jest.Mock).mockReturnValue([ - false, - mockAlertDetailsTimelineResponse, - mockAlertDetailsFieldsResponse, - mockAlertNestedDetailsTimelineResponse, - jest.fn(), - ]); - const { getByTestId } = render( - - - - - - ); - - expect(getByTestId('header-page-title')).toHaveTextContent( - mockAlertDetailsFieldsResponse.fields[ALERT_RULE_NAME][0] - ); - }); - - it('should create a timeline', () => { - (useTimelineEventsDetails as jest.Mock).mockReturnValue([ - false, - mockAlertDetailsTimelineResponse, - mockAlertDetailsFieldsResponse, - mockAlertNestedDetailsTimelineResponse, - jest.fn(), - ]); - render( - - - - - - ); - - expect(mockDispatch).toHaveBeenCalledWith('new-timeline'); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/pages/alert_details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/alert_details/index.tsx deleted file mode 100644 index 8935ff132f246..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/alert_details/index.tsx +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { memo, useEffect, useMemo } from 'react'; -import { useParams } from 'react-router-dom'; -import { Routes, Route } from '@kbn/shared-ux-router'; -import { ALERT_RULE_NAME, TIMESTAMP } from '@kbn/rule-data-utils'; -import { EuiSpacer } from '@elastic/eui'; -import { useDispatch } from 'react-redux'; -import type { RunTimeMappings } from '../../../../common/api/search_strategy'; -import { timelineActions } from '../../../timelines/store/timeline'; -import { TimelineId } from '../../../../common/types/timeline'; -import { useGetFieldsData } from '../../../common/hooks/use_get_fields_data'; -import { useSourcererDataView } from '../../../common/containers/sourcerer'; -import { SourcererScopeName } from '../../../common/store/sourcerer/model'; -import { SpyRoute } from '../../../common/utils/route/spy_routes'; -import { getAlertDetailsTabUrl } from '../../../common/components/link_to'; -import { AlertDetailRouteType } from './types'; -import { TabNavigation } from '../../../common/components/navigation/tab_navigation'; -import { getAlertDetailsNavTabs } from './utils/navigation'; -import { SecurityPageName } from '../../../../common/constants'; -import { eventID } from '../../../../common/endpoint/models/event'; -import { useTimelineEventsDetails } from '../../../timelines/containers/details'; -import { AlertDetailsLoadingPage } from './components/loading_page'; -import { AlertDetailsErrorPage } from './components/error_page'; -import { AlertDetailsHeader } from './components/header'; -import { DetailsSummaryTab } from './tabs/summary'; - -// eslint-disable-next-line react/display-name -export const AlertDetailsPage = memo(() => { - const { detailName: eventId } = useParams<{ detailName: string }>(); - const dispatch = useDispatch(); - const sourcererDataView = useSourcererDataView(SourcererScopeName.detections); - const indexName = useMemo( - () => sourcererDataView.selectedPatterns.join(','), - [sourcererDataView.selectedPatterns] - ); - - const [loading, detailsData, searchHit, dataAsNestedObject] = useTimelineEventsDetails({ - indexName, - eventId, - runtimeMappings: sourcererDataView.runtimeMappings as RunTimeMappings, - skip: !eventID, - }); - const dataNotFound = !loading && !detailsData; - const hasData = !loading && detailsData; - - // Example of using useGetFieldsData. Only place it is used currently - const getFieldsData = useGetFieldsData(searchHit?.fields); - const timestamp = getFieldsData(TIMESTAMP) as string | undefined; - const ruleName = getFieldsData(ALERT_RULE_NAME) as string | undefined; - - useEffect(() => { - // TODO: move detail panel to it's own redux state - dispatch( - timelineActions.createTimeline({ - id: TimelineId.detectionsAlertDetailsPage, - columns: [], - dataViewId: null, - indexNames: [], - expandedDetail: {}, - show: false, - }) - ); - }, [dispatch]); - - return ( - <> - {loading && } - {dataNotFound && } - {hasData && ( - <> - - - - - - - - - - )} - - - ); -}); diff --git a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/alert_renderer_panel/alert_render_panel.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/alert_renderer_panel/alert_render_panel.test.tsx deleted file mode 100644 index c3952801e5ca4..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/alert_renderer_panel/alert_render_panel.test.tsx +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { get } from 'lodash/fp'; -import { render } from '@testing-library/react'; -import { AlertRendererPanel } from '.'; -import { TestProviders } from '../../../../../../common/mock'; -import { mockAlertNestedDetailsTimelineResponse } from '../../../__mocks__'; -import { ALERT_RENDERER_FIELDS } from '../../../../../../timelines/components/timeline/body/renderers/alert_renderer'; - -describe('AlertDetailsPage - SummaryTab - AlertRendererPanel', () => { - it('should render the reason renderer', () => { - const { getByTestId } = render( - - - - ); - - expect(getByTestId('alert-renderer-panel')).toBeVisible(); - }); - - it('should render the render the expected values', () => { - const { getByTestId } = render( - - - - ); - const alertRendererPanelPanel = getByTestId('alert-renderer-panel'); - - ALERT_RENDERER_FIELDS.forEach((rendererField) => { - const fieldValues: string[] | null = get( - rendererField, - mockAlertNestedDetailsTimelineResponse - ); - if (fieldValues && fieldValues.length > 0) { - fieldValues.forEach((value) => { - expect(alertRendererPanelPanel).toHaveTextContent(value); - }); - } - }); - }); - - it('should not render the reason renderer if data is not provided', () => { - const { queryByTestId } = render( - - - - ); - - expect(queryByTestId('alert-renderer-panel')).toBeNull(); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/alert_renderer_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/alert_renderer_panel/index.tsx deleted file mode 100644 index 0dec93eb40b24..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/alert_renderer_panel/index.tsx +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useMemo } from 'react'; -import styled from 'styled-components'; -import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; -import { defaultRowRenderers } from '../../../../../../timelines/components/timeline/body/renderers'; -import { getRowRenderer } from '../../../../../../timelines/components/timeline/body/renderers/get_row_renderer'; -import { TimelineId } from '../../../../../../../common/types/timeline'; -import { SummaryPanel } from '../wrappers'; -import { ALERT_REASON_PANEL_TITLE } from '../translation'; - -export interface AlertRendererPanelProps { - dataAsNestedObject: Ecs | null; -} - -const RendererContainer = styled.div` - overflow-x: auto; - margin-left: -24px; - - & .euiFlexGroup { - justify-content: flex-start; - } -`; - -export const AlertRendererPanel = React.memo(({ dataAsNestedObject }: AlertRendererPanelProps) => { - const renderer = useMemo( - () => - dataAsNestedObject != null - ? getRowRenderer({ data: dataAsNestedObject, rowRenderers: defaultRowRenderers }) - : null, - [dataAsNestedObject] - ); - - return ( - - {renderer != null && dataAsNestedObject != null && ( -
- - {renderer.renderRow({ - data: dataAsNestedObject, - isDraggable: false, - scopeId: TimelineId.detectionsAlertDetailsPage, - })} - -
- )} -
- ); -}); - -AlertRendererPanel.displayName = 'AlertRendererPanel'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/cases_panel/cases_panel.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/cases_panel/cases_panel.test.tsx deleted file mode 100644 index 32905019eb3f8..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/cases_panel/cases_panel.test.tsx +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { render } from '@testing-library/react'; -import type { RelatedCase } from '@kbn/cases-plugin/common'; -import { CasesPanel, CASES_PANEL_CASES_COUNT_MAX } from '.'; -import { TestProviders } from '../../../../../../common/mock'; -import { - mockAlertDetailsTimelineResponse, - mockAlertNestedDetailsTimelineResponse, -} from '../../../__mocks__'; -import { ERROR_LOADING_CASES, LOADING_CASES } from '../translation'; -import { useGetRelatedCasesByEvent } from '../../../../../../common/containers/cases/use_get_related_cases_by_event'; -import { useGetUserCasesPermissions } from '../../../../../../common/lib/kibana'; -import { CaseStatuses } from '@kbn/cases-components'; - -jest.mock('../../../../../../common/containers/cases/use_get_related_cases_by_event'); -jest.mock('../../../../../../common/lib/kibana'); - -const defaultPanelProps = { - eventId: mockAlertNestedDetailsTimelineResponse._id, - dataAsNestedObject: mockAlertNestedDetailsTimelineResponse, - detailsData: mockAlertDetailsTimelineResponse, -}; - -describe('AlertDetailsPage - SummaryTab - CasesPanel', () => { - describe('No data', () => { - beforeEach(() => { - (useGetUserCasesPermissions as jest.Mock).mockReturnValue({ - create: true, - update: true, - }); - }); - it('should render the loading panel', () => { - (useGetRelatedCasesByEvent as jest.Mock).mockReturnValue({ - loading: true, - }); - const { getByText } = render( - - - - ); - expect(getByText(LOADING_CASES)).toBeVisible(); - }); - - it('should render the error panel if an error is returned', () => { - (useGetRelatedCasesByEvent as jest.Mock).mockReturnValue({ - loading: false, - error: true, - }); - const { getByText } = render( - - - - ); - - expect(getByText(ERROR_LOADING_CASES)).toBeVisible(); - }); - - it('should render the error panel if data is undefined', () => { - (useGetRelatedCasesByEvent as jest.Mock).mockReturnValue({ - loading: false, - error: false, - relatedCases: undefined, - }); - const { getByText } = render( - - - - ); - - expect(getByText(ERROR_LOADING_CASES)).toBeVisible(); - }); - - describe('Partial permissions', () => { - it('should only render the add to new case button', () => { - (useGetRelatedCasesByEvent as jest.Mock).mockReturnValue({ - loading: false, - relatedCases: [], - }); - (useGetUserCasesPermissions as jest.Mock).mockReturnValue({ - create: true, - update: false, - }); - const { getByTestId, queryByTestId } = render( - - - - ); - - expect(getByTestId('add-to-new-case-button')).toBeVisible(); - expect(queryByTestId('add-to-existing-case-button')).toBe(null); - }); - - it('should only render the add to existing case button', () => { - (useGetRelatedCasesByEvent as jest.Mock).mockReturnValue({ - loading: false, - relatedCases: [], - }); - (useGetUserCasesPermissions as jest.Mock).mockReturnValue({ - create: false, - update: true, - }); - const { getByTestId, queryByTestId } = render( - - - - ); - - expect(getByTestId('add-to-existing-case-button')).toBeVisible(); - expect(queryByTestId('add-to-new-case-button')).toBe(null); - }); - - it('should render both add to new case and add to existing case buttons', () => { - (useGetRelatedCasesByEvent as jest.Mock).mockReturnValue({ - loading: false, - relatedCases: [], - }); - (useGetUserCasesPermissions as jest.Mock).mockReturnValue({ - create: true, - update: true, - }); - const { getByTestId, queryByTestId } = render( - - - - ); - - expect(getByTestId('add-to-new-case-button')).toBeVisible(); - expect(queryByTestId('add-to-existing-case-button')).toBeVisible(); - }); - }); - }); - describe('has a single related cases', () => { - const mockRelatedCase: RelatedCase = { - createdAt: '2022-11-04T17:22:13.267Z', - title: 'test case', - description: 'Test case description', - status: CaseStatuses.open, - id: 'test-case-id', - totals: { - alerts: 2, - userComments: 4, - }, - }; - - beforeEach(() => { - (useGetUserCasesPermissions as jest.Mock).mockReturnValue({ - create: true, - update: true, - }); - (useGetRelatedCasesByEvent as jest.Mock).mockReturnValue({ - loading: false, - relatedCases: [mockRelatedCase], - }); - }); - - it('should show the related case', () => { - const { getByTestId } = render( - - - - ); - - expect(getByTestId('case-panel')).toHaveTextContent(mockRelatedCase.title); - expect(getByTestId('case-panel')).toHaveTextContent(mockRelatedCase.description); - expect(getByTestId('case-panel')).toHaveTextContent(`${mockRelatedCase.totals.alerts}`); - expect(getByTestId('case-panel')).toHaveTextContent(`${mockRelatedCase.totals.userComments}`); - }); - }); - describe(`has more than ${CASES_PANEL_CASES_COUNT_MAX} related cases`, () => { - const mockRelatedCase: RelatedCase = { - createdAt: '2022-11-04T17:22:13.267Z', - title: 'test case', - description: 'Test case description', - status: CaseStatuses.open, - id: 'test-case-id', - totals: { - alerts: 2, - userComments: 4, - }, - }; - - const mockRelatedCaseList = Array.from(Array(CASES_PANEL_CASES_COUNT_MAX + 3).keys()).map( - (position) => ({ - ...mockRelatedCase, - title: `test case ${position + 1}`, - id: `test-case-id-${position + 1}`, - }) - ); - - beforeEach(() => { - (useGetUserCasesPermissions as jest.Mock).mockReturnValue({ - create: true, - update: true, - }); - (useGetRelatedCasesByEvent as jest.Mock).mockReturnValue({ - loading: false, - relatedCases: mockRelatedCaseList, - }); - }); - - it('should show the related case', () => { - const { getByTestId } = render( - - - - ); - - expect(getByTestId('case-panel')).toHaveTextContent( - `test case ${CASES_PANEL_CASES_COUNT_MAX}` - ); - expect(getByTestId('case-panel')).not.toHaveTextContent( - `test case ${CASES_PANEL_CASES_COUNT_MAX + 1}` - ); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/cases_panel/cases_panel_actions.tsx b/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/cases_panel/cases_panel_actions.tsx deleted file mode 100644 index 4ffc16603cb0b..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/cases_panel/cases_panel_actions.tsx +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { EuiButtonIcon, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; -import type { CasesPermissions } from '@kbn/cases-plugin/common'; -import React, { useCallback, useMemo, useState } from 'react'; -import type { CasesPanelProps } from '.'; -import { - ADD_TO_EXISTING_CASE_BUTTON, - ADD_TO_NEW_CASE_BUTTON, - SUMMARY_PANEL_ACTIONS, -} from '../translation'; - -export const CASES_PANEL_ACTIONS_CLASS = 'cases-panel-actions-trigger'; - -export interface CasesPanelActionsProps extends CasesPanelProps { - addToNewCase: () => void; - addToExistingCase: () => void; - className?: string; - userCasesPermissions: CasesPermissions; -} - -export const CasesPanelActions = React.memo( - ({ - addToNewCase, - addToExistingCase, - className, - userCasesPermissions, - }: CasesPanelActionsProps) => { - const [isPopoverOpen, setPopover] = useState(false); - - const onButtonClick = useCallback(() => { - setPopover(!isPopoverOpen); - }, [isPopoverOpen]); - - const closePopover = () => { - setPopover(false); - }; - - const items = useMemo(() => { - const options = []; - - if (userCasesPermissions.create) { - options.push( - - {ADD_TO_NEW_CASE_BUTTON} - - ); - } - - if (userCasesPermissions.update) { - options.push( - - {ADD_TO_EXISTING_CASE_BUTTON} - - ); - } - return options; - }, [addToExistingCase, addToNewCase, userCasesPermissions.create, userCasesPermissions.update]); - - const button = useMemo( - () => ( - - ), - [onButtonClick] - ); - - return ( -
- - - -
- ); - } -); - -CasesPanelActions.displayName = 'CasesPanelActions'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/cases_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/cases_panel/index.tsx deleted file mode 100644 index 72b469780097b..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/cases_panel/index.tsx +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useCallback, useMemo } from 'react'; -import { - EuiButton, - EuiEmptyPrompt, - EuiFlexGroup, - EuiFlexItem, - EuiLoadingSpinner, -} from '@elastic/eui'; -import type { Ecs } from '@kbn/cases-plugin/common'; -import { AttachmentType } from '@kbn/cases-plugin/common'; -import type { CaseAttachmentsWithoutOwner } from '@kbn/cases-plugin/public'; -import styled from 'styled-components'; -import type { TimelineEventsDetailsItem } from '../../../../../../../common/search_strategy'; -import { useGetUserCasesPermissions, useKibana } from '../../../../../../common/lib/kibana'; -import { useGetRelatedCasesByEvent } from '../../../../../../common/containers/cases/use_get_related_cases_by_event'; -import { - ADD_TO_EXISTING_CASE_BUTTON, - ADD_TO_NEW_CASE_BUTTON, - CASES_PANEL_SUBTITLE, - CASES_PANEL_TITLE, - CASE_NO_READ_PERMISSIONS, - ERROR_LOADING_CASES, - LOADING_CASES, - NO_RELATED_CASES_FOUND, -} from '../translation'; -import { SummaryPanel } from '../wrappers'; -import { CasesPanelActions, CASES_PANEL_ACTIONS_CLASS } from './cases_panel_actions'; -import { RelatedCasesList } from './related_case'; - -export interface CasesPanelProps { - eventId: string; - dataAsNestedObject: Ecs | null; - detailsData: TimelineEventsDetailsItem[]; -} - -const StyledCasesFlexGroup = styled(EuiFlexGroup)` - max-height: 300px; - overflow-y: auto; -`; - -/** - * There is currently no api limit for the number of cases that can be returned - * To prevent the UI from growing too large, we limit to 25 most recent cases - */ -export const CASES_PANEL_CASES_COUNT_MAX = 25; - -const CasesPanelLoading = () => ( - } - title={

{LOADING_CASES}

} - titleSize="xxs" - /> -); - -const CasesPanelError = () => <>{ERROR_LOADING_CASES}; - -export const CasesPanelNoReadPermissions = () => ; - -export const CasesPanel = React.memo( - ({ eventId, dataAsNestedObject, detailsData }) => { - const { cases: casesUi } = useKibana().services; - const { loading, error, relatedCases, refetchRelatedCases } = - useGetRelatedCasesByEvent(eventId); - const userCasesPermissions = useGetUserCasesPermissions(); - - const caseAttachments: CaseAttachmentsWithoutOwner = useMemo(() => { - return dataAsNestedObject - ? [ - { - alertId: eventId, - index: dataAsNestedObject._index ?? '', - type: AttachmentType.alert, - rule: casesUi.helpers.getRuleIdFromEvent({ - ecs: dataAsNestedObject, - data: detailsData, - }), - }, - ] - : []; - }, [casesUi.helpers, dataAsNestedObject, detailsData, eventId]); - - const createCaseFlyout = casesUi.hooks.useCasesAddToNewCaseFlyout({ - onSuccess: refetchRelatedCases, - }); - - const selectCaseModal = casesUi.hooks.useCasesAddToExistingCaseModal({ - onSuccess: refetchRelatedCases, - }); - - const addToNewCase = useCallback(() => { - if (userCasesPermissions.create) { - createCaseFlyout.open({ attachments: caseAttachments }); - } - }, [userCasesPermissions.create, createCaseFlyout, caseAttachments]); - - const addToExistingCase = useCallback(() => { - if (userCasesPermissions.update) { - selectCaseModal.open({ getAttachments: () => caseAttachments }); - } - }, [caseAttachments, selectCaseModal, userCasesPermissions.update]); - - const renderCasesActions = useCallback( - () => ( - - ), - [ - addToExistingCase, - addToNewCase, - dataAsNestedObject, - detailsData, - eventId, - userCasesPermissions, - ] - ); - - // Sort by most recently created being first - const relatedCasesCount = relatedCases ? relatedCases.length : 0; - const visibleCaseCount = useMemo( - () => Math.min(relatedCasesCount, CASES_PANEL_CASES_COUNT_MAX), - [relatedCasesCount] - ); - const hasRelatedCases = relatedCasesCount > 0; - - if (loading) return ; - - if (error || relatedCases === undefined) return ; - - return ( - - {hasRelatedCases ? ( - - - - ) : ( - - {userCasesPermissions.update && ( - - - {ADD_TO_EXISTING_CASE_BUTTON} - - - )} - {userCasesPermissions.create && ( - - - {ADD_TO_NEW_CASE_BUTTON} - - - )} - - } - /> - )} - - ); - } -); - -CasesPanel.displayName = 'CasesPanel'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/cases_panel/related_case.tsx b/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/cases_panel/related_case.tsx deleted file mode 100644 index ef55a3d158a83..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/cases_panel/related_case.tsx +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiIcon, EuiText } from '@elastic/eui'; -import { Status } from '@kbn/cases-components'; -import type { RelatedCase } from '@kbn/cases-plugin/common'; -import React, { useMemo } from 'react'; -import styled from 'styled-components'; -import { CaseDetailsLink } from '../../../../../../common/components/links'; -import { CASES_PANEL_CASE_STATUS } from '../translation'; - -const DescriptionText = styled(EuiText)` - text-overflow: ellipsis; - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; - word-break: normal; -`; - -const ChildFlexGroup = styled(EuiFlexGroup)` - margin: 0; -`; - -const StyledStatusText = styled.span` - margin-right: ${({ theme }) => theme.eui.euiSizeS}; -`; - -const StyledIcon = styled(EuiIcon)` - margin-right: ${({ theme }) => theme.eui.euiSizeS}; -`; - -export const RelatedCasesList = ({ - relatedCases, - maximumVisible, -}: { - relatedCases: RelatedCase[]; - maximumVisible?: number; -}) => { - // Sort related cases, showing the most recently created first. - const sortedRelatedCases = useMemo( - () => - relatedCases - ? relatedCases.sort( - (case1, case2) => - new Date(case2.createdAt).getTime() - new Date(case1.createdAt).getTime() - ) - : [], - [relatedCases] - ); - - // If a maximum visible count is provided, only show cases up to that amount - const visibleCases = useMemo( - () => - maximumVisible && maximumVisible > 0 - ? sortedRelatedCases.slice(0, maximumVisible) - : sortedRelatedCases, - [maximumVisible, sortedRelatedCases] - ); - - return ( - <> - {visibleCases?.map(({ id, title, description, status, totals }) => ( - - - {title} - - - - {description} - - - - - - {`${CASES_PANEL_CASE_STATUS}:`} - - - - - - - - - {totals.userComments} - - - - - - {totals.alerts} - - - - - - - ))} - - ); -}; diff --git a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/host_panel/host_panel.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/host_panel/host_panel.test.tsx deleted file mode 100644 index 304e03a27de9f..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/host_panel/host_panel.test.tsx +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { render } from '@testing-library/react'; -import { find } from 'lodash/fp'; -import { TestProviders } from '../../../../../../common/mock'; -import { - mockAlertDetailsTimelineResponse, - mockAlertNestedDetailsTimelineResponse, -} from '../../../__mocks__'; -import type { HostPanelProps } from '.'; -import { HostPanel } from '.'; -import { mockBrowserFields } from '../../../../../../common/containers/source/mock'; -import { getTimelineEventData } from '../../../utils/get_timeline_event_data'; -import { RiskSeverity } from '../../../../../../../common/search_strategy'; -import { useRiskScore } from '../../../../../../explore/containers/risk_score'; - -jest.mock('../../../../../../management/hooks', () => { - const Generator = jest.requireActual( - '../../../../../../../common/endpoint/data_generators/endpoint_metadata_generator' - ); - - return { - useGetEndpointDetails: jest.fn(() => { - return { - data: new Generator.EndpointMetadataGenerator('seed').generateHostInfo({ - metadata: { - Endpoint: { - state: { - isolation: true, - }, - }, - }, - }), - }; - }), - }; -}); - -jest.mock('../../../../../../explore/containers/risk_score'); -const mockUseRiskScore = useRiskScore as jest.Mock; - -jest.mock('../../../../../containers/detection_engine/alerts/use_host_isolation_status', () => { - return { - useHostIsolationStatus: jest.fn().mockReturnValue({ - loading: false, - isIsolated: false, - agentStatus: 'healthy', - }), - }; -}); - -describe('AlertDetailsPage - SummaryTab - HostPanel', () => { - const defaultRiskReturnValues = { - inspect: null, - refetch: () => {}, - isModuleEnabled: true, - isAuthorized: true, - loading: false, - }; - const HostPanelWithDefaultProps = (propOverrides: Partial) => ( - - - - ); - - beforeEach(() => { - mockUseRiskScore.mockReturnValue({ ...defaultRiskReturnValues }); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should render basic host fields', () => { - const { getByTestId } = render(); - const simpleHostFields = ['host.name', 'host.os.name']; - - simpleHostFields.forEach((simpleHostField) => { - expect(getByTestId('host-panel')).toHaveTextContent( - getTimelineEventData(simpleHostField, mockAlertDetailsTimelineResponse) - ); - }); - }); - - describe('Agent status', () => { - it('should show healthy', () => { - const { getByTestId } = render(); - expect(getByTestId('endpointHostAgentStatus').textContent).toEqual('HealthyIsolated'); - }); - }); - - describe('host risk', () => { - it('should not show risk if the license is not valid', () => { - mockUseRiskScore.mockReturnValue({ - ...defaultRiskReturnValues, - isAuthorized: false, - data: null, - }); - const { queryByTestId } = render(); - expect(queryByTestId('host-panel-risk')).toBe(null); - }); - - it('should render risk fields', () => { - const calculatedScoreNorm = 98.9; - const calculatedLevel = RiskSeverity.critical; - - mockUseRiskScore.mockReturnValue({ - ...defaultRiskReturnValues, - isAuthorized: true, - data: [ - { - host: { - name: mockAlertNestedDetailsTimelineResponse.host?.name, - risk: { - calculated_score_norm: calculatedScoreNorm, - calculated_level: calculatedLevel, - }, - }, - }, - ], - }); - const { getByTestId } = render(); - - expect(getByTestId('host-panel-risk')).toHaveTextContent( - `${Math.round(calculatedScoreNorm)}` - ); - expect(getByTestId('host-panel-risk')).toHaveTextContent(calculatedLevel); - }); - }); - - describe('host ip', () => { - it('should render all the ip fields', () => { - const { getByTestId } = render(); - const ipFields = find( - { field: 'host.ip', category: 'host' }, - mockAlertDetailsTimelineResponse - )?.values as string[]; - expect(getByTestId('host-panel-ip')).toHaveTextContent(ipFields[0]); - expect(getByTestId('host-panel-ip')).toHaveTextContent('+1 More'); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/host_panel/host_panel_actions.tsx b/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/host_panel/host_panel_actions.tsx deleted file mode 100644 index d078785bf93f8..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/host_panel/host_panel_actions.tsx +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { EuiButtonIcon, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; -import React, { useCallback, useMemo, useState } from 'react'; -import { SecurityPageName } from '../../../../../../app/types'; -import { useGetSecuritySolutionLinkProps } from '../../../../../../common/components/links'; -import { getHostDetailsUrl } from '../../../../../../common/components/link_to'; - -import { OPEN_HOST_DETAILS_PAGE, SUMMARY_PANEL_ACTIONS, VIEW_HOST_SUMMARY } from '../translation'; - -export const HOST_PANEL_ACTIONS_CLASS = 'host-panel-actions-trigger'; - -export const HostPanelActions = React.memo( - ({ - className, - openHostDetailsPanel, - hostName, - }: { - className?: string; - hostName: string; - openHostDetailsPanel: (hostName: string) => void; - }) => { - const [isPopoverOpen, setPopover] = useState(false); - const { href } = useGetSecuritySolutionLinkProps()({ - deepLinkId: SecurityPageName.hosts, - path: getHostDetailsUrl(hostName), - }); - - const onButtonClick = useCallback(() => { - setPopover(!isPopoverOpen); - }, [isPopoverOpen]); - - const closePopover = () => { - setPopover(false); - }; - - const handleOpenHostDetailsPanel = useCallback(() => { - openHostDetailsPanel(hostName); - closePopover(); - }, [hostName, openHostDetailsPanel]); - - const items = useMemo( - () => [ - - {VIEW_HOST_SUMMARY} - , - - {OPEN_HOST_DETAILS_PAGE} - , - ], - [handleOpenHostDetailsPanel, href] - ); - - const button = useMemo( - () => ( - - ), - [onButtonClick] - ); - - return ( -
- - - -
- ); - } -); - -HostPanelActions.displayName = 'HostPanelActions'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/host_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/host_panel/index.tsx deleted file mode 100644 index 2688dd5cabf3c..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/host_panel/index.tsx +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EuiTitle, EuiSpacer, EuiFlexGroup, EuiFlexItem, EuiIcon } from '@elastic/eui'; -import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; -import React, { useCallback, useMemo } from 'react'; -import { find } from 'lodash/fp'; -import type { EuiFlexItemProps } from '@elastic/eui'; -import { TimelineId } from '../../../../../../../common/types/timeline'; -import { isAlertFromEndpointEvent } from '../../../../../../common/utils/endpoint_alert_check'; -import { SummaryValueCell } from '../../../../../../common/components/event_details/table/summary_value_cell'; -import { useRiskScore } from '../../../../../../explore/containers/risk_score'; -import { RiskScoreEntity } from '../../../../../../../common/search_strategy'; -import { getEmptyTagValue } from '../../../../../../common/components/empty_value'; -import { RiskScoreLevel } from '../../../../../../explore/components/risk_score/severity/common'; -import { - FirstLastSeen, - FirstLastSeenType, -} from '../../../../../../common/components/first_last_seen'; -import { DefaultFieldRenderer } from '../../../../../../timelines/components/field_renderers/field_renderers'; -import { HostDetailsLink, NetworkDetailsLink } from '../../../../../../common/components/links'; -import type { SelectedDataView } from '../../../../../../common/store/sourcerer/model'; -import { SourcererScopeName } from '../../../../../../common/store/sourcerer/model'; -import { getEnrichedFieldInfo } from '../../../../../../common/components/event_details/helpers'; -import { getTimelineEventData } from '../../../utils/get_timeline_event_data'; -import { - AGENT_STATUS_TITLE, - HOST_NAME_TITLE, - HOST_PANEL_TITLE, - HOST_RISK_LEVEL, - HOST_RISK_SCORE, - IP_ADDRESSES_TITLE, - LAST_SEEN_TITLE, - OPERATING_SYSTEM_TITLE, -} from '../translation'; -import { SummaryPanel } from '../wrappers'; -import { HostPanelActions, HOST_PANEL_ACTIONS_CLASS } from './host_panel_actions'; - -export interface HostPanelProps { - data: TimelineEventsDetailsItem[]; - id: string; - openHostDetailsPanel: (hostName: string, onClose?: (() => void) | undefined) => void; - selectedPatterns: SelectedDataView['selectedPatterns']; - browserFields: SelectedDataView['browserFields']; -} - -const HostPanelSection: React.FC<{ - title?: string | React.ReactElement; - grow?: EuiFlexItemProps['grow']; -}> = ({ grow, title, children }) => - children ? ( - - {title && ( - <> - -
{title}
-
- - - )} - {children} -
- ) : null; - -export const HostPanel = React.memo( - ({ data, id, browserFields, openHostDetailsPanel, selectedPatterns }: HostPanelProps) => { - const hostName = getTimelineEventData('host.name', data); - const hostOs = getTimelineEventData('host.os.name', data); - - const enrichedAgentStatus = useMemo(() => { - const item = find({ field: 'agent.id', category: 'agent' }, data); - if (!data || !isAlertFromEndpointEvent({ data })) return null; - return ( - item && - getEnrichedFieldInfo({ - eventId: id, - contextId: TimelineId.detectionsAlertDetailsPage, - scopeId: TimelineId.detectionsAlertDetailsPage, - browserFields, - item, - field: { id: 'agent.id', overrideField: 'agent.status' }, - linkValueField: undefined, - }) - ); - }, [browserFields, data, id]); - - const { data: hostRisk, isAuthorized: isRiskScoreAuthorized } = useRiskScore({ - riskEntity: RiskScoreEntity.host, - skip: hostName == null, - }); - - const [hostRiskScore, hostRiskLevel] = useMemo(() => { - const hostRiskData = hostRisk && hostRisk.length > 0 ? hostRisk[0] : undefined; - const hostRiskValue = hostRiskData - ? Math.round(hostRiskData.host.risk.calculated_score_norm) - : getEmptyTagValue(); - const hostRiskSeverity = hostRiskData ? ( - - ) : ( - getEmptyTagValue() - ); - - return [hostRiskValue, hostRiskSeverity]; - }, [hostRisk]); - - const hostIpFields = useMemo( - () => find({ field: 'host.ip', category: 'host' }, data)?.values ?? [], - [data] - ); - - const renderHostIp = useCallback( - (ip: string) => (ip != null ? : getEmptyTagValue()), - [] - ); - - const renderHostActions = useCallback( - () => , - [hostName, openHostDetailsPanel] - ); - - return ( - - - - - - - - - - - - - - {hostOs} - {enrichedAgentStatus && ( - - - - )} - - - {isRiskScoreAuthorized && ( - <> - - {hostRiskScore && ( - {hostRiskScore} - )} - {hostRiskLevel && ( - {hostRiskLevel} - )} - - - - )} - - - - - - - - - - - - - - - - ); - } -); - -HostPanel.displayName = 'HostPanel'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/index.tsx deleted file mode 100644 index 2f6d146c8cc9f..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/index.tsx +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { EuiFlexGroup } from '@elastic/eui'; -import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; -import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; -import type { SearchHit } from '../../../../../../common/search_strategy'; -import { TimelineId } from '../../../../../../common/types/timeline'; -import { useDetailPanel } from '../../../../../timelines/components/side_panel/hooks/use_detail_panel'; -import { useGetUserCasesPermissions } from '../../../../../common/lib/kibana'; -import type { SelectedDataView } from '../../../../../common/store/sourcerer/model'; -import { SourcererScopeName } from '../../../../../common/store/sourcerer/model'; -import { AlertRendererPanel } from './alert_renderer_panel'; -import { RulePanel } from './rule_panel'; -import { CasesPanel, CasesPanelNoReadPermissions } from './cases_panel'; -import { HostPanel } from './host_panel'; -import { UserPanel } from './user_panel'; -import { SummaryColumn, SummaryRow } from './wrappers'; - -export interface DetailsSummaryTabProps { - eventId: string; - dataAsNestedObject: Ecs | null; - detailsData: TimelineEventsDetailsItem[]; - searchHit?: SearchHit; - sourcererDataView: SelectedDataView; -} - -export const DetailsSummaryTab = React.memo( - ({ - dataAsNestedObject, - detailsData, - searchHit, - eventId, - sourcererDataView, - }: DetailsSummaryTabProps) => { - const userCasesPermissions = useGetUserCasesPermissions(); - - const { DetailsPanel, openHostDetailsPanel, openUserDetailsPanel } = useDetailPanel({ - isFlyoutView: true, - sourcererScope: SourcererScopeName.detections, - scopeId: TimelineId.detectionsAlertDetailsPage, - }); - - return ( - <> - - - - - - - - - - - {userCasesPermissions.read ? ( - - ) : ( - - )} - - - {DetailsPanel} - - ); - } -); - -DetailsSummaryTab.displayName = 'DetailsSummaryTab'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/rule_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/rule_panel/index.tsx deleted file mode 100644 index 4b67dcb87356c..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/rule_panel/index.tsx +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EuiTitle, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import type { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; -import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; -import React, { useCallback, useMemo } from 'react'; -import { css } from '@emotion/react'; -import { find } from 'lodash/fp'; -import type { EuiFlexItemProps } from '@elastic/eui/src/components/flex/flex_item'; -import { - ALERT_RISK_SCORE, - ALERT_RULE_DESCRIPTION, - ALERT_RULE_NAME, - ALERT_RULE_UUID, - ALERT_SEVERITY, - KIBANA_NAMESPACE, -} from '@kbn/rule-data-utils'; -import type { SearchHit } from '../../../../../../../common/search_strategy'; -import { TimelineId } from '../../../../../../../common/types/timeline'; -import { SeverityBadge } from '../../../../../components/rules/severity_badge'; -import { getEnrichedFieldInfo } from '../../../../../../common/components/event_details/helpers'; -import type { SelectedDataView } from '../../../../../../common/store/sourcerer/model'; -import { FormattedFieldValue } from '../../../../../../timelines/components/timeline/body/renderers/formatted_field'; -import { - RISK_SCORE_TITLE, - RULE_DESCRIPTION_TITLE, - RULE_NAME_TITLE, - RULE_PANEL_TITLE, - SEVERITY_TITLE, -} from '../translation'; -import { getMitreComponentParts } from '../../../../../mitre/get_mitre_threat_component'; -import { getTimelineEventData } from '../../../utils/get_timeline_event_data'; -import { SummaryPanel } from '../wrappers'; -import { RulePanelActions, RULE_PANEL_ACTIONS_CLASS } from './rule_panel_actions'; - -export interface RulePanelProps { - data: TimelineEventsDetailsItem[]; - id: string; - browserFields: SelectedDataView['browserFields']; - searchHit?: SearchHit; -} - -const threatTacticContainerStyles = css` - flex-wrap: nowrap; - & .euiFlexGroup { - flex-wrap: nowrap; - } -`; - -interface RuleSectionProps { - ['data-test-subj']?: string; - title: string; - grow?: EuiFlexItemProps['grow']; -} -const RuleSection: React.FC = ({ - grow, - title, - children, - 'data-test-subj': dataTestSubj, -}) => ( - - -
{title}
-
- - {children} -
-); - -export const RulePanel = React.memo(({ data, id, searchHit, browserFields }: RulePanelProps) => { - const ruleUuid = useMemo(() => getTimelineEventData(ALERT_RULE_UUID, data), [data]); - const threatDetails = useMemo(() => getMitreComponentParts(searchHit), [searchHit]); - const alertRiskScore = useMemo(() => getTimelineEventData(ALERT_RISK_SCORE, data), [data]); - const alertSeverity = useMemo( - () => getTimelineEventData(ALERT_SEVERITY, data) as Severity, - [data] - ); - const alertRuleDescription = useMemo( - () => getTimelineEventData(ALERT_RULE_DESCRIPTION, data), - [data] - ); - const shouldShowThreatDetails = !!threatDetails && threatDetails?.length > 0; - - const renderRuleActions = useCallback(() => , [ruleUuid]); - const ruleNameData = useMemo(() => { - const item = find({ field: ALERT_RULE_NAME, category: KIBANA_NAMESPACE }, data); - const linkValueField = find({ field: ALERT_RULE_UUID, category: KIBANA_NAMESPACE }, data); - return ( - item && - getEnrichedFieldInfo({ - eventId: id, - contextId: TimelineId.detectionsAlertDetailsPage, - scopeId: TimelineId.detectionsAlertDetailsPage, - browserFields, - item, - linkValueField, - }) - ); - }, [browserFields, data, id]); - - return ( - - - - - - - - {alertRiskScore} - - - - - - - - {alertRuleDescription} - - - - - {shouldShowThreatDetails && ( - - {threatDetails[0].description} - - )} - - - - - - ); -}); - -RulePanel.displayName = 'RulePanel'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/rule_panel/rule_panel.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/rule_panel/rule_panel.test.tsx deleted file mode 100644 index a41659fd2bcf6..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/rule_panel/rule_panel.test.tsx +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { render } from '@testing-library/react'; -import { ALERT_RISK_SCORE, ALERT_RULE_DESCRIPTION, ALERT_RULE_NAME } from '@kbn/rule-data-utils'; -import { TestProviders } from '../../../../../../common/mock'; -import { - mockAlertDetailsTimelineResponse, - mockAlertNestedDetailsTimelineResponse, -} from '../../../__mocks__'; -import type { RulePanelProps } from '.'; -import { RulePanel } from '.'; -import { getTimelineEventData } from '../../../utils/get_timeline_event_data'; -import { mockBrowserFields } from '../../../../../../common/containers/source/mock'; - -describe('AlertDetailsPage - SummaryTab - RulePanel', () => { - const RulePanelWithDefaultProps = (propOverrides: Partial) => ( - - - - ); - it('should render basic rule fields', () => { - const { getByTestId } = render(); - const simpleRuleFields = [ALERT_RISK_SCORE, ALERT_RULE_DESCRIPTION]; - - simpleRuleFields.forEach((simpleRuleField) => { - expect(getByTestId('rule-panel')).toHaveTextContent( - getTimelineEventData(simpleRuleField, mockAlertDetailsTimelineResponse) - ); - }); - }); - - it('should render the expected severity', () => { - const { getByTestId } = render(); - expect(getByTestId('rule-panel-severity')).toHaveTextContent('Medium'); - }); - - describe('Rule name link', () => { - it('should render the rule name as a link button', () => { - const { getByTestId } = render(); - const ruleName = getTimelineEventData(ALERT_RULE_NAME, mockAlertDetailsTimelineResponse); - expect(getByTestId('ruleName')).toHaveTextContent(ruleName); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/rule_panel/rule_panel_actions.tsx b/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/rule_panel/rule_panel_actions.tsx deleted file mode 100644 index a2eec20864a2b..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/rule_panel/rule_panel_actions.tsx +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { EuiButtonIcon, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; -import React, { useCallback, useMemo, useState } from 'react'; -import { getRuleDetailsUrl } from '../../../../../../common/components/link_to'; -import { SecurityPageName } from '../../../../../../app/types'; -import { useGetSecuritySolutionLinkProps } from '../../../../../../common/components/links'; - -import { SUMMARY_PANEL_ACTIONS, OPEN_RULE_DETAILS_PAGE } from '../translation'; - -export const RULE_PANEL_ACTIONS_CLASS = 'rule-panel-actions-trigger'; - -export const RulePanelActions = React.memo( - ({ className, ruleUuid }: { className?: string; ruleUuid: string }) => { - const [isPopoverOpen, setPopover] = useState(false); - const { href } = useGetSecuritySolutionLinkProps()({ - deepLinkId: SecurityPageName.rules, - path: getRuleDetailsUrl(ruleUuid), - }); - - const onButtonClick = useCallback(() => { - setPopover(!isPopoverOpen); - }, [isPopoverOpen]); - - const closePopover = () => { - setPopover(false); - }; - - const items = useMemo( - () => [ - - {OPEN_RULE_DETAILS_PAGE} - , - ], - [href] - ); - - const button = useMemo( - () => ( - - ), - [onButtonClick] - ); - - return ( -
- - - -
- ); - } -); - -RulePanelActions.displayName = 'RulePanelActions'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/translation.ts b/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/translation.ts deleted file mode 100644 index 6a509ff958735..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/translation.ts +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; - -export const CASES_PANEL_TITLE = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.summary.cases.title', - { - defaultMessage: 'Cases', - } -); - -export const CASES_PANEL_SUBTITLE = (caseCount: number) => - i18n.translate('xpack.securitySolution.alerts.alertDetails.summary.cases.subTitle', { - values: { caseCount }, - defaultMessage: 'Showing the {caseCount} most recently created cases containing this alert', - }); - -export const CASES_PANEL_CASE_STATUS = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.summary.cases.status', - { - defaultMessage: 'Status', - } -); - -export const ALERT_REASON_PANEL_TITLE = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.summary.alertReason.title', - { - defaultMessage: 'Alert reason', - } -); - -export const RULE_PANEL_TITLE = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.summary.rule.title', - { - defaultMessage: 'Rule', - } -); - -export const HOST_PANEL_TITLE = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.summary.host.title', - { - defaultMessage: 'Host', - } -); - -export const USER_PANEL_TITLE = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.summary.user.title', - { - defaultMessage: 'User', - } -); - -export const RULE_NAME_TITLE = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.summary.rule.name', - { - defaultMessage: 'Rule name', - } -); - -export const RISK_SCORE_TITLE = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.summary.rule.riskScore', - { - defaultMessage: 'Risk score', - } -); - -export const SEVERITY_TITLE = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.summary.rule.severity', - { - defaultMessage: 'Severity', - } -); - -export const RULE_DESCRIPTION_TITLE = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.summary.rule.description', - { - defaultMessage: 'Rule description', - } -); - -export const OPEN_RULE_DETAILS_PAGE = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.summary.rule.action.openRuleDetailsPage', - { - defaultMessage: 'Open rule details page', - } -); - -export const NO_RELATED_CASES_FOUND = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.summary.case.noCasesFound', - { - defaultMessage: 'Related cases were not found for this alert', - } -); - -export const LOADING_CASES = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.summary.case.loading', - { - defaultMessage: 'Loading related cases...', - } -); - -export const ERROR_LOADING_CASES = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.summary.case.error', - { - defaultMessage: 'Error loading related cases', - } -); - -export const CASE_NO_READ_PERMISSIONS = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.summary.case.noRead', - { - defaultMessage: - 'You do not have the required permissions to view related cases. If you need to view cases, contact your Kibana administrator', - } -); - -export const ADD_TO_EXISTING_CASE_BUTTON = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.summary.case.addToExistingCase', - { - defaultMessage: 'Add to existing case', - } -); - -export const ADD_TO_NEW_CASE_BUTTON = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.summary.case.addToNewCase', - { - defaultMessage: 'Add to new case', - } -); - -export const HOST_NAME_TITLE = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.summary.host.hostName.title', - { - defaultMessage: 'Host name', - } -); - -export const OPERATING_SYSTEM_TITLE = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.summary.host.osName.title', - { - defaultMessage: 'Operating system', - } -); - -export const AGENT_STATUS_TITLE = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.summary.host.agentStatus.title', - { - defaultMessage: 'Agent status', - } -); - -export const IP_ADDRESSES_TITLE = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.summary.ipAddresses.title', - { - defaultMessage: 'IP addresses', - } -); - -export const LAST_SEEN_TITLE = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.summary.lastSeen.title', - { - defaultMessage: 'Last seen', - } -); - -export const VIEW_HOST_SUMMARY = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.summary.host.action.viewHostSummary', - { - defaultMessage: 'View host summary', - } -); - -export const OPEN_HOST_DETAILS_PAGE = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.summary.host.action.openHostDetailsPage', - { - defaultMessage: 'Open host details page', - } -); - -export const HOST_RISK_SCORE = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.summary.host.riskScore', - { - defaultMessage: 'Host risk score', - } -); - -export const HOST_RISK_LEVEL = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.summary.host.riskLevel', - { - defaultMessage: 'Host risk level', - } -); - -export const USER_NAME_TITLE = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.summary.user.userName.title', - { - defaultMessage: 'User name', - } -); - -export const USER_RISK_SCORE = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.summary.user.riskScore', - { - defaultMessage: 'User risk score', - } -); - -export const USER_RISK_LEVEL = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.summary.user.riskLevel', - { - defaultMessage: 'User risk level', - } -); - -export const VIEW_USER_SUMMARY = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.summary.user.action.viewUserSummary', - { - defaultMessage: 'View user summary', - } -); - -export const OPEN_USER_DETAILS_PAGE = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.summary.user.action.openUserDetailsPage', - { - defaultMessage: 'Open user details page', - } -); - -export const SUMMARY_PANEL_ACTIONS = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.summary.panelMoreActions', - { - defaultMessage: 'More actions', - } -); diff --git a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/user_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/user_panel/index.tsx deleted file mode 100644 index 3fca60579b1da..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/user_panel/index.tsx +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EuiTitle, EuiSpacer, EuiFlexGroup, EuiFlexItem, EuiIcon } from '@elastic/eui'; -import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; -import React, { useCallback, useMemo } from 'react'; -import { find } from 'lodash/fp'; -import type { EuiFlexItemProps } from '@elastic/eui/src/components/flex/flex_item'; -import { useRiskScore } from '../../../../../../explore/containers/risk_score'; -import { RiskScoreEntity } from '../../../../../../../common/search_strategy'; -import { getEmptyTagValue } from '../../../../../../common/components/empty_value'; -import { RiskScoreLevel } from '../../../../../../explore/components/risk_score/severity/common'; -import { - FirstLastSeen, - FirstLastSeenType, -} from '../../../../../../common/components/first_last_seen'; -import { DefaultFieldRenderer } from '../../../../../../timelines/components/field_renderers/field_renderers'; -import { NetworkDetailsLink, UserDetailsLink } from '../../../../../../common/components/links'; -import type { SelectedDataView } from '../../../../../../common/store/sourcerer/model'; -import { SourcererScopeName } from '../../../../../../common/store/sourcerer/model'; -import { getTimelineEventData } from '../../../utils/get_timeline_event_data'; -import { - IP_ADDRESSES_TITLE, - LAST_SEEN_TITLE, - USER_NAME_TITLE, - USER_PANEL_TITLE, - USER_RISK_LEVEL, - USER_RISK_SCORE, -} from '../translation'; -import { SummaryPanel } from '../wrappers'; -import { UserPanelActions, USER_PANEL_ACTIONS_CLASS } from './user_panel_actions'; - -export interface UserPanelProps { - data: TimelineEventsDetailsItem[] | null; - selectedPatterns: SelectedDataView['selectedPatterns']; - openUserDetailsPanel: (userName: string, onClose?: (() => void) | undefined) => void; -} - -const UserPanelSection: React.FC<{ - title?: string | React.ReactElement; - grow?: EuiFlexItemProps['grow']; -}> = ({ grow, title, children }) => - children ? ( - - {title && ( - <> - -
{title}
-
- - - )} - {children} -
- ) : null; - -export const UserPanel = React.memo( - ({ data, selectedPatterns, openUserDetailsPanel }: UserPanelProps) => { - const userName = useMemo(() => getTimelineEventData('user.name', data), [data]); - - const { data: userRisk, isAuthorized: isRiskScoreAuthorized } = useRiskScore({ - riskEntity: RiskScoreEntity.user, - skip: userName == null, - }); - - const renderUserActions = useCallback( - () => , - [openUserDetailsPanel, userName] - ); - - const [userRiskScore, userRiskLevel] = useMemo(() => { - const userRiskData = userRisk && userRisk.length > 0 ? userRisk[0] : undefined; - const userRiskValue = userRiskData - ? Math.round(userRiskData.user.risk.calculated_score_norm) - : getEmptyTagValue(); - const userRiskSeverity = userRiskData ? ( - - ) : ( - getEmptyTagValue() - ); - - return [userRiskValue, userRiskSeverity]; - }, [userRisk]); - - const sourceIpFields = useMemo( - () => find({ field: 'source.ip', category: 'source' }, data)?.values ?? [], - [data] - ); - - const renderSourceIp = useCallback( - (ip: string) => (ip != null ? : getEmptyTagValue()), - [] - ); - - return ( - - - - - - - - - {userName ? : getEmptyTagValue()} - - - - {isRiskScoreAuthorized && ( - <> - - {userRiskScore && ( - {userRiskScore} - )} - {userRiskLevel && ( - {userRiskLevel} - )} - - - - )} - - - - - - - - - - - - - - - - ); - } -); - -UserPanel.displayName = 'UserPanel'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/user_panel/user_panel.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/user_panel/user_panel.test.tsx deleted file mode 100644 index a2d5978d05e96..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/user_panel/user_panel.test.tsx +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { render } from '@testing-library/react'; -import { TestProviders } from '../../../../../../common/mock'; -import { - mockAlertDetailsTimelineResponse, - mockAlertNestedDetailsTimelineResponse, -} from '../../../__mocks__'; -import type { UserPanelProps } from '.'; -import { UserPanel } from '.'; -import { getTimelineEventData } from '../../../utils/get_timeline_event_data'; -import { RiskSeverity } from '../../../../../../../common/search_strategy'; -import { useRiskScore } from '../../../../../../explore/containers/risk_score'; -import { find } from 'lodash/fp'; - -jest.mock('../../../../../../explore/containers/risk_score'); -const mockUseRiskScore = useRiskScore as jest.Mock; - -describe('AlertDetailsPage - SummaryTab - UserPanel', () => { - const defaultRiskReturnValues = { - inspect: null, - refetch: () => {}, - isModuleEnabled: true, - isAuthorized: true, - loading: false, - }; - const UserPanelWithDefaultProps = (propOverrides: Partial) => ( - - - - ); - - beforeEach(() => { - mockUseRiskScore.mockReturnValue({ ...defaultRiskReturnValues }); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should render basic user fields', () => { - const { getByTestId } = render(); - const simpleUserFields = ['user.name']; - - simpleUserFields.forEach((simpleUserField) => { - expect(getByTestId('user-panel')).toHaveTextContent( - getTimelineEventData(simpleUserField, mockAlertDetailsTimelineResponse) - ); - }); - }); - - describe('user risk', () => { - it('should not show risk if the license is not valid', () => { - mockUseRiskScore.mockReturnValue({ - ...defaultRiskReturnValues, - isAuthorized: false, - data: null, - }); - const { queryByTestId } = render(); - expect(queryByTestId('user-panel-risk')).toBe(null); - }); - - it('should render risk fields', () => { - const calculatedScoreNorm = 98.9; - const calculatedLevel = RiskSeverity.critical; - - mockUseRiskScore.mockReturnValue({ - ...defaultRiskReturnValues, - isAuthorized: true, - data: [ - { - user: { - name: mockAlertNestedDetailsTimelineResponse.user?.name, - risk: { - calculated_score_norm: calculatedScoreNorm, - calculated_level: calculatedLevel, - }, - }, - }, - ], - }); - const { getByTestId } = render(); - - expect(getByTestId('user-panel-risk')).toHaveTextContent( - `${Math.round(calculatedScoreNorm)}` - ); - expect(getByTestId('user-panel-risk')).toHaveTextContent(calculatedLevel); - }); - }); - - describe('source ip', () => { - it('should render all the ip fields', () => { - const { getByTestId } = render(); - const ipFields = find( - { field: 'source.ip', category: 'source' }, - mockAlertDetailsTimelineResponse - )?.values as string[]; - expect(getByTestId('user-panel-ip')).toHaveTextContent(ipFields[0]); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/user_panel/user_panel_actions.tsx b/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/user_panel/user_panel_actions.tsx deleted file mode 100644 index 575673a494a2f..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/user_panel/user_panel_actions.tsx +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { EuiButtonIcon, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; -import React, { useCallback, useMemo, useState } from 'react'; -import { getUsersDetailsUrl } from '../../../../../../common/components/link_to/redirect_to_users'; -import { SecurityPageName } from '../../../../../../app/types'; -import { useGetSecuritySolutionLinkProps } from '../../../../../../common/components/links'; - -import { OPEN_USER_DETAILS_PAGE, SUMMARY_PANEL_ACTIONS, VIEW_USER_SUMMARY } from '../translation'; - -export const USER_PANEL_ACTIONS_CLASS = 'user-panel-actions-trigger'; - -export const UserPanelActions = React.memo( - ({ - className, - openUserDetailsPanel, - userName, - }: { - className?: string; - userName: string; - openUserDetailsPanel: (userName: string) => void; - }) => { - const [isPopoverOpen, setPopover] = useState(false); - const { href } = useGetSecuritySolutionLinkProps()({ - deepLinkId: SecurityPageName.users, - path: getUsersDetailsUrl(userName), - }); - - const onButtonClick = useCallback(() => { - setPopover(!isPopoverOpen); - }, [isPopoverOpen]); - - const closePopover = () => { - setPopover(false); - }; - - const handleopenUserDetailsPanel = useCallback(() => { - openUserDetailsPanel(userName); - closePopover(); - }, [userName, openUserDetailsPanel]); - - const items = useMemo( - () => [ - - {VIEW_USER_SUMMARY} - , - - {OPEN_USER_DETAILS_PAGE} - , - ], - [handleopenUserDetailsPanel, href] - ); - - const button = useMemo( - () => ( - - ), - [onButtonClick] - ); - - return ( -
- - - -
- ); - } -); - -UserPanelActions.displayName = 'UserPanelActions'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/wrappers.tsx b/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/wrappers.tsx deleted file mode 100644 index fd9cf26c7280e..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/alert_details/tabs/summary/wrappers.tsx +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; -import type { EuiFlexItemProps } from '@elastic/eui'; -import { css } from '@emotion/react'; -import { HoverVisibilityContainer } from '../../../../../common/components/hover_visibility_container'; - -export const SummaryColumn: React.FC<{ grow?: EuiFlexItemProps['grow'] }> = ({ - children, - grow, -}) => ( - - - {children} - - -); - -export const SummaryRow: React.FC<{ grow?: EuiFlexItemProps['grow'] }> = ({ children, grow }) => ( - - - {children} - - -); - -export const SummaryPanel: React.FC<{ - grow?: EuiFlexItemProps['grow']; - title: string; - description?: string; - actionsClassName?: string; - renderActionsPopover?: () => JSX.Element; -}> = ({ actionsClassName, children, description, grow = false, renderActionsPopover, title }) => ( - - - - - - -

{title}

-
- - {description && ( - -

{description}

-
- )} -
- {actionsClassName && renderActionsPopover ? ( - {renderActionsPopover()} - ) : null} -
-
- - {children} -
-
-); diff --git a/x-pack/plugins/security_solution/public/detections/pages/alert_details/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/alert_details/translations.ts deleted file mode 100644 index 0a6ac1d91401f..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/alert_details/translations.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; - -export const ALERT_DETAILS_TECHNICAL_PREVIEW = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.header.technicalPreview', - { - defaultMessage: 'Technical Preview', - } -); - -export const SUMMARY_PAGE_TITLE = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.navigation.summary', - { - defaultMessage: 'Summary', - } -); - -export const BACK_TO_ALERTS_LINK = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.header.backToAlerts', - { - defaultMessage: 'Back to alerts', - } -); - -export const LOADING_PAGE_MESSAGE = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.loadingPage.message', - { - defaultMessage: 'Loading details page...', - } -); - -export const ERROR_PAGE_TITLE = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.errorPage.title', - { - defaultMessage: 'Unable to load the details page', - } -); - -export const ERROR_PAGE_BODY = i18n.translate( - 'xpack.securitySolution.alerts.alertDetails.errorPage.message', - { - defaultMessage: - 'There was an error loading the details page. Please confirm the following id points to a valid document', - } -); diff --git a/x-pack/plugins/security_solution/public/detections/pages/alert_details/types.ts b/x-pack/plugins/security_solution/public/detections/pages/alert_details/types.ts deleted file mode 100644 index 3b4138a9d3d7d..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/alert_details/types.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { NavTab } from '../../../common/components/navigation/types'; - -export enum AlertDetailRouteType { - summary = 'summary', -} - -export type AlertDetailNavTabs = Record<`${AlertDetailRouteType}`, NavTab>; diff --git a/x-pack/plugins/security_solution/public/detections/pages/alert_details/utils/breadcrumbs.ts b/x-pack/plugins/security_solution/public/detections/pages/alert_details/utils/breadcrumbs.ts deleted file mode 100644 index 2b6dca72bf078..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/alert_details/utils/breadcrumbs.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { ChromeBreadcrumb } from '@kbn/core/public'; -import type { GetTrailingBreadcrumbs } from '../../../../common/components/navigation/breadcrumbs/types'; -import { getAlertDetailsUrl } from '../../../../common/components/link_to'; -import { SecurityPageName } from '../../../../../common/constants'; -import type { AlertDetailRouteSpyState } from '../../../../common/utils/route/types'; -import { AlertDetailRouteType } from '../types'; -import * as i18n from '../translations'; - -const TabNameMappedToI18nKey: Record = { - [AlertDetailRouteType.summary]: i18n.SUMMARY_PAGE_TITLE, -}; - -/** - * This module should only export this function. - * All the `getTrailingBreadcrumbs` functions in Security are loaded into the main bundle. - * We should be careful to not import unnecessary modules in this file to avoid increasing the main app bundle size. - */ -export const getTrailingBreadcrumbs: GetTrailingBreadcrumbs = ( - params, - getSecuritySolutionUrl -) => { - let breadcrumb: ChromeBreadcrumb[] = []; - - if (params.detailName != null) { - breadcrumb = [ - ...breadcrumb, - { - text: params.state?.ruleName ?? params.detailName, - href: getSecuritySolutionUrl({ - path: getAlertDetailsUrl(params.detailName, ''), - deepLinkId: SecurityPageName.alerts, - }), - }, - ]; - } - if (params.tabName != null) { - breadcrumb = [ - ...breadcrumb, - { - text: TabNameMappedToI18nKey[params.tabName], - href: '', - }, - ]; - } - return breadcrumb; -}; diff --git a/x-pack/plugins/security_solution/public/detections/pages/alert_details/utils/get_timeline_event_data.ts b/x-pack/plugins/security_solution/public/detections/pages/alert_details/utils/get_timeline_event_data.ts deleted file mode 100644 index 7d5dbc5440087..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/alert_details/utils/get_timeline_event_data.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { TimelineEventsDetailsItem } from '../../../../../common/search_strategy'; - -export const getTimelineEventData = (field: string, data: TimelineEventsDetailsItem[] | null) => { - const valueArray = data?.find((datum) => datum.field === field)?.values; - return valueArray && valueArray.length > 0 ? valueArray[0] : ''; -}; diff --git a/x-pack/plugins/security_solution/public/detections/pages/alert_details/utils/navigation.ts b/x-pack/plugins/security_solution/public/detections/pages/alert_details/utils/navigation.ts deleted file mode 100644 index 540cd99ad9bde..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/pages/alert_details/utils/navigation.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { AlertDetailNavTabs } from '../types'; -import { ALERTS_PATH } from '../../../../../common/constants'; -import { AlertDetailRouteType } from '../types'; -import * as i18n from '../translations'; - -export const getAlertDetailsTabUrl = (alertId: string, tabName: AlertDetailRouteType) => - `${ALERTS_PATH}/${alertId}/${tabName}`; - -export const getAlertDetailsNavTabs = (alertId: string): AlertDetailNavTabs => ({ - [AlertDetailRouteType.summary]: { - id: AlertDetailRouteType.summary, - name: i18n.SUMMARY_PAGE_TITLE, - href: getAlertDetailsTabUrl(alertId, AlertDetailRouteType.summary), - disabled: false, - }, -}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx index 2f530424f9384..82d41e0eafb33 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx @@ -23,12 +23,6 @@ import React from 'react'; import styled from 'styled-components'; import { useAssistantAvailability } from '../../../../assistant/use_assistant_availability'; -import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; -import { getAlertDetailsUrl } from '../../../../common/components/link_to'; -import { - SecuritySolutionLinkAnchor, - useGetSecuritySolutionLinkProps, -} from '../../../../common/components/links'; import type { TimelineTabs } from '../../../../../common/types/timeline'; import type { BrowserFields } from '../../../../common/containers/source'; import { EventDetails } from '../../../../common/components/event_details/event_details'; @@ -39,7 +33,6 @@ import { EVENT_SUMMARY_CONVERSATION_ID, } from '../../../../common/components/event_details/translations'; import { PreferenceFormattedDate } from '../../../../common/components/formatted_date'; -import { SecurityPageName } from '../../../../../common/constants'; import { useGetAlertDetailsFlyoutLink } from './use_get_alert_details_flyout_link'; export type HandleOnEventClosed = () => void; @@ -98,12 +91,6 @@ export const ExpandableEventTitle = React.memo( timestamp, }) => { const { hasAssistantPrivilege } = useAssistantAvailability(); - const isAlertDetailsPageEnabled = useIsExperimentalFeatureEnabled('alertDetailsPageEnabled'); - const { onClick } = useGetSecuritySolutionLinkProps()({ - deepLinkId: SecurityPageName.alerts, - path: eventId && isAlert ? getAlertDetailsUrl(eventId) : '', - }); - const alertDetailsLink = useGetAlertDetailsFlyoutLink({ _id: eventId, _index: eventIndex, @@ -124,19 +111,6 @@ export const ExpandableEventTitle = React.memo( )} - {isAlert && eventId && isAlertDetailsPageEnabled && ( - <> - - - {i18n.OPEN_ALERT_DETAILS_PAGE} - - - - )} )} diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/translations.ts index a40a9095ea111..3416636f71e17 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/translations.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/translations.ts @@ -7,20 +7,6 @@ import { i18n } from '@kbn/i18n'; -export const MESSAGE = i18n.translate( - 'xpack.securitySolution.timeline.expandableEvent.messageTitle', - { - defaultMessage: 'Message', - } -); - -export const OPEN_ALERT_DETAILS_PAGE = i18n.translate( - 'xpack.securitySolution.timeline.expandableEvent.openAlertDetails', - { - defaultMessage: 'Open alert details page', - } -); - export const CLOSE = i18n.translate( 'xpack.securitySolution.timeline.expandableEvent.closeEventDetailsLabel', { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx index cd0b49c515bb6..4ab99faba09c4 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx @@ -131,19 +131,6 @@ jest.mock('../../../../common/components/links', () => { }; }); -jest.mock( - '../../../../detections/components/alerts_table/timeline_actions/use_open_alert_details', - () => { - return { - useOpenAlertDetailsAction: () => { - return { - alertDetailsActionItems: [], - }; - }, - }; - } -); - // Prevent Resolver from rendering jest.mock('../../graph_overlay'); diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 25530be35e9a7..ba03ddd298d5f 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -29158,7 +29158,6 @@ "xpack.securitySolution.alertDetails.overview.insights.relatedCasesFailure": "Impossible de charger les cas connexes : \"{error}\"", "xpack.securitySolution.alertDetails.overview.insights.suppressedAlertsCount": "{count} {count, plural, =1 {alerte} one {alertes} many {alertes} other {alertes}} supprimée(s)", "xpack.securitySolution.alertDetails.overview.riskDataTooltipContent": "La classification des risques n'est affichée que lorsqu'elle est disponible pour une {riskEntity}. Vérifiez que {riskScoreDocumentationLink} est activé dans votre environnement.", - "xpack.securitySolution.alerts.alertDetails.summary.cases.subTitle": "Affichage des {caseCount} cas les plus récemment créés contenant cette alerte", "xpack.securitySolution.alertSummaryView.alertSummaryViewContextDescription": "Alerte (à partir de {view})", "xpack.securitySolution.alertSummaryView.eventSummaryViewContextDescription": "Événement (à partir de {view})", "xpack.securitySolution.anomaliesTable.table.unit": "{totalCount, plural, =1 {anomalie} one {anomalies} many {anomalies} other {anomalies}}", @@ -29735,42 +29734,6 @@ "xpack.securitySolution.alertDetails.summary.readLess": "Lire moins", "xpack.securitySolution.alertDetails.summary.readMore": "En savoir plus", "xpack.securitySolution.alertDetails.threatIntel": "Threat Intelligence", - "xpack.securitySolution.alerts.alertDetails.errorPage.message": "Une erreur s'est produite lors du chargement de la page de détails. Veuillez confirmer que l'ID suivant pointe vers un document valide", - "xpack.securitySolution.alerts.alertDetails.errorPage.title": "Impossible de charger la page de détails", - "xpack.securitySolution.alerts.alertDetails.header.backToAlerts": "Retour aux alertes", - "xpack.securitySolution.alerts.alertDetails.header.technicalPreview": "Version d'évaluation technique", - "xpack.securitySolution.alerts.alertDetails.loadingPage.message": "Chargement de la page de détails...", - "xpack.securitySolution.alerts.alertDetails.navigation.summary": "Résumé", - "xpack.securitySolution.alerts.alertDetails.summary.alertReason.title": "Raison d'alerte", - "xpack.securitySolution.alerts.alertDetails.summary.case.addToExistingCase": "Ajouter à un cas existant", - "xpack.securitySolution.alerts.alertDetails.summary.case.addToNewCase": "Ajouter au nouveau cas", - "xpack.securitySolution.alerts.alertDetails.summary.case.error": "Erreur lors du chargement des cas connexes", - "xpack.securitySolution.alerts.alertDetails.summary.case.loading": "Chargement des cas connexes...", - "xpack.securitySolution.alerts.alertDetails.summary.case.noCasesFound": "Impossible de trouver les cas connexes pour cette alerte", - "xpack.securitySolution.alerts.alertDetails.summary.case.noRead": "Vous ne disposez pas des autorisations requises pour afficher les cas connexes. Si vous avez besoin d'afficher les cas, contactez votre administrateur Kibana", - "xpack.securitySolution.alerts.alertDetails.summary.cases.status": "Statut", - "xpack.securitySolution.alerts.alertDetails.summary.cases.title": "Cas", - "xpack.securitySolution.alerts.alertDetails.summary.host.action.openHostDetailsPage": "Ouvrir la page de détails de l'hôte", - "xpack.securitySolution.alerts.alertDetails.summary.host.action.viewHostSummary": "Afficher le résumé de l'hôte", - "xpack.securitySolution.alerts.alertDetails.summary.host.agentStatus.title": "Statut de l'agent", - "xpack.securitySolution.alerts.alertDetails.summary.host.hostName.title": "Nom d'hôte", - "xpack.securitySolution.alerts.alertDetails.summary.host.osName.title": "Système d'exploitation", - "xpack.securitySolution.alerts.alertDetails.summary.host.riskScore": "Score de risque de l'hôte", - "xpack.securitySolution.alerts.alertDetails.summary.host.title": "Hôte", - "xpack.securitySolution.alerts.alertDetails.summary.ipAddresses.title": "Adresses IP", - "xpack.securitySolution.alerts.alertDetails.summary.lastSeen.title": "Vu en dernier", - "xpack.securitySolution.alerts.alertDetails.summary.panelMoreActions": "Plus d'actions", - "xpack.securitySolution.alerts.alertDetails.summary.rule.action.openRuleDetailsPage": "Ouvrir la page de détails de la règle", - "xpack.securitySolution.alerts.alertDetails.summary.rule.description": "Description de la règle", - "xpack.securitySolution.alerts.alertDetails.summary.rule.name": "Nom de règle", - "xpack.securitySolution.alerts.alertDetails.summary.rule.riskScore": "Score de risque", - "xpack.securitySolution.alerts.alertDetails.summary.rule.severity": "Sévérité", - "xpack.securitySolution.alerts.alertDetails.summary.rule.title": "Règle", - "xpack.securitySolution.alerts.alertDetails.summary.user.action.openUserDetailsPage": "Ouvrir la page de détails de l'utilisateur", - "xpack.securitySolution.alerts.alertDetails.summary.user.action.viewUserSummary": "Afficher le résumé de l'utilisateur", - "xpack.securitySolution.alerts.alertDetails.summary.user.riskScore": "Score de risque de l'utilisateur", - "xpack.securitySolution.alerts.alertDetails.summary.user.title": "Utilisateur", - "xpack.securitySolution.alerts.alertDetails.summary.user.userName.title": "Nom d'utilisateur", "xpack.securitySolution.alerts.badge.readOnly.tooltip": "Impossible de mettre à jour les alertes", "xpack.securitySolution.alerts.riskScoreMapping.defaultDescriptionLabel": "Sélectionnez un score de risque pour toutes les alertes générées par cette règle.", "xpack.securitySolution.alerts.riskScoreMapping.defaultRiskScoreTitle": "Score de risque par défaut", @@ -30364,7 +30327,6 @@ "xpack.securitySolution.detectionEngine.alerts.actions.addToNewCase": "Ajouter au nouveau cas", "xpack.securitySolution.detectionEngine.alerts.actions.investigateInTimelineAriaLabel": "Envoyer une alerte à la chronologie", "xpack.securitySolution.detectionEngine.alerts.actions.investigateInTimelineTitle": "Investiguer dans la chronologie", - "xpack.securitySolution.detectionEngine.alerts.actions.openAlertDetails": "Ouvrir la page de détails de l'alerte", "xpack.securitySolution.detectionEngine.alerts.alertsByGrouping.chartTitle": "Alertes les plus fréquentes par", "xpack.securitySolution.detectionEngine.alerts.alertsByGrouping.destinationLabel": "destination", "xpack.securitySolution.detectionEngine.alerts.alertsByGrouping.hostNameLabel": "hôte", @@ -33543,8 +33505,6 @@ "xpack.securitySolution.timeline.expandableEvent.alertTitleLabel": "Détails de l'alerte", "xpack.securitySolution.timeline.expandableEvent.closeEventDetailsLabel": "fermer", "xpack.securitySolution.timeline.expandableEvent.eventTitleLabel": "Détails de l'événement", - "xpack.securitySolution.timeline.expandableEvent.messageTitle": "Message", - "xpack.securitySolution.timeline.expandableEvent.openAlertDetails": "Ouvrir la page de détails de l'alerte", "xpack.securitySolution.timeline.expandableEvent.placeholder": "Sélectionner un événement pour afficher ses détails", "xpack.securitySolution.timeline.expandableEvent.shareAlert": "Partager l'alerte", "xpack.securitySolution.timeline.failDescription": "Une erreur s'est produite", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 3fa06a6d8c35f..b2a00bc71bd43 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -29157,7 +29157,6 @@ "xpack.securitySolution.alertDetails.overview.insights.relatedCasesFailure": "関連するケースを読み込めません:\"{error}\"", "xpack.securitySolution.alertDetails.overview.insights.suppressedAlertsCount": "{count}件の抑制された{count, plural, =1 {アラート} other {アラート}}", "xpack.securitySolution.alertDetails.overview.riskDataTooltipContent": "リスク分類は、{riskEntity}で使用可能なときにのみ表示されます。{riskScoreDocumentationLink}が環境内で有効であることを確認します。", - "xpack.securitySolution.alerts.alertDetails.summary.cases.subTitle": "このアラートを含む直近に作成された{caseCount}件のケースを表示しています", "xpack.securitySolution.alertSummaryView.alertSummaryViewContextDescription": "アラート({view}から)", "xpack.securitySolution.alertSummaryView.eventSummaryViewContextDescription": "イベント({view}から)", "xpack.securitySolution.anomaliesTable.table.unit": "{totalCount, plural, =1 {異常} other {異常}}", @@ -29734,42 +29733,6 @@ "xpack.securitySolution.alertDetails.summary.readLess": "表示を減らす", "xpack.securitySolution.alertDetails.summary.readMore": "続きを読む", "xpack.securitySolution.alertDetails.threatIntel": "Threat Intel", - "xpack.securitySolution.alerts.alertDetails.errorPage.message": "詳細ページの読み込みエラーが発生しました。次のIDが有効なドキュメントを参照していることを確認してください", - "xpack.securitySolution.alerts.alertDetails.errorPage.title": "詳細ページを読み込めません", - "xpack.securitySolution.alerts.alertDetails.header.backToAlerts": "アラートに戻る", - "xpack.securitySolution.alerts.alertDetails.header.technicalPreview": "テクニカルプレビュー", - "xpack.securitySolution.alerts.alertDetails.loadingPage.message": "詳細ページを読み込んでいます...", - "xpack.securitySolution.alerts.alertDetails.navigation.summary": "まとめ", - "xpack.securitySolution.alerts.alertDetails.summary.alertReason.title": "アラートの理由", - "xpack.securitySolution.alerts.alertDetails.summary.case.addToExistingCase": "既存のケースに追加", - "xpack.securitySolution.alerts.alertDetails.summary.case.addToNewCase": "新しいケースに追加", - "xpack.securitySolution.alerts.alertDetails.summary.case.error": "関連するケースの読み込みエラー", - "xpack.securitySolution.alerts.alertDetails.summary.case.loading": "関連するケースを読み込んでいます...", - "xpack.securitySolution.alerts.alertDetails.summary.case.noCasesFound": "このアラートの関連ケースが見つかりませんでした", - "xpack.securitySolution.alerts.alertDetails.summary.case.noRead": "関連ケースを表示する権限がありません。ケースを表示する必要がある場合は、Kibana管理者に連絡してください。", - "xpack.securitySolution.alerts.alertDetails.summary.cases.status": "ステータス", - "xpack.securitySolution.alerts.alertDetails.summary.cases.title": "ケース", - "xpack.securitySolution.alerts.alertDetails.summary.host.action.openHostDetailsPage": "ホスト詳細ページを開く", - "xpack.securitySolution.alerts.alertDetails.summary.host.action.viewHostSummary": "ホスト概要を表示", - "xpack.securitySolution.alerts.alertDetails.summary.host.agentStatus.title": "エージェントステータス", - "xpack.securitySolution.alerts.alertDetails.summary.host.hostName.title": "ホスト名", - "xpack.securitySolution.alerts.alertDetails.summary.host.osName.title": "オペレーティングシステム", - "xpack.securitySolution.alerts.alertDetails.summary.host.riskScore": "ホストリスクスコア", - "xpack.securitySolution.alerts.alertDetails.summary.host.title": "ホスト", - "xpack.securitySolution.alerts.alertDetails.summary.ipAddresses.title": "IP アドレス", - "xpack.securitySolution.alerts.alertDetails.summary.lastSeen.title": "前回の認識", - "xpack.securitySolution.alerts.alertDetails.summary.panelMoreActions": "さらにアクションを表示", - "xpack.securitySolution.alerts.alertDetails.summary.rule.action.openRuleDetailsPage": "ルール詳細ページを開く", - "xpack.securitySolution.alerts.alertDetails.summary.rule.description": "ルールの説明", - "xpack.securitySolution.alerts.alertDetails.summary.rule.name": "ルール名", - "xpack.securitySolution.alerts.alertDetails.summary.rule.riskScore": "リスクスコア", - "xpack.securitySolution.alerts.alertDetails.summary.rule.severity": "深刻度", - "xpack.securitySolution.alerts.alertDetails.summary.rule.title": "ルール", - "xpack.securitySolution.alerts.alertDetails.summary.user.action.openUserDetailsPage": "ユーザー詳細ページを開く", - "xpack.securitySolution.alerts.alertDetails.summary.user.action.viewUserSummary": "ユーザー概要を表示", - "xpack.securitySolution.alerts.alertDetails.summary.user.riskScore": "ユーザーリスクスコア", - "xpack.securitySolution.alerts.alertDetails.summary.user.title": "ユーザー", - "xpack.securitySolution.alerts.alertDetails.summary.user.userName.title": "ユーザー名", "xpack.securitySolution.alerts.badge.readOnly.tooltip": "アラートを更新できません", "xpack.securitySolution.alerts.riskScoreMapping.defaultDescriptionLabel": "このルールで生成されたすべてのアラートのリスクスコアを選択します。", "xpack.securitySolution.alerts.riskScoreMapping.defaultRiskScoreTitle": "デフォルトリスクスコア", @@ -30363,7 +30326,6 @@ "xpack.securitySolution.detectionEngine.alerts.actions.addToNewCase": "新しいケースに追加", "xpack.securitySolution.detectionEngine.alerts.actions.investigateInTimelineAriaLabel": "アラートをタイムラインに送信", "xpack.securitySolution.detectionEngine.alerts.actions.investigateInTimelineTitle": "タイムラインで調査", - "xpack.securitySolution.detectionEngine.alerts.actions.openAlertDetails": "アラート詳細ページを開く", "xpack.securitySolution.detectionEngine.alerts.alertsByGrouping.chartTitle": "上位のアラート", "xpack.securitySolution.detectionEngine.alerts.alertsByGrouping.destinationLabel": "デスティネーション", "xpack.securitySolution.detectionEngine.alerts.alertsByGrouping.hostNameLabel": "ホスト", @@ -33542,8 +33504,6 @@ "xpack.securitySolution.timeline.expandableEvent.alertTitleLabel": "アラートの詳細", "xpack.securitySolution.timeline.expandableEvent.closeEventDetailsLabel": "閉じる", "xpack.securitySolution.timeline.expandableEvent.eventTitleLabel": "イベントの詳細", - "xpack.securitySolution.timeline.expandableEvent.messageTitle": "メッセージ", - "xpack.securitySolution.timeline.expandableEvent.openAlertDetails": "アラート詳細ページを開く", "xpack.securitySolution.timeline.expandableEvent.placeholder": "イベント詳細を表示するには、イベントを選択します", "xpack.securitySolution.timeline.expandableEvent.shareAlert": "アラートを共有", "xpack.securitySolution.timeline.failDescription": "エラーが発生しました", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 2c57adbd57df1..5c97128b8c8f1 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -29153,7 +29153,6 @@ "xpack.securitySolution.alertDetails.overview.insights.relatedCasesFailure": "无法加载相关案例:“{error}”", "xpack.securitySolution.alertDetails.overview.insights.suppressedAlertsCount": "{count} 个已阻止{count, plural, =1 {告警} other {告警}}", "xpack.securitySolution.alertDetails.overview.riskDataTooltipContent": "仅在其对 {riskEntity} 可用时才会显示风险分类。确保在您的环境中启用了 {riskScoreDocumentationLink}。", - "xpack.securitySolution.alerts.alertDetails.summary.cases.subTitle": "正在显示 {caseCount} 个包含此告警的最新创建的案例", "xpack.securitySolution.alertSummaryView.alertSummaryViewContextDescription": "告警(来自 {view})", "xpack.securitySolution.alertSummaryView.eventSummaryViewContextDescription": "事件(来自 {view})", "xpack.securitySolution.anomaliesTable.table.unit": "{totalCount, plural, =1 {异常} other {异常}}", @@ -29730,42 +29729,6 @@ "xpack.securitySolution.alertDetails.summary.readLess": "阅读更少内容", "xpack.securitySolution.alertDetails.summary.readMore": "阅读更多内容", "xpack.securitySolution.alertDetails.threatIntel": "威胁情报", - "xpack.securitySolution.alerts.alertDetails.errorPage.message": "加载详情页面时出错。请确认以下 ID 是否指向有效文档", - "xpack.securitySolution.alerts.alertDetails.errorPage.title": "无法加载详情页面", - "xpack.securitySolution.alerts.alertDetails.header.backToAlerts": "返回到告警", - "xpack.securitySolution.alerts.alertDetails.header.technicalPreview": "技术预览", - "xpack.securitySolution.alerts.alertDetails.loadingPage.message": "正在加载详情页面......", - "xpack.securitySolution.alerts.alertDetails.navigation.summary": "摘要", - "xpack.securitySolution.alerts.alertDetails.summary.alertReason.title": "告警原因", - "xpack.securitySolution.alerts.alertDetails.summary.case.addToExistingCase": "添加到现有案例", - "xpack.securitySolution.alerts.alertDetails.summary.case.addToNewCase": "添加到新案例", - "xpack.securitySolution.alerts.alertDetails.summary.case.error": "加载相关案例时出错", - "xpack.securitySolution.alerts.alertDetails.summary.case.loading": "正在加载相关案例......", - "xpack.securitySolution.alerts.alertDetails.summary.case.noCasesFound": "找不到此告警的相关案例", - "xpack.securitySolution.alerts.alertDetails.summary.case.noRead": "您没有查看相关案例所需的权限。如果需要查看案例,请联系您的 Kibana 管理员", - "xpack.securitySolution.alerts.alertDetails.summary.cases.status": "状态", - "xpack.securitySolution.alerts.alertDetails.summary.cases.title": "案例", - "xpack.securitySolution.alerts.alertDetails.summary.host.action.openHostDetailsPage": "打开主机详情页面", - "xpack.securitySolution.alerts.alertDetails.summary.host.action.viewHostSummary": "查看主机摘要", - "xpack.securitySolution.alerts.alertDetails.summary.host.agentStatus.title": "代理状态", - "xpack.securitySolution.alerts.alertDetails.summary.host.hostName.title": "主机名", - "xpack.securitySolution.alerts.alertDetails.summary.host.osName.title": "操作系统", - "xpack.securitySolution.alerts.alertDetails.summary.host.riskScore": "主机风险分数", - "xpack.securitySolution.alerts.alertDetails.summary.host.title": "主机", - "xpack.securitySolution.alerts.alertDetails.summary.ipAddresses.title": "IP 地址", - "xpack.securitySolution.alerts.alertDetails.summary.lastSeen.title": "最后看到时间", - "xpack.securitySolution.alerts.alertDetails.summary.panelMoreActions": "更多操作", - "xpack.securitySolution.alerts.alertDetails.summary.rule.action.openRuleDetailsPage": "打开规则详情页面", - "xpack.securitySolution.alerts.alertDetails.summary.rule.description": "规则描述", - "xpack.securitySolution.alerts.alertDetails.summary.rule.name": "规则名称", - "xpack.securitySolution.alerts.alertDetails.summary.rule.riskScore": "风险分数", - "xpack.securitySolution.alerts.alertDetails.summary.rule.severity": "严重性", - "xpack.securitySolution.alerts.alertDetails.summary.rule.title": "规则", - "xpack.securitySolution.alerts.alertDetails.summary.user.action.openUserDetailsPage": "打开用户详情页面", - "xpack.securitySolution.alerts.alertDetails.summary.user.action.viewUserSummary": "查看用户摘要", - "xpack.securitySolution.alerts.alertDetails.summary.user.riskScore": "用户风险分数", - "xpack.securitySolution.alerts.alertDetails.summary.user.title": "用户", - "xpack.securitySolution.alerts.alertDetails.summary.user.userName.title": "用户名", "xpack.securitySolution.alerts.badge.readOnly.tooltip": "无法更新告警", "xpack.securitySolution.alerts.riskScoreMapping.defaultDescriptionLabel": "选择此规则生成的所有告警的风险分数。", "xpack.securitySolution.alerts.riskScoreMapping.defaultRiskScoreTitle": "默认风险分数", @@ -30359,7 +30322,6 @@ "xpack.securitySolution.detectionEngine.alerts.actions.addToNewCase": "添加到新案例", "xpack.securitySolution.detectionEngine.alerts.actions.investigateInTimelineAriaLabel": "将告警发送到时间线", "xpack.securitySolution.detectionEngine.alerts.actions.investigateInTimelineTitle": "在时间线中调查", - "xpack.securitySolution.detectionEngine.alerts.actions.openAlertDetails": "打开告警详情页面", "xpack.securitySolution.detectionEngine.alerts.alertsByGrouping.chartTitle": "排名靠前规则排列依据", "xpack.securitySolution.detectionEngine.alerts.alertsByGrouping.destinationLabel": "目标", "xpack.securitySolution.detectionEngine.alerts.alertsByGrouping.hostNameLabel": "主机", @@ -33538,8 +33500,6 @@ "xpack.securitySolution.timeline.expandableEvent.alertTitleLabel": "告警详情", "xpack.securitySolution.timeline.expandableEvent.closeEventDetailsLabel": "关闭", "xpack.securitySolution.timeline.expandableEvent.eventTitleLabel": "事件详情", - "xpack.securitySolution.timeline.expandableEvent.messageTitle": "消息", - "xpack.securitySolution.timeline.expandableEvent.openAlertDetails": "打开告警详情页面", "xpack.securitySolution.timeline.expandableEvent.placeholder": "选择事件以显示事件详情", "xpack.securitySolution.timeline.expandableEvent.shareAlert": "共享告警", "xpack.securitySolution.timeline.failDescription": "发生错误", diff --git a/x-pack/test/security_solution_cypress/config.ts b/x-pack/test/security_solution_cypress/config.ts index 99569e7b3084f..fb34362f7fb9b 100644 --- a/x-pack/test/security_solution_cypress/config.ts +++ b/x-pack/test/security_solution_cypress/config.ts @@ -45,7 +45,6 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { '--xpack.alerting.rules.minimumScheduleInterval.value=1s', '--xpack.ruleRegistry.unsafe.legacyMultiTenancy.enabled=true', `--xpack.securitySolution.enableExperimental=${JSON.stringify([ - 'alertDetailsPageEnabled', 'chartEmbeddablesEnabled', ])}`, // mock cloud to enable the guided onboarding tour in e2e tests diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/enrichments.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/enrichments.cy.ts index f5716f33ff288..99cbf31012d75 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/enrichments.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/enrichments.cy.ts @@ -7,8 +7,8 @@ import { getNewRule } from '../../objects/rule'; import { - HOST_RISK_HEADER_COLIMN, - USER_RISK_HEADER_COLIMN, + HOST_RISK_HEADER_COLUMN, + USER_RISK_HEADER_COLUMN, HOST_RISK_COLUMN, USER_RISK_COLUMN, ACTION_COLUMN, @@ -71,8 +71,8 @@ describe('Enrichment', { tags: ['@ess', '@serverless'] }, () => { cy.get(ALERTS_COUNT) .invoke('text') .should('match', /^[1-9].+$/); // Any number of alerts - cy.get(HOST_RISK_HEADER_COLIMN).contains('host.risk.calculated_level'); - cy.get(USER_RISK_HEADER_COLIMN).contains('user.risk.calculated_level'); + cy.get(HOST_RISK_HEADER_COLUMN).contains('host.risk.calculated_level'); + cy.get(USER_RISK_HEADER_COLUMN).contains('user.risk.calculated_level'); scrollAlertTableColumnIntoView(HOST_RISK_COLUMN); cy.get(HOST_RISK_COLUMN).contains('Low'); scrollAlertTableColumnIntoView(USER_RISK_COLUMN); @@ -115,8 +115,8 @@ describe('Enrichment', { tags: ['@ess', '@serverless'] }, () => { cy.get(ALERTS_COUNT) .invoke('text') .should('match', /^[1-9].+$/); // Any number of alerts - cy.get(HOST_RISK_HEADER_COLIMN).contains('host.risk.calculated_level'); - cy.get(USER_RISK_HEADER_COLIMN).contains('user.risk.calculated_level'); + cy.get(HOST_RISK_HEADER_COLUMN).contains('host.risk.calculated_level'); + cy.get(USER_RISK_HEADER_COLUMN).contains('user.risk.calculated_level'); scrollAlertTableColumnIntoView(HOST_RISK_COLUMN); cy.get(HOST_RISK_COLUMN).contains('Critical'); scrollAlertTableColumnIntoView(USER_RISK_COLUMN); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/attach_alert_to_case.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/attach_alert_to_case.cy.ts index caa560f13aead..f10681a516146 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/attach_alert_to_case.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/explore/cases/attach_alert_to_case.cy.ts @@ -16,7 +16,7 @@ import { login } from '../../../tasks/login'; import { visit } from '../../../tasks/navigation'; import { ALERTS_URL } from '../../../urls/navigation'; -import { ATTACH_ALERT_TO_CASE_BUTTON, ATTACH_TO_NEW_CASE_BUTTON } from '../../../screens/alerts'; +import { ATTACH_ALERT_TO_CASE_BUTTON, TIMELINE_CONTEXT_MENU_BTN } from '../../../screens/alerts'; import { LOADING_INDICATOR } from '../../../screens/security_header'; const loadDetectionsPage = (role: ROLES) => { @@ -41,15 +41,13 @@ describe('Alerts timeline', { tags: ['@ess'] }, () => { }); it('should not allow user with read only privileges to attach alerts to existing cases', () => { - // Disabled actions for read only users are hidden, so only open alert details button should show - expandFirstAlertActions(); - cy.get(ATTACH_ALERT_TO_CASE_BUTTON).should('not.exist'); + // Disabled actions for read only users are hidden, so the ... icon is not even shown + cy.get(TIMELINE_CONTEXT_MENU_BTN).should('not.exist'); }); it('should not allow user with read only privileges to attach alerts to a new case', () => { - // Disabled actions for read only users are hidden, so only open alert details button should show - expandFirstAlertActions(); - cy.get(ATTACH_TO_NEW_CASE_BUTTON).should('not.exist'); + // Disabled actions for read only users are hidden, so the ... icon is not even shown + cy.get(TIMELINE_CONTEXT_MENU_BTN).should('not.exist'); }); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/navigation.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/navigation.cy.ts deleted file mode 100644 index d61ba89fa90b2..0000000000000 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/navigation.cy.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { expandFirstAlert, waitForAlerts } from '../../../tasks/alerts'; -import { createRule } from '../../../tasks/api_calls/rules'; -import { cleanKibana } from '../../../tasks/common'; -import { login } from '../../../tasks/login'; -import { visit, visitWithTimeRange } from '../../../tasks/navigation'; - -import { getNewRule } from '../../../objects/rule'; - -import { ALERTS_URL } from '../../../urls/navigation'; -import { - OPEN_ALERT_DETAILS_PAGE_CONTEXT_MENU_BTN, - TIMELINE_CONTEXT_MENU_BTN, - ALERTS_REFRESH_BTN, -} from '../../../screens/alerts'; -import { PAGE_TITLE } from '../../../screens/common/page'; -import { OPEN_ALERT_DETAILS_PAGE } from '../../../screens/alerts_details'; - -// This is skipped as the details page POC will be removed in favor of the expanded alert flyout -// https://github.com/elastic/kibana/issues/154477 -describe.skip('Alert Details Page Navigation', { tags: ['@ess', '@serverless'] }, () => { - describe('navigating to alert details page', () => { - const rule = getNewRule(); - before(() => { - cleanKibana(); - login(); - createRule({ ...rule, rule_id: 'rule1' }); - }); - - describe('context menu', () => { - beforeEach(() => { - visit(ALERTS_URL); - waitForAlerts(); - }); - - it('should navigate to the details page from the alert context menu', () => { - // Sometimes the alerts are not loaded yet, so we need to refresh the page - cy.get(TIMELINE_CONTEXT_MENU_BTN).then(($btns) => { - if ($btns.length === 0) { - cy.get(ALERTS_REFRESH_BTN).click(); - } - }); - cy.get(TIMELINE_CONTEXT_MENU_BTN).first().click({ force: true }); - cy.get(OPEN_ALERT_DETAILS_PAGE_CONTEXT_MENU_BTN).click({ force: true }); - cy.get(PAGE_TITLE).should('contain.text', rule.name); - cy.url().should('include', '/summary'); - }); - }); - - describe('flyout', () => { - beforeEach(() => { - visitWithTimeRange(ALERTS_URL); - waitForAlerts(); - }); - - it('should navigate to the details page from the alert flyout', () => { - expandFirstAlert(); - cy.get(OPEN_ALERT_DETAILS_PAGE).click({ force: true }); - cy.get(PAGE_TITLE).should('contain.text', rule.name); - cy.url().should('include', '/summary'); - }); - }); - }); -}); diff --git a/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts b/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts index 86a1703959ea9..d0681a4348e06 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts @@ -33,8 +33,6 @@ export const ALERT_SEVERITY = '[data-test-subj="formatted-field-kibana.alert.sev export const ALERT_DATA_GRID = '[data-test-subj="euiDataGridBody"]'; -export const ALERTS = '[data-test-subj="events-viewer-panel"][data-test-subj="event"]'; - export const ALERTS_COUNT = '[data-test-subj="toolbar-alerts-count"]'; export const CLOSE_ALERT_BTN = '[data-test-subj="close-alert-status"]'; @@ -53,12 +51,6 @@ export const TAKE_ACTION_MENU = '[data-test-subj="takeActionPanelMenu"]'; export const CLOSE_FLYOUT = '[data-test-subj="euiFlyoutCloseButton"]'; -export const GROUP_BY_TOP_INPUT = '[data-test-subj="groupByTop"] [data-test-subj="comboBoxInput"]'; - -export const HOST_NAME = '[data-test-subj^=formatted-field][data-test-subj$=host\\.name]'; - -export const MANAGE_ALERT_DETECTION_RULES_BTN = '[data-test-subj="manage-alert-detection-rules"]'; - export const MARK_ALERT_ACKNOWLEDGED_BTN = '[data-test-subj="acknowledged-alert-status"]'; export const ALERTS_REFRESH_BTN = `${GLOBAL_FILTERS_CONTAINER} [data-test-subj="querySubmitButton"]`; @@ -69,19 +61,11 @@ export const OPEN_ALERT_BTN = '[data-test-subj="open-alert-status"]'; export const OPENED_ALERTS_FILTER_BTN = '[data-test-subj="openAlerts"]'; -export const OPEN_ALERT_DETAILS_PAGE_CONTEXT_MENU_BTN = - '[data-test-subj="open-alert-details-page-menu-item"]'; - export const COLUMN_HEADER = '[data-test-subj="dataGridHeader"]'; -export const TIMESTAMP_COLUMN = '[data-test-subj="dataGridHeaderCell-@timestamp"]'; -export const MESSAGE = '[data-test-subj="formatted-field-message"]'; -export const REASON = - '[data-test-subj="dataGridRowCell"][data-gridcell-column-id="kibana.alert.reason"]'; - -export const RISK_SCORE = '[data-test-subj^=formatted-field][data-test-subj$=risk_score]'; +export const TIMESTAMP_COLUMN = '[data-test-subj="dataGridHeaderCell-@timestamp"]'; -export const RULE_NAME = '[data-test-subj^=formatted-field][data-test-subj$=rule\\.name]'; +export const MESSAGE = '[data-test-subj="formatted-field-message"]'; export const SELECTED_ALERTS = '[data-test-subj="selectedShowBulkActionsButton"]'; @@ -93,28 +77,22 @@ export const OPEN_ANALYZER_BTN = '[data-test-subj="view-in-analyzer"]'; export const ANALYZER_NODE = '[data-test-subj="resolver:node"'; -export const SEVERITY = '[data-test-subj^=formatted-field][data-test-subj$=severity]'; - -export const SOURCE_IP = '[data-test-subj^=formatted-field][data-test-subj$=source\\.ip]'; - export const TAKE_ACTION_POPOVER_BTN = '[data-test-subj="selectedShowBulkActionsButton"]'; export const TIMELINE_CONTEXT_MENU_BTN = '[data-test-subj="timeline-context-menu-button"]'; -export const USER_NAME = '[data-test-subj^=formatted-field][data-test-subj$=user\\.name]'; - export const ATTACH_ALERT_TO_CASE_BUTTON = '[data-test-subj="add-to-existing-case-action"]'; export const ATTACH_TO_NEW_CASE_BUTTON = '[data-test-subj="add-to-new-case-action"]'; export const USER_COLUMN = '[data-gridcell-column-id="user.name"]'; -export const HOST_RISK_HEADER_COLIMN = +export const HOST_RISK_HEADER_COLUMN = '[data-test-subj="dataGridHeaderCell-host.risk.calculated_level"]'; export const HOST_RISK_COLUMN = '[data-gridcell-column-id="host.risk.calculated_level"]'; -export const USER_RISK_HEADER_COLIMN = +export const USER_RISK_HEADER_COLUMN = '[data-test-subj="dataGridHeaderCell-user.risk.calculated_level"]'; export const USER_RISK_COLUMN = '[data-gridcell-column-id="user.risk.calculated_level"]'; @@ -152,8 +130,6 @@ export const ACTIONS_EXPAND_BUTTON = '[data-test-subj="euiDataGridCellExpandButt export const SHOW_TOP_N_HEADER = '[data-test-subj="topN-container"] [data-test-subj="header-section-title"]'; -export const SHOW_TOP_N_CLOSE_BUTTON = '[data-test-subj="close"]'; - export const ALERTS_HISTOGRAM_LEGEND = '[data-test-subj="alerts-histogram-panel"] .echLegendItem__action'; diff --git a/x-pack/test/security_solution_cypress/cypress/screens/alerts_details.ts b/x-pack/test/security_solution_cypress/cypress/screens/alerts_details.ts index 9d57a2b502f33..70f43b35d1211 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/alerts_details.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/alerts_details.ts @@ -17,10 +17,6 @@ export const ENRICHMENT_QUERY_START_INPUT = '.start-picker'; export const ENRICHMENT_QUERY_END_INPUT = '.end-picker'; -export const FIELD = (value: string) => { - return `[data-test-subj="event-field-${value}"]`; -}; - export const FILTER_INPUT = '[data-test-subj="eventDetails"] .euiFieldSearch'; export const INDICATOR_MATCH_ENRICHMENT_SECTION = '[data-test-subj="threat-match-detected"]'; @@ -32,14 +28,8 @@ export const JSON_VIEW_TAB = '[data-test-subj="jsonViewTab"]'; export const JSON_TEXT = '[data-test-subj="jsonView"]'; -export const OVERVIEW_RISK_SCORE = '[data-test-subj="eventDetails"] [data-test-subj="riskScore"]'; - export const OVERVIEW_RULE = '[data-test-subj="eventDetails"] [data-test-subj="ruleName"]'; -export const OVERVIEW_RULE_TYPE = '[data-test-subj="event-field-kibana.alert.rule.type"]'; - -export const OVERVIEW_SEVERITY = '[data-test-subj="eventDetails"] [data-test-subj="severity"]'; - export const OVERVIEW_STATUS = '[data-test-subj="eventDetails"] [data-test-subj="alertStatus"]'; export const EVENT_DETAILS_ALERT_STATUS_POPOVER = @@ -69,8 +59,6 @@ export const THREAT_INTEL_TAB = '[data-test-subj="threatIntelTab"]'; export const UPDATE_ENRICHMENT_RANGE_BUTTON = '[data-test-subj="enrichment-button"]'; -export const OVERVIEW_TAB = '[data-test-subj="overviewTab"]'; - export const SUMMARY_VIEW_INVESTIGATE_IN_TIMELINE_BUTTON = `${SUMMARY_VIEW} [aria-label='Investigate in timeline']`; export const INSIGHTS_RELATED_ALERTS_BY_SESSION = `[data-test-subj='related-alerts-by-session']`; @@ -83,6 +71,4 @@ export const INSIGHTS_INVESTIGATE_ANCESTRY_ALERTS_IN_TIMELINE_BUTTON = `[data-te export const ENRICHED_DATA_ROW = `[data-test-subj='EnrichedDataRow']`; -export const OPEN_ALERT_DETAILS_PAGE = `[data-test-subj="open-alert-details-page"]`; - export const COPY_ALERT_FLYOUT_LINK = `[data-test-subj="copy-alert-flyout-link"]`; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts b/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts index 4950f2c65fab2..a81cc24ae9653 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/alerts.ts @@ -9,15 +9,12 @@ import { encode } from '@kbn/rison'; import { recurse } from 'cypress-recurse'; import { formatPageFilterSearchParam } from '@kbn/security-solution-plugin/common/utils/format_page_filter_search_param'; import type { FilterItemObj } from '@kbn/security-solution-plugin/public/common/components/filter_group/types'; -import { TOP_N_CONTAINER } from '../screens/network/flows'; import { ADD_EXCEPTION_BTN, ALERT_CHECKBOX, CLOSE_ALERT_BTN, CLOSE_SELECTED_ALERTS_BTN, EXPAND_ALERT_BTN, - GROUP_BY_TOP_INPUT, - MANAGE_ALERT_DETECTION_RULES_BTN, MARK_ALERT_ACKNOWLEDGED_BTN, OPEN_ALERT_BTN, SEND_ALERT_TO_TIMELINE_BTN, @@ -43,7 +40,6 @@ import { ALERT_COUNT_TABLE_COLUMN, SELECT_HISTOGRAM, CELL_FILTER_OUT_BUTTON, - SHOW_TOP_N_CLOSE_BUTTON, ALERTS_HISTOGRAM_LEGEND, LEGEND_ACTIONS, SESSION_VIEWER_BUTTON, @@ -239,10 +235,6 @@ export const goToClosedAlerts = () => { cy.get(TIMELINE_COLUMN_SPINNER).should('not.exist'); }; -export const goToManageAlertsDetectionRules = () => { - cy.get(MANAGE_ALERT_DETECTION_RULES_BTN).should('exist').click(); -}; - export const goToOpenedAlertsOnRuleDetailsPage = () => { cy.get(OPENED_ALERTS_FILTER_BTN).click({ force: true }); cy.get(REFRESH_BUTTON).should('not.have.attr', 'aria-label', 'Needs updating'); @@ -283,11 +275,6 @@ export const selectAlertsHistogram = () => { cy.get(SELECT_HISTOGRAM).click({ force: true }); }; -export const clearGroupByTopInput = () => { - cy.get(GROUP_BY_TOP_INPUT).focus(); - cy.get(GROUP_BY_TOP_INPUT).type('{backspace}'); -}; - export const goToAcknowledgedAlerts = () => { /* * below line commented because alertPageFiltersEnabled feature flag @@ -372,11 +359,6 @@ export const showTopNAlertProperty = (propertySelector: string, rowIndex: number clickExpandActions(propertySelector, rowIndex); cy.get(CELL_SHOW_TOP_FIELD_BUTTON).first().click({ force: true }); }; -export const closeTopNAlertProperty = () => { - cy.get(TOP_N_CONTAINER).then(() => { - cy.get(SHOW_TOP_N_CLOSE_BUTTON).click(); - }); -}; export const waitForAlerts = () => { /* @@ -477,11 +459,6 @@ export const openSessionViewerFromAlertTable = (rowIndex: number = 0) => { cy.get(SESSION_VIEWER_BUTTON).eq(rowIndex).click(); }; -export const openAlertTaggingContextMenu = () => { - cy.get(TIMELINE_CONTEXT_MENU_BTN).first().click(); - cy.get(ALERT_TAGGING_CONTEXT_MENU_ITEM).click(); -}; - export const openAlertTaggingBulkActionMenu = () => { cy.get(TAKE_ACTION_POPOVER_BTN).click(); cy.get(ALERT_TAGGING_CONTEXT_MENU_ITEM).click(); From 0993ce4db9f94cd0c561945a510a7fc022266a40 Mon Sep 17 00:00:00 2001 From: amyjtechwriter <61687663+amyjtechwriter@users.noreply.github.com> Date: Wed, 25 Oct 2023 14:50:27 +0100 Subject: [PATCH 23/24] [DOCS] 8.11.0 release notes targeting elastic:main (#169819) ## Summary Adds the release notes for 8.11.0, and incorporates the feedback from the original draft [PR](https://github.com/elastic/kibana/pull/168593). The merged in [PR](https://github.com/elastic/kibana/pull/168710) targeted the wrong branch, so no release notes are visible for 8.11.0. --- docs/CHANGELOG.asciidoc | 230 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 230 insertions(+) diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc index 8c66afcd0ef29..2b24ca538caa9 100644 --- a/docs/CHANGELOG.asciidoc +++ b/docs/CHANGELOG.asciidoc @@ -10,6 +10,7 @@ Review important information about the {kib} 8.x releases. +* <> * <> * <> * <> @@ -52,6 +53,235 @@ Review important information about the {kib} 8.x releases. * <> -- +[[release-notes-8.11.0]] +== {kib} 8.11.0 + + +For information about the {kib} 8.11.0 release, review the following information. + + +[float] +[[breaking-changes-8.11.0]] +=== Breaking changes + + +Breaking changes can prevent your application from optimal operation and performance. +Before you upgrade to 8.11.0, review the breaking changes, then mitigate the impact to your application. + + +[discrete] +[[breaking-167085]] +.Improve config output validation for default output. +[%collapsible] +==== +*Details* + +Improve config output validation to not allow to defining multiple default outputs in {kib} configuration. For more information, refer to ({kibana-pull}167085[#167085]). +==== +[discrete] +[[breaking-161806]] +.Convert filterQuery to KQL. +[%collapsible] +==== +*Details* + +Converts `filterQuery` to a KQL query string. For more information, refer to ({kibana-pull}161806[#161806]). +==== +[float] +[[deprecations-8.11.0]] +=== Deprecations + + +The following functionality is deprecated in 8.11.0, and will be removed in 9.0.0. +Deprecated functionality does not have an immediate impact on your application, but we strongly recommend +you make the necessary updates after you upgrade to 8.11.0. + + +[discrete] +[[deprecation-164651]] +.Updates to move from doc_root.vulnerability.package -> doc_root.package (ECS). +[%collapsible] +==== +*Details* + +This updates all instances of vulnerability.package to the ECS standard package fieldset. For more information, refer to ({kibana-pull}164651[#164651]). +==== +[float] +[[features-8.11.0]] +=== Features +{kib} 8.11.0 adds the following new and notable features. + + +Alerting:: +* Adds support for the new ES|QL language for {es} query rules ({kibana-pull}165973[#165973]). +* Elasticsearch query rule can select multiple group-by terms ({kibana-pull}166146[#166146]). +* Adds a Log tab to the Observability Rules page ({kibana-pull}165115[#165115]). +APM:: +* Adds bulk action to untrack selected alerts ({kibana-pull}167579[#167579]). +* Introduce custom dashboards tab in service overview ({kibana-pull}166789[#166789]). +* Adds service profiling Top 10 Functions ({kibana-pull}166226[#166226]). +* Adds service profiling flamegraph ({kibana-pull}165360[#165360]). +Cases:: +* Adds custom fields in Cases ({kibana-pull}167016[#167016]). +Dashboard:: +* Copy panel refactor ({kibana-pull}166991[#166991]). +* Make links panel available under technical preview ({kibana-pull}166896[#166896]). +* Store view mode in local storage ({kibana-pull}166523[#166523]). +* Adds a read only state for Managed Dashboards ({kibana-pull}166204[#166204]). +Discover:: +* Adds resize support to the Discover field list sidebar ({kibana-pull}167066[#167066]). +Elastic Security:: +For the Elastic Security 8.11.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. +Enterprise Search:: +For the Elastic Enterprise Search 8.11.0 release information, refer to {enterprise-search-ref}/changelog.html[_Elastic Enterprise Search Documentation Release notes_]. +Fleet:: +* Set env variable `ELASTIC_NETINFO:false` in {kib} ({kibana-pull}166156[#166156]). +* Added restart upgrade action ({kibana-pull}166154[#166154]). +* Adds ability to set a proxy for agent binary source ({kibana-pull}164168[#164168]). +* Adds ability to set a proxy for agent download source ({kibana-pull}164078[#164078]). +Lens & Visualizations:: +* Adds color mapping for categorical dimensions in *Lens* available under technical preview ({kibana-pull}162389[#162389]). +* Inline editing of **Lens** panels on a dashboard or canvas ({kibana-pull}166169[#166169]). +* Individual annotation editing from library ({kibana-pull}163346[#163346]). +Logs:: +* Convert log explorer profile into standalone app available under technical preview ({kibana-pull}164493[#164493]). +Machine Learning:: +* Adds support for the ELSER v2 download in the Trained Models UI ({kibana-pull}167407[#167407]). +* Adds data drift detection workflow from Trained Models to Data comparison view ({kibana-pull}162853[#162853]). +Management:: +* Supports for viewing and editing data retention per data stream in Index Management is available under technical preview ({kibana-pull}167006[#167006]). +* Supports for viewing and editing data retention per data stream in Index Management is available under technical preview ({kibana-pull}167006[#167006]). +* Index details can now be viewed on a new index details page in Index Management ({kibana-pull}165705[#165705]). +* Supports for managing, executing, and deleting enrich policies in Index Management ({kibana-pull}164080[#164080]). +Platform:: +* ES|QL, a new query language, is available under technical preview in Discover and Dashboards ({kibana-pull}146971[#146971]). +Querying & Filtering:: +* Saved queries can now be shared between multiple spaces ({kibana-pull}163436[#163436]). +Uptime:: +* Adds a document viewer to the summary pings table ({kibana-pull}163926[#163926]). + + +For more information about the features introduced in 8.11.0, refer to <>. + + +[[enhancements-and-bug-fixes-v8.11.0]] +=== Enhancements and bug fixes + + +For detailed information about the 8.11.0 release, review the enhancements and bug fixes. + + +[float] +[[enhancement-v8.11.0]] +=== Enhancements +APM:: +* Changed mobile badge from 'technical preview' to 'beta' ({kibana-pull}167543[#167543]). +* New Profiling ES Flamegraph API ({kibana-pull}167477[#167477]). +* Adds Universal Profiling to O11y overview and Setup guide ({kibana-pull}165092[#165092]). +* Mark disabled alerts as Untracked in both Stack Management and o11y ({kibana-pull}164788[#164788]). +* Adds time range to event metadata API ({kibana-pull}167132[#167132]). +* New settings to control CO2 calculation ({kibana-pull}166637[#166637]). +* Adds permissions for "input-only" package ({kibana-pull}166234[#166234]). +* Adds selecting the consumer based on the authorized consumers when a user is creating an ES Query threshold rule ({kibana-pull}166032[#166032]). +* Migrate Ace based `EuiCodeEditor` to Monaco based code editor ({kibana-pull}165951[#165951]). +* Mobile UI crash widget added ({kibana-pull}163527[#163527]). +Cases:: +* Show a warning message to inform user that navigating after the 10Kth case is not possible ({kibana-pull}164323[#164323]). +Dashboard:: +* Focus on a single panel while disabling all other panels ({kibana-pull}165417[#165417]). +* Adds filter details to panel settings ({kibana-pull}162913[#162913]). +* Adds support for date fields in the options list controls ({kibana-pull}164362[#164362]). +Discover:: +* Redesign for the grid, panels and sidebar ({kibana-pull}165866[#165866]). +* Set data table row height to auto-fit by default ({kibana-pull}164218[#164218]). +* Allow fetching more documents on Discover page ({kibana-pull}163784[#163784]). +Elastic Security:: +For the Elastic Security 8.11.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. +Enterprise Search:: +For the Elastic Enterprise Search 8.11.0 release information, refer to {enterprise-search-ref}/changelog.html[_Elastic Enterprise Search Documentation Release notes_]. +Fleet:: +* Adds sidebar navigation showing headings extracted from the readme ({kibana-pull}167216[#167216]). +Inspector:: +* Clusters tab added under Inspector ({kibana-pull}166025[#166025]). +* Open incomplete response warning in Inspector ({kibana-pull}167205[#167205]). +Lens & Visualizations:: +* Other bucket defaults to false for top values greater than equal 1000 in *Lens* ({kibana-pull}167141[#167141]). +* Adds support for decimals in percentiles in *Lens* ({kibana-pull}165703[#165703]). +Machine Learning:: +* Updates ELSER version for Elastic Assistant ({kibana-pull}167522[#167522]). +* Retains `created_by` setting when exporting anomaly detection jobs ({kibana-pull}167319[#167319]). +* Improves the wording of awaiting ML nodes messages ({kibana-pull}167306[#167306]). +* Adds `created_by` job property for the advanced wizard ({kibana-pull}167021[#167021]). +* Trained model testing: only show indices with supported fields ({kibana-pull}166490[#166490]). +* Alerts as data integration for Anomaly Detection rule type ({kibana-pull}166349[#166349]). +* Data Frame Analytics Trained models: adds the ability to reindex after pipeline creation ({kibana-pull}166312[#166312]). +* Adds Create a data view button to index or saved search selector in ML pages and Transforms management ({kibana-pull}166668[#166668]). +* Improvements to UX of adding ML embeddables to a dashboard ({kibana-pull}165714[#165714]). +* AIOps: Supports text fields in log rate analysis ({kibana-pull}165124[#165124]). +* Data Frame Analytics creation wizard: adds ability to add custom URLs to jobs ({kibana-pull}164520[#164520]). +Management:: +* Adds Create a data view button to index or saved search selector in ML pages and Transforms management ({kibana-pull}166668[#166668]). +* Improve loading behavior of Transforms list if stats request is slow or is not available ({kibana-pull}166320[#166320]). +* Adds support for PATCH requests in Console ({kibana-pull}165634[#165634]). +* Improves autocomplete to suggest knn in search query ({kibana-pull}165531[#165531]). +* Improves display for long descriptions in Transforms ({kibana-pull}165149[#165149]). +* Improve transform list reloading behavior ({kibana-pull}164296[#164296]). +Maps:: +* Allow by value styling for EMS boundary fields ({kibana-pull}166306[#166306]). +* Adds support for `geo_shape` fields as the entity geospatial field when creating tracking containment alerts ({kibana-pull}164100[#164100]). +Observability:: +* ES|QL query generation ({kibana-pull}166041[#166041]). +Querying & Filtering:: +* New "Saved Query Management" privilege to allow saving queries across Kibana ({kibana-pull}166937[#166937]). +* Improvements to the filter builder inputs for long fields ({kibana-pull}166024[#166024]). +Uptime:: +* Added ability to hide public locations ({kibana-pull}164863[#164863]). + + +[float] +[[fixes-v8.11.0]] +=== Bug Fixes +Alerting:: +* Improve error handling in ES Index action response ({kibana-pull}164841[#164841]). +* Bring back toggle column on alert table ({kibana-pull}168158[#168158]). +* Fixes Errors rules link on observability alert page ({kibana-pull}167027[#167027]). +* Enable read-only users to access rules ({kibana-pull}167003[#167003]). +* Fixes rule snooze toast copy ({kibana-pull}166030[#166030]). +APM:: +* Ensure APM data view is available across all spaces ({kibana-pull}167704[#167704]). +* Adds an environment param to the service metadata details endpoint ({kibana-pull}167173[#167173]). +* Fixes set up process ({kibana-pull}167067[#167067]). +Dashboard:: +* Generate new panel IDs on Dashboard clone ({kibana-pull}166299[#166299]). +Elastic Security:: +For the Elastic Security 8.11.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. +Enterprise Search:: +For the Elastic Enterprise Search 8.11.0 release information, refer to {enterprise-search-ref}/changelog.html[_Elastic Enterprise Search Documentation Release notes_]. +Fleet:: +* Vastly improve performance of Fleet final pipeline's date formatting logic for `event.ingested` ({kibana-pull}167318[#167318]). +Lens & Visualizations:: +* Fixes heatmap color assignment on single value scenario in *Lens* ({kibana-pull}167995[#167995]). +* Fixes mosaic with 2 axis coloring in *Lens* ({kibana-pull}167035[#167035]). +* Show icons/titles instead of previews in suggestions panel in *Lens* ({kibana-pull}166808[#166808]). +* Consider root level filters buckets correctly when building other terms bucket ({kibana-pull}165656[#165656]). +* Prevent user to use decimals for custom Percentile rank function in Top values in *Lens* ({kibana-pull}165616[#165616]). +* Fixes the Graph application settings tab when in dark mode ({kibana-pull}165614[#165614]). +* Fixes Visualize List search and CRUD operations via content management ({kibana-pull}165485[#165485]). +Logs:: +* Use correct ML API to query blocking tasks ({kibana-pull}167779[#167779]). +Machine Learning:: +* AIOps: Fixes log pattern analysis sparklines and chart ({kibana-pull}168337[#168337]). +* AIOps: Fixes Data View runtime fields support in the Change point detection UI ({kibana-pull}168249[#168249]). +* Fixes anomaly charts when partition field contains an empty string ({kibana-pull}168102[#168102]). +* Data Frame analytics outlier detection results: ensure scatterplot matrix adheres to bounding box ({kibana-pull}167941[#167941]). +* Fixes Anomaly charts embeddable fails to load if partition value is empty string ({kibana-pull}167827[#167827]). +Management:: +* Fixes `isErrorResponse` when cluster details are provided ({kibana-pull}166667[#166667]). +* Fixes autocomplete not to be prompted between triple quotes ({kibana-pull}165535[#165535]). +* Fixes autocomplete on only 1 letter typed in Console's request editor ({kibana-pull}164707[#164707]). +* Fixing duration field formatter showing 0 seconds instead of "few seconds" ({kibana-pull}164659[#164659]). +* Fixes a bug that autocomplete does not work right after a comma ({kibana-pull}164608[#164608]). +* Fixes unnecessary autocompletes on HTTP methods ({kibana-pull}163233[#163233]). +Presentation:: +* Fixes ES query rule boundary field changed when editing the rule ({kibana-pull}165155[#165155]). + [[release-notes-8.10.4]] == {kib} 8.10.4 From 13d17925440a87a46db8490414e3c6acd6edccf7 Mon Sep 17 00:00:00 2001 From: Dzmitry Lemechko Date: Wed, 25 Oct 2023 16:01:46 +0200 Subject: [PATCH 24/24] [ftr] read username from config (#169755) ## Summary Similar to #169639 The tests fail on MKI because username is hardcoded to `elastic_serverless`. Reading value from FTR config should fix it. --- .../functional/test_suites/common/reporting/management.ts | 6 ++++-- .../functional/test_suites/observability/cases/view_case.ts | 4 ++-- .../functional/test_suites/security/ftr/cases/view_case.ts | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/x-pack/test_serverless/functional/test_suites/common/reporting/management.ts b/x-pack/test_serverless/functional/test_suites/common/reporting/management.ts index 70f0037cd17c3..c36000889d481 100644 --- a/x-pack/test_serverless/functional/test_suites/common/reporting/management.ts +++ b/x-pack/test_serverless/functional/test_suites/common/reporting/management.ts @@ -17,6 +17,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const retry = getService('retry'); const PageObjects = getPageObjects(['common', 'svlCommonPage', 'header']); const reportingAPI = getService('svlReportingApi'); + const config = getService('config'); const navigateToReportingManagement = async () => { log.debug(`navigating to reporting management app`); @@ -47,8 +48,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { ], }; - const TEST_USERNAME = 'elastic_serverless'; - const TEST_PASSWORD = 'changeme'; + // Kibana CI and MKI use different users + const TEST_USERNAME = config.get('servers.kibana.username'); + const TEST_PASSWORD = config.get('servers.kibana.password'); before('initialize saved object archive', async () => { // add test saved search object diff --git a/x-pack/test_serverless/functional/test_suites/observability/cases/view_case.ts b/x-pack/test_serverless/functional/test_suites/observability/cases/view_case.ts index c60b7a8ed103c..0e60fa0125234 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cases/view_case.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cases/view_case.ts @@ -28,7 +28,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { const cases = getService('cases'); const svlCases = getService('svlCases'); const find = getService('find'); - + const config = getService('config'); const retry = getService('retry'); const comboBox = getService('comboBox'); const svlCommonNavigation = getPageObject('svlCommonNavigation'); @@ -453,7 +453,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { const reporterText = await reporter.getVisibleText(); - expect(reporterText).to.be('elastic_serverless'); + expect(reporterText).to.be(config.get('servers.kibana.username')); }); }); diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/view_case.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/view_case.ts index c3d8285857634..d9531a4529ee5 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/cases/view_case.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/cases/view_case.ts @@ -28,7 +28,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { const cases = getService('cases'); const svlCases = getService('svlCases'); const find = getService('find'); - + const config = getService('config'); const retry = getService('retry'); const comboBox = getService('comboBox'); const svlCommonNavigation = getPageObject('svlCommonNavigation'); @@ -452,7 +452,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { const reporterText = await reporter.getVisibleText(); - expect(reporterText).to.be('elastic_serverless'); + expect(reporterText).to.be(config.get('servers.kibana.username')); }); });