From 1f79ff4f1cf3b24b7aee453c61b2dd33f0f601ac Mon Sep 17 00:00:00 2001 From: Thom Heymann <190132+thomheymann@users.noreply.github.com> Date: Fri, 11 Aug 2023 12:14:45 +0100 Subject: [PATCH 01/11] Document interactive setup (#163619) ## Summary Add instruction for developer on how to run interactive setup locally --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- docs/developer/plugin-list.asciidoc | 2 +- src/plugins/interactive_setup/README.md | 44 +++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 7cad590c7f52c..923496d123682 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -215,7 +215,7 @@ in Kibana, e.g. visualizations. It has the form of a flyout panel. |{kib-repo}blob/{branch}/src/plugins/interactive_setup/README.md[interactiveSetup] -|The plugin provides UI and APIs for the interactive setup mode. +|This plugin provides UI and APIs for interactive setup mode a.k.a "enrollment flow". |{kib-repo}blob/{branch}/src/plugins/kibana_overview/README.md[kibanaOverview] diff --git a/src/plugins/interactive_setup/README.md b/src/plugins/interactive_setup/README.md index 9fd43eb0445b6..1c4d9d56771c7 100644 --- a/src/plugins/interactive_setup/README.md +++ b/src/plugins/interactive_setup/README.md @@ -1,3 +1,43 @@ -# `interactiveSetup` plugin +# Interactive Setup Plugin -The plugin provides UI and APIs for the interactive setup mode. +This plugin provides UI and APIs for interactive setup mode a.k.a "enrollment flow". + +## How to run interactive setup locally + +Kibana does not start interactive setup mode if it detects that an Elasticsearch connection has already been configured. This is always the case when running `yarn start` so in order to trigger interactive setup we need to run Elasticsearch manually and pass a special command line flag to the Kibana start command. + +1. Start a clean copy of Elasticsearch from inside your Kibana working directory: + + ``` + cd /.es/cache + tar -xzf elasticsearch-8.10.0-SNAPSHOT-darwin-aarch64.tar.gz + cd ./elasticsearch-8.10.0-SNAPSHOT + ./bin/elasticsearch + ``` + + You should see the enrollment token get logged: + + ``` + Elasticsearch security features have been automatically configured! + + • Copy the following enrollment token and paste it into Kibana in your browser: + eyJ2ZXIiOiI4LjEwLjAiLCJhZHIiOlsiMTkyLjE2OC4xLjIxMTo5MjAwIl0sImZnciI6ImZiYWZjOTgxODM0MjAwNzQ0M2ZhMzNmNTQ2N2QzMTM0YTk1NzU2NjEwOTcxNmJmMjdlYWViZWNlYTE3NmM3MTkiLCJrZXkiOiJxdVVQallrQkhOTkFxOVBqNEY0ejpZUkVMaFR5ZlNlZTZGZW9PQVZwaDRnIn0= + ``` + +2. Start Kibana without dev credentials and config: + + ``` + yarn start --no-dev-credentials --no-dev-config + ``` + + You should see the magic link get logged: + + ``` + i Kibana has not been configured. + + Go to http://localhost:5601/tcu/?code=651822 to get started. + ``` + +3. Open the link and copy the enrollment token from Elasticsearch when prompted to complete setup. + +Note: If you want to go through the enrollment flow again you will need to clear all Elasticsearch settings (`elasticsearch.*`) from your `kibana.yml` file and trash your Elasticsearch folder before starting with Step 1. From d5c4f461b5e871e873bbc97cd011b178826f130a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Fri, 11 Aug 2023 13:19:55 +0200 Subject: [PATCH 02/11] [Flaky Test #111821] Mock `moment` to avoid midnight TZ issues (#163157) --- .../integration_tests/daily_rollups.test.ts | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/rollups/integration_tests/daily_rollups.test.ts b/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/rollups/integration_tests/daily_rollups.test.ts index 32ce4ed198038..6014c3b3acf4c 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/rollups/integration_tests/daily_rollups.test.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/rollups/integration_tests/daily_rollups.test.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import moment, { type MomentInput } from 'moment'; import type { Logger, ISavedObjectsRepository, SavedObject } from '@kbn/core/server'; import { type TestElasticsearchUtils, @@ -13,7 +14,7 @@ import { createTestServers, createRootWithCorePlugins, } from '@kbn/core-test-helpers-kbn-server'; -import { rollDailyData } from '../daily'; + import { metricsServiceMock } from '@kbn/core/server/mocks'; import { @@ -21,10 +22,21 @@ import { serializeSavedObjectId, EventLoopDelaysDaily, } from '../../saved_objects'; -import moment from 'moment'; +import { rollDailyData } from '../daily'; const eventLoopDelaysMonitor = metricsServiceMock.createEventLoopDelaysMonitor(); +/* + * Mocking the constructor of moment, so we can control the time of the day. + * This is to avoid flaky tests when starting to run before midnight and ending the test after midnight + * because the logic might remove one extra document since we moved to the next day. + */ +jest.doMock('moment', () => { + const mockedMoment = (date?: MomentInput) => moment(date ?? '2023-07-04T10:00:00.000Z'); + Object.setPrototypeOf(mockedMoment, moment); // inherit the prototype of `moment` so it has all the same methods. + return mockedMoment; +}); + function createRawObject(date: moment.MomentInput): SavedObject { const pid = Math.round(Math.random() * 10000); const instanceUuid = 'mock_instance'; @@ -45,8 +57,8 @@ function createRawObject(date: moment.MomentInput): SavedObject { +describe(`daily rollups integration test`, () => { let esServer: TestElasticsearchUtils; let root: TestKibanaUtils['root']; let internalRepository: ISavedObjectsRepository; @@ -82,15 +93,6 @@ describe.skip(`daily rollups integration test`, () => { logger = root.logger.get('test daily rollups'); internalRepository = start.savedObjects.createInternalRepository([SAVED_OBJECTS_DAILY_TYPE]); - // If we are less than 1 second away from midnight, let's wait 1 second before creating the docs. - // Otherwise, we may receive 1 document less than the expected ones. - if (moment().endOf('day').diff(moment(), 's', true) < 1) { - logger.info( - 'Delaying the creation of the docs 1s, just in case we create them before midnight and run the tests on the following day.' - ); - await new Promise((resolve) => setTimeout(resolve, 1000)); - } - // Create the docs now const rawDailyDocs = createRawEventLoopDelaysDailyDocs(); rawEventLoopDelaysDaily = rawDailyDocs.rawEventLoopDelaysDaily; From 26e5c20238376bbff6136cb9a20b83c004046bcd Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Fri, 11 Aug 2023 14:05:45 +0200 Subject: [PATCH 03/11] [Security Solution] Re-enable fixed rule snoozing Cypress test (#160037) **Addresses:** https://github.com/elastic/kibana/issues/159349 ## Summary This PR fixes and re-enables rule snoozing Cypress test `Rule editing page / actions tab - adds an action to a snoozed rule`. ## Details Basically the test wasn't flaky it just failed on the `main` branch and was skipped. The cause lays in interlacing [Rule snooze tests PR](https://github.com/elastic/kibana/pull/158195) with [Rule Editing page refactoring PR](https://github.com/elastic/kibana/pull/157749). Two PRs were merged independently to the `main` branch (while the tests PR was merged the last) so combining them lead to a test failure while each one separately didn't cause any problems. ### The root cause The root cause is in a combination of factors. [useForm](https://github.com/elastic/kibana/blob/main/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts#L33) hook has a flaw it can't update its state when new `defaultValue` comes in. The same issue also in [useField](https://github.com/elastic/kibana/blob/main/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts#L77) hook. Rule Editing page fetched a fresh rule's data every time it's rendered via [useRule](https://github.com/elastic/kibana/blob/main/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_editing/index.tsx#L581). As `useRule` hook is based on `React Query` it returns stale data during the first render while firing an HTTP request for the fresh data. Obviously the problem happens if the stale data is passed to `useForm` hook as `defaultValue` and when fresh data is fetched and passed again to `useForm` hook the latter just ignores it. Staying longer on the Rule Details page helps to avoid the problem as fetched rule's data is cached and returned as stale data on the Rule Editing page after transition. As stale and fresh data are the same the test would pass. Anyway this behaviour can be reproduced in prod with a slow internet connection. ### What was done to fix the problem? Functionality has been added to update the cached rule's data (React Query cache) upon mutation successful update rule mutation. The mutation if fired by pressing "Save changes" button on the Rule Editing page. It is possible as update rule endpoint returns an updated rule in its response. Along the way it turned out update rule endpoint's and read rule endpoint's responses weren't properly typed so it lead to types mismatch. To fix it `updateRule`'s and `UseMutationOptions`'s return types were changed to `Rule` instead of `RuleResponse`. ### Misc Along the way it turned out `cy.get(RULE_INDICES).should('be.visible');` isn't required anymore to access rule actions form. --- .../rule_actions/snoozing/rule_snoozing.cy.ts | 6 +---- .../rule_management/api/api.ts | 14 +++++++++-- .../api/hooks/use_fetch_rule_by_id_query.ts | 24 +++++++++++++++++++ .../api/hooks/use_update_rule_mutation.ts | 23 +++++++++--------- 4 files changed, 49 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_response/rule_management/rule_actions/snoozing/rule_snoozing.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_response/rule_management/rule_actions/snoozing/rule_snoozing.cy.ts index 08bddea26288a..b3939ff3c7b27 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_response/rule_management/rule_actions/snoozing/rule_snoozing.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_response/rule_management/rule_actions/snoozing/rule_snoozing.cy.ts @@ -35,7 +35,6 @@ import { duplicateFirstRule, importRules } from '../../../../../tasks/alerts_det import { goToActionsStepTab } from '../../../../../tasks/create_new_rule'; import { goToRuleEditSettings } from '../../../../../tasks/rule_details'; import { actionFormSelector } from '../../../../../screens/common/rule_actions'; -import { RULE_INDICES } from '../../../../../screens/create_new_rule'; import { addEmailConnectorAndRuleAction } from '../../../../../tasks/common/rule_actions'; import { saveEditedRule } from '../../../../../tasks/edit_rule'; import { DISABLED_SNOOZE_BADGE } from '../../../../../screens/rule_snoozing'; @@ -169,8 +168,7 @@ describe('rule snoozing', () => { }); }); - // SKIPPED: https://github.com/elastic/kibana/issues/159349 - describe.skip('Rule editing page / actions tab', () => { + describe('Rule editing page / actions tab', () => { beforeEach(() => { deleteConnectors(); }); @@ -178,8 +176,6 @@ describe('rule snoozing', () => { it('adds an action to a snoozed rule', () => { createSnoozedRule(getNewRule({ name: 'Snoozed rule' })).then(({ body: rule }) => { visitWithoutDateRange(ruleEditUrl(rule.id)); - // Wait for rule data being loaded - cy.get(RULE_INDICES).should('be.visible'); goToActionsStepTab(); addEmailConnectorAndRuleAction('abc@example.com', 'Test action'); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts index d62114881adad..27ce67f23a978 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts @@ -104,10 +104,15 @@ export const createRule = async ({ rule, signal }: CreateRulesProps): Promise An updated rule + * + * In fact this function should return Promise but it'd require massive refactoring. + * It should be addressed as a part of OpenAPI schema adoption. + * * @throws An error if response is not OK */ -export const updateRule = async ({ rule, signal }: UpdateRulesProps): Promise => - KibanaServices.get().http.fetch(DETECTION_ENGINE_RULES_URL, { +export const updateRule = async ({ rule, signal }: UpdateRulesProps): Promise => + KibanaServices.get().http.fetch(DETECTION_ENGINE_RULES_URL, { method: 'PUT', body: JSON.stringify(rule), signal, @@ -198,6 +203,11 @@ export const fetchRules = async ({ * @param id Rule ID's (not rule_id) * @param signal to cancel request * + * @returns Promise + * + * In fact this function should return Promise but it'd require massive refactoring. + * It should be addressed as a part of OpenAPI schema adoption. + * * @throws An error if response is not OK */ export const fetchRuleById = async ({ id, signal }: FetchRuleProps): Promise => diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_rule_by_id_query.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_rule_by_id_query.ts index 4c8ee37e3e211..197b97effa1f8 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_rule_by_id_query.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_rule_by_id_query.ts @@ -59,3 +59,27 @@ export const useInvalidateFetchRuleByIdQuery = () => { }); }, [queryClient]); }; + +/** + * We should use this hook to update the rules cache when modifying a rule. + * Use it with the new rule data after operations like rule edit. + * + * @returns A rules cache update callback + */ +export const useUpdateRuleByIdCache = () => { + const queryClient = useQueryClient(); + /** + * Use this method to update rules data cached by react-query. + * It is useful when we receive new rules back from a mutation query (bulk edit, etc.); + * we can merge those rules with the existing cache to avoid an extra roundtrip to re-fetch updated rules. + */ + return useCallback( + (updatedRuleResponse: Rule) => { + queryClient.setQueryData['data']>( + [...FIND_ONE_RULE_QUERY_KEY, updatedRuleResponse.id], + transformInput(updatedRuleResponse) + ); + }, + [queryClient] + ); +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_update_rule_mutation.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_update_rule_mutation.ts index 53103287046be..932c50ff2a46f 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_update_rule_mutation.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_update_rule_mutation.ts @@ -6,42 +6,43 @@ */ import type { UseMutationOptions } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query'; -import type { - RuleResponse, - RuleUpdateProps, -} from '../../../../../common/api/detection_engine/model/rule_schema'; +import type { RuleUpdateProps } from '../../../../../common/api/detection_engine/model/rule_schema'; import { transformOutput } from '../../../../detections/containers/detection_engine/rules/transforms'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { updateRule } from '../api'; import { useInvalidateFindRulesQuery } from './use_find_rules_query'; -import { useInvalidateFetchRuleByIdQuery } from './use_fetch_rule_by_id_query'; +import { useUpdateRuleByIdCache } from './use_fetch_rule_by_id_query'; import { useInvalidateFetchRuleManagementFiltersQuery } from './use_fetch_rule_management_filters_query'; import { useInvalidateFetchCoverageOverviewQuery } from './use_fetch_coverage_overview'; +import type { Rule } from '../../logic/types'; export const UPDATE_RULE_MUTATION_KEY = ['PUT', DETECTION_ENGINE_RULES_URL]; export const useUpdateRuleMutation = ( - options?: UseMutationOptions + options?: UseMutationOptions ) => { const invalidateFindRulesQuery = useInvalidateFindRulesQuery(); const invalidateFetchRuleManagementFilters = useInvalidateFetchRuleManagementFiltersQuery(); - const invalidateFetchRuleByIdQuery = useInvalidateFetchRuleByIdQuery(); const invalidateFetchCoverageOverviewQuery = useInvalidateFetchCoverageOverviewQuery(); + const updateRuleCache = useUpdateRuleByIdCache(); - return useMutation( + return useMutation( (rule: RuleUpdateProps) => updateRule({ rule: transformOutput(rule) }), { ...options, mutationKey: UPDATE_RULE_MUTATION_KEY, onSettled: (...args) => { invalidateFindRulesQuery(); - invalidateFetchRuleByIdQuery(); invalidateFetchRuleManagementFilters(); invalidateFetchCoverageOverviewQuery(); - if (options?.onSettled) { - options.onSettled(...args); + const [response] = args; + + if (response) { + updateRuleCache(response); } + + options?.onSettled?.(...args); }, } ); From 589ef228fb9c2525a49d6abc5db64eb98b2c381e Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Fri, 11 Aug 2023 14:07:35 +0200 Subject: [PATCH 04/11] [Security Solution] Unskip rules table auto-refresh Cypress tests (#163451) **Addresses:** https://github.com/elastic/kibana/issues/154694 ## Summary This PR unskips rules table auto-refresh tests. ## Details Rules table auto-refresh Cypress tests were outdated so it was hard to determine the original flakiness reason. To make it working the following has been done - `mockGlobalClock()` moved above `visit()` to mock the time before visiting the page. It didn't look like working so the test had to wait for 60 seconds (our refresh interval) - removed unnecessary waiting `waitForRulesTableToBeLoaded()` as the test should work without it - removed `setRowsPerPageTo(5)` as the test doesn't look related to the page size - `AutoRefreshButtonComponent ` was refactored in attempt to fix popover closing related flakiness but it doesn't seem to help but the refactoring looks improving readability so leaving as a part of this PR - auto-refresh popover's behavior has been changed. The trigger button gets disabled if there is a rule selected. Clicking the disabled button won't make the auto-refresh popover to appear so this part was changed. ### Encountered issues - `EuiPopover` should close by clicking the trigger button, clicking outside or pressing `Esc` button. The latter two cause flakiness for some reason. I spent quite some time to investigate the reason but without success. It looks like there is a race condition related to [EuiFocusTrap](https://github.com/elastic/eui/blob/main/src/components/focus_trap/focus_trap.tsx) but it doesn't look to be an issue outside Cypress. - `waitForRulesTableToBeLoaded()` basically does nothing after changes in the rules table. Looking at tis contents ```ts export const waitForRulesTableToBeLoaded = () => { // Wait up to 5 minutes for the rules to load as in CI containers this can be very slow cy.get(RULES_TABLE_INITIAL_LOADING_INDICATOR, { timeout: 300000 }).should('not.exist'); }; ``` reveals that `RULES_TABLE_INITIAL_LOADING_INDICATOR` defined as `[data-test-subj="initialLoadingPanelAllRulesTable"]` doesn't exist anymore in our codebase so this function instantly returns. The selector will be fixed in a separate PR. - invoking `mockGlobalClock()` during the test leads to appearing a lot of `Executing a cancelled action` error messages. ### Flaky test runner [50 runs succeeded](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/2842) --- .../rules_table_auto_refresh.cy.ts | 76 ++++++++----------- .../cypress/screens/alerts_detection_rules.ts | 2 +- .../cypress/tasks/alerts_detection_rules.ts | 42 ++++++---- .../auto_refresh_button.tsx | 56 +++++++------- 4 files changed, 88 insertions(+), 88 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_response/rule_management/rules_table/rules_table_auto_refresh.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_response/rule_management/rules_table/rules_table_auto_refresh.cy.ts index 7304972a65790..bd3363572915d 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_response/rule_management/rules_table/rules_table_auto_refresh.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_response/rule_management/rules_table/rules_table_auto_refresh.cy.ts @@ -8,20 +8,19 @@ import { RULE_CHECKBOX, REFRESH_RULES_STATUS, - REFRESH_SETTINGS_SWITCH, - REFRESH_SETTINGS_SELECTION_NOTE, + RULES_TABLE_AUTOREFRESH_INDICATOR, + RULES_MANAGEMENT_TABLE, } from '../../../../screens/alerts_detection_rules'; import { - checkAutoRefresh, - waitForRulesTableToBeLoaded, selectAllRules, - openRefreshSettingsPopover, clearAllRuleSelection, selectNumberOfRules, mockGlobalClock, disableAutoRefresh, - checkAutoRefreshIsDisabled, - checkAutoRefreshIsEnabled, + expectAutoRefreshIsDisabled, + expectAutoRefreshIsEnabled, + expectAutoRefreshIsDeactivated, + expectNumberOfRules, } from '../../../../tasks/alerts_detection_rules'; import { login, visit, visitWithoutDateRange } from '../../../../tasks/login'; @@ -29,16 +28,16 @@ import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../../../urls/navigation'; import { createRule } from '../../../../tasks/api_calls/rules'; import { cleanKibana } from '../../../../tasks/common'; import { getNewRule } from '../../../../objects/rule'; -import { setRowsPerPageTo } from '../../../../tasks/table_pagination'; const DEFAULT_RULE_REFRESH_INTERVAL_VALUE = 60000; +const NUM_OF_TEST_RULES = 6; -// TODO: See https://github.com/elastic/kibana/issues/154694 -describe.skip('Rules table: auto-refresh', () => { +describe('Rules table: auto-refresh', () => { before(() => { cleanKibana(); login(); - for (let i = 1; i < 7; i += 1) { + + for (let i = 1; i <= NUM_OF_TEST_RULES; ++i) { createRule(getNewRule({ name: `Test rule ${i}`, rule_id: `${i}` })); } }); @@ -48,31 +47,31 @@ describe.skip('Rules table: auto-refresh', () => { }); it('Auto refreshes rules', () => { + mockGlobalClock(); visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); - waitForRulesTableToBeLoaded(); - - // ensure rules have rendered. As there is no user interaction in this test, - // rules were not rendered before test completes - cy.get(RULE_CHECKBOX).should('have.length', 6); + expectNumberOfRules(RULES_MANAGEMENT_TABLE, NUM_OF_TEST_RULES); // // mock 1 minute passing to make sure refresh is conducted - mockGlobalClock(); - checkAutoRefresh(DEFAULT_RULE_REFRESH_INTERVAL_VALUE, 'be.visible'); + cy.get(RULES_TABLE_AUTOREFRESH_INDICATOR).should('not.exist'); + cy.tick(DEFAULT_RULE_REFRESH_INTERVAL_VALUE); + cy.get(RULES_TABLE_AUTOREFRESH_INDICATOR).should('be.visible'); cy.contains(REFRESH_RULES_STATUS, 'Updated now'); }); it('should prevent table from rules refetch if any rule selected', () => { + mockGlobalClock(); visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL); - waitForRulesTableToBeLoaded(); + expectNumberOfRules(RULES_MANAGEMENT_TABLE, NUM_OF_TEST_RULES); selectNumberOfRules(1); // mock 1 minute passing to make sure refresh is not conducted - mockGlobalClock(); - checkAutoRefresh(DEFAULT_RULE_REFRESH_INTERVAL_VALUE, 'not.exist'); + cy.get(RULES_TABLE_AUTOREFRESH_INDICATOR).should('not.exist'); + cy.tick(DEFAULT_RULE_REFRESH_INTERVAL_VALUE); + cy.get(RULES_TABLE_AUTOREFRESH_INDICATOR).should('not.exist'); // ensure rule is still selected cy.get(RULE_CHECKBOX).first().should('be.checked'); @@ -82,50 +81,37 @@ describe.skip('Rules table: auto-refresh', () => { it('should disable auto refresh when any rule selected and enable it after rules unselected', () => { visit(DETECTIONS_RULE_MANAGEMENT_URL); - waitForRulesTableToBeLoaded(); - setRowsPerPageTo(5); + + expectNumberOfRules(RULES_MANAGEMENT_TABLE, NUM_OF_TEST_RULES); // check refresh settings if it's enabled before selecting - openRefreshSettingsPopover(); - checkAutoRefreshIsEnabled(); + expectAutoRefreshIsEnabled(); selectAllRules(); - // auto refresh should be disabled after rules selected - openRefreshSettingsPopover(); - checkAutoRefreshIsDisabled(); - - // if any rule selected, refresh switch should be disabled and help note to users should displayed - cy.get(REFRESH_SETTINGS_SWITCH).should('be.disabled'); - cy.contains( - REFRESH_SETTINGS_SELECTION_NOTE, - 'Note: Refresh is disabled while there is an active selection.' - ); + // auto refresh should be deactivated (which means disabled without an ability to enable it) after rules selected + expectAutoRefreshIsDeactivated(); clearAllRuleSelection(); - // after all rules unselected, auto refresh should renew - openRefreshSettingsPopover(); - checkAutoRefreshIsEnabled(); + // after all rules unselected, auto refresh should be reset to its previous state + expectAutoRefreshIsEnabled(); }); it('should not enable auto refresh after rules were unselected if auto refresh was disabled', () => { visit(DETECTIONS_RULE_MANAGEMENT_URL); - waitForRulesTableToBeLoaded(); - setRowsPerPageTo(5); - openRefreshSettingsPopover(); + expectNumberOfRules(RULES_MANAGEMENT_TABLE, NUM_OF_TEST_RULES); + disableAutoRefresh(); selectAllRules(); - openRefreshSettingsPopover(); - checkAutoRefreshIsDisabled(); + expectAutoRefreshIsDeactivated(); clearAllRuleSelection(); // after all rules unselected, auto refresh should still be disabled - openRefreshSettingsPopover(); - checkAutoRefreshIsDisabled(); + expectAutoRefreshIsDisabled(); }); }); diff --git a/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts b/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts index 40d16dd088af8..ab5a2697d4be4 100644 --- a/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts +++ b/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts @@ -158,7 +158,7 @@ export const RULES_SELECTED_TAG = '.euiSelectableListItem[aria-checked="true"]'; export const SELECTED_RULES_NUMBER_LABEL = '[data-test-subj="selectedRules"]'; -export const REFRESH_SETTINGS_POPOVER = '[data-test-subj="refreshSettings-popover"]'; +export const AUTO_REFRESH_POPOVER_TRIGGER_BUTTON = '[data-test-subj="autoRefreshButton"]'; export const REFRESH_RULES_TABLE_BUTTON = '[data-test-subj="refreshRulesAction-linkIcon"]'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts b/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts index d73a67cd6c271..221c3d6a61501 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts @@ -12,7 +12,6 @@ import { DELETE_RULE_ACTION_BTN, RULES_SELECTED_TAG, RULES_TABLE_INITIAL_LOADING_INDICATOR, - RULES_TABLE_AUTOREFRESH_INDICATOR, RULE_CHECKBOX, RULE_NAME, RULE_SWITCH, @@ -37,7 +36,6 @@ import { RULES_TAGS_POPOVER_WRAPPER, INTEGRATIONS_POPOVER, SELECTED_RULES_NUMBER_LABEL, - REFRESH_SETTINGS_POPOVER, REFRESH_SETTINGS_SWITCH, ELASTIC_RULES_BTN, TOASTER_ERROR_BTN, @@ -57,6 +55,7 @@ import { TOASTER_CLOSE_ICON, ADD_ELASTIC_RULES_EMPTY_PROMPT_BTN, CONFIRM_DELETE_RULE_BTN, + AUTO_REFRESH_POPOVER_TRIGGER_BUTTON, } from '../screens/alerts_detection_rules'; import type { RULES_MONITORING_TABLE } from '../screens/alerts_detection_rules'; import { EUI_CHECKBOX } from '../screens/common/controls'; @@ -305,12 +304,6 @@ export const waitForRuleToUpdate = () => { cy.get(RULE_SWITCH_LOADER, { timeout: 300000 }).should('not.exist'); }; -export const checkAutoRefresh = (ms: number, condition: string) => { - cy.get(RULES_TABLE_AUTOREFRESH_INDICATOR).should('not.exist'); - cy.tick(ms); - cy.get(RULES_TABLE_AUTOREFRESH_INDICATOR).should(condition); -}; - export const importRules = (rulesFile: string) => { cy.get(RULE_IMPORT_MODAL).click(); cy.get(INPUT_FILE).click({ force: true }); @@ -459,22 +452,45 @@ export const testMultipleSelectedRulesLabel = (rulesCount: number) => { cy.get(SELECTED_RULES_NUMBER_LABEL).should('have.text', `Selected ${rulesCount} rules`); }; -export const openRefreshSettingsPopover = () => { - cy.get(REFRESH_SETTINGS_POPOVER).click(); +const openRefreshSettingsPopover = () => { + cy.get(REFRESH_SETTINGS_SWITCH).should('not.exist'); + cy.get(AUTO_REFRESH_POPOVER_TRIGGER_BUTTON).click(); cy.get(REFRESH_SETTINGS_SWITCH).should('be.visible'); }; -export const checkAutoRefreshIsDisabled = () => { +const closeRefreshSettingsPopover = () => { + cy.get(REFRESH_SETTINGS_SWITCH).should('be.visible'); + cy.get(AUTO_REFRESH_POPOVER_TRIGGER_BUTTON).click(); + cy.get(REFRESH_SETTINGS_SWITCH).should('not.exist'); +}; + +export const expectAutoRefreshIsDisabled = () => { + cy.get(AUTO_REFRESH_POPOVER_TRIGGER_BUTTON).should('be.enabled'); + cy.get(AUTO_REFRESH_POPOVER_TRIGGER_BUTTON).should('have.text', 'Off'); + openRefreshSettingsPopover(); cy.get(REFRESH_SETTINGS_SWITCH).should('have.attr', 'aria-checked', 'false'); + closeRefreshSettingsPopover(); }; -export const checkAutoRefreshIsEnabled = () => { +export const expectAutoRefreshIsEnabled = () => { + cy.get(AUTO_REFRESH_POPOVER_TRIGGER_BUTTON).should('be.enabled'); + cy.get(AUTO_REFRESH_POPOVER_TRIGGER_BUTTON).should('have.text', 'On'); + openRefreshSettingsPopover(); cy.get(REFRESH_SETTINGS_SWITCH).should('have.attr', 'aria-checked', 'true'); + closeRefreshSettingsPopover(); +}; + +// Expects the auto refresh to be deactivated which means it's disabled without an ability to enable it +// so it's even impossible to open the popover +export const expectAutoRefreshIsDeactivated = () => { + cy.get(AUTO_REFRESH_POPOVER_TRIGGER_BUTTON).should('be.disabled'); + cy.get(AUTO_REFRESH_POPOVER_TRIGGER_BUTTON).should('have.text', 'Off'); }; export const disableAutoRefresh = () => { + openRefreshSettingsPopover(); cy.get(REFRESH_SETTINGS_SWITCH).click(); - checkAutoRefreshIsDisabled(); + expectAutoRefreshIsDisabled(); }; export const mockGlobalClock = () => { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/auto_refresh_button/auto_refresh_button.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/auto_refresh_button/auto_refresh_button.tsx index 83499efd323c8..560be75abee26 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/auto_refresh_button/auto_refresh_button.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/auto_refresh_button/auto_refresh_button.tsx @@ -41,9 +41,14 @@ const AutoRefreshButtonComponent = ({ setIsRefreshOn, }: AutoRefreshButtonProps) => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const closePopover = useCallback(() => setIsPopoverOpen(false), [setIsPopoverOpen]); + const togglePopover = useCallback( + () => setIsPopoverOpen((prevState) => !prevState), + [setIsPopoverOpen] + ); const handleAutoRefreshSwitch = useCallback( - (closePopover: () => void) => (e: EuiSwitchEvent) => { + (e: EuiSwitchEvent) => { const refreshOn = e.target.checked; if (refreshOn) { reFetchRules(); @@ -51,18 +56,35 @@ const AutoRefreshButtonComponent = ({ setIsRefreshOn(refreshOn); closePopover(); }, - [reFetchRules, setIsRefreshOn] + [reFetchRules, setIsRefreshOn, closePopover] ); - const handleGetRefreshSettingsPopoverContent = useCallback( - (closePopover: () => void) => ( + return ( + + {isRefreshOn ? 'On' : 'Off'} + + } + > - ), - [isRefreshOn, handleAutoRefreshSwitch, isDisabled] - ); - - return ( - setIsPopoverOpen(false)} - button={ - setIsPopoverOpen(!isPopoverOpen)} - disabled={isDisabled} - css={css` - margin-left: 10px; - `} - > - {isRefreshOn ? 'On' : 'Off'} - - } - > - {handleGetRefreshSettingsPopoverContent(() => setIsPopoverOpen(false))} ); }; From aa45152a4e787f36014675b864fcc0fce7bd6758 Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Fri, 11 Aug 2023 14:24:06 +0200 Subject: [PATCH 05/11] [FTR] Implement browser network condition utils (#163633) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📓 Summary The PR implements some utilities into the `browser` service to allow controlling the network conditions during the execution of a functional test. ### `getNetworkConditions` Returns the current network simulation options. If none conditions are previously set, it returns `undefined` **N.B.**: _if the testing environment is not a Chromium browser, it throws an error that can be easily caught to manually skip the test or handle a fallback scenario._ ```ts it('should display a loading skeleton while loading', async function () { // Skip the test in case network condition utils are not available try { const networkConditions = await browser.getNetworkConditions(); // undefined await browser.setNetworkConditions('SLOW_3G'); const networkConditions = await browser.getNetworkConditions(); // { // offline: false, // latency: 2000, // download_throughput: 50000, // upload_throughput: 50000, // } } catch (error) { this.skip(); } }); ``` ### `setNetworkConditions` Set the desired network conditions. It supports different presets that match the [network profiles provided by Chrome debugger](https://github.com/ChromeDevTools/devtools-frontend/blob/da276a3faec9769cb55e442f0db77ebdce5cd178/front_end/core/sdk/NetworkManager.ts#L363-L393): - `NO_THROTTLING` - `FAST_3G` - `SLOW_3G` - `OFFLINE` - `CLOUD_USER` (pre-existing) It also accepts ad-hoc options to configure more specifically the network conditions. **N.B.**: _if the testing environment is not a Chromium browser, it throws an error that can be easily caught to manually skip the test or handle a fallback scenario._ ```ts it('should display a loading skeleton while loading', async function () { // Skip the test in case network condition utils are not available try { await browser.setNetworkConditions('NO_THROTTLING'); await browser.setNetworkConditions('FAST_3G'); await browser.setNetworkConditions('SLOW_3G'); await browser.setNetworkConditions('OFFLINE'); await browser.setNetworkConditions('CLOUD_USER'); await browser.setNetworkConditions({ offline: false, latency: 5, // Additional latency (ms). download_throughput: 500 * 1024, // Maximal aggregated download throughput. upload_throughput: 500 * 1024, // Maximal aggregated upload throughput. }); } catch (error) { this.skip(); } }); ``` ### restoreNetworkConditions Restore the original network conditions, setting to `NO_THROTTLING`. The native implementation of `deleteNetworkConditions` exposed by selenium is unofficial and didn't consistently work, the recommended approach by the google dev tools team is to restore the connection setting the no throttling profile. **N.B.**: _if the testing environment is not a Chromium browser, it throws an error that can be easily caught to manually skip the test or handle a fallback scenario._ ```ts it('should display a loading skeleton while loading', async function () { // Skip the test in case network condition utils are not available try { await browser.setNetworkConditions('SLOW_3G'); // Slow down network conditions // Do your assertions await browser.restoreNetworkConditions(); // Restore network conditions } catch (error) { this.skip(); } }); ``` Co-authored-by: Marco Antonio Ghiani Co-authored-by: Dzmitry Lemechko --- .../from_the_browser/loaded_kibana.ts | 2 +- test/functional/services/common/browser.ts | 75 +++++++++++++++++-- .../services/remote/network_profiles.ts | 45 +++++++++-- test/functional/services/remote/webdriver.ts | 24 ++---- 4 files changed, 117 insertions(+), 29 deletions(-) diff --git a/test/analytics/tests/instrumented_events/from_the_browser/loaded_kibana.ts b/test/analytics/tests/instrumented_events/from_the_browser/loaded_kibana.ts index e4ac2346b5893..02e1e7656bf78 100644 --- a/test/analytics/tests/instrumented_events/from_the_browser/loaded_kibana.ts +++ b/test/analytics/tests/instrumented_events/from_the_browser/loaded_kibana.ts @@ -62,7 +62,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(event.properties.value4).to.be.a('number'); expect(event.properties.value5).to.be.a('number'); - if (browser.isChromium) { + if (browser.isChromium()) { // Kibana Loaded memory expect(meta).to.have.property('jsHeapSizeLimit'); expect(meta.jsHeapSizeLimit).to.be.a('number'); diff --git a/test/functional/services/common/browser.ts b/test/functional/services/common/browser.ts index 393f093d54189..fd46e5ac1448b 100644 --- a/test/functional/services/common/browser.ts +++ b/test/functional/services/common/browser.ts @@ -7,8 +7,9 @@ */ import { setTimeout as setTimeoutAsync } from 'timers/promises'; -import { cloneDeepWith } from 'lodash'; +import { cloneDeepWith, isString } from 'lodash'; import { Key, Origin, WebDriver } from 'selenium-webdriver'; +import { Driver as ChromiumWebDriver } from 'selenium-webdriver/chrome'; import { modifyUrl } from '@kbn/std'; import sharp from 'sharp'; @@ -16,6 +17,7 @@ import { NoSuchSessionError } from 'selenium-webdriver/lib/error'; import { WebElementWrapper } from '../lib/web_element_wrapper'; import { FtrProviderContext, FtrService } from '../../ftr_provider_context'; import { Browsers } from '../remote/browsers'; +import { NetworkOptions, NetworkProfile, NETWORK_PROFILES } from '../remote/network_profiles'; export type Browser = BrowserService; @@ -25,19 +27,20 @@ class BrowserService extends FtrService { */ public readonly keys = Key; public readonly isFirefox: boolean; - public readonly isChromium: boolean; private readonly log = this.ctx.getService('log'); constructor( ctx: FtrProviderContext, public readonly browserType: string, - private readonly driver: WebDriver + protected readonly driver: WebDriver | ChromiumWebDriver ) { super(ctx); this.isFirefox = this.browserType === Browsers.Firefox; - this.isChromium = - this.browserType === Browsers.Chrome || this.browserType === Browsers.ChromiumEdge; + } + + public isChromium(): this is { driver: ChromiumWebDriver } { + return this.driver instanceof ChromiumWebDriver; } /** @@ -661,6 +664,68 @@ class BrowserService extends FtrService { } } } + + /** + * Get the network simulation for chromium browsers if available. + * https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/chrome_exports_Driver.html#getNetworkConditions + * + * @return {Promise} + */ + public async getNetworkConditions() { + if (this.isChromium()) { + return this.driver.getNetworkConditions().catch(() => undefined); // Return undefined instead of throwing if no conditions are set. + } else { + const message = + 'WebDriver does not support the .getNetworkConditions method.\nProbably the browser in used is not chromium based.'; + this.log.error(message); + throw new Error(message); + } + } + + /** + * Delete the network simulation for chromium browsers if available. + * + * @return {Promise} + */ + public async restoreNetworkConditions() { + this.log.debug('Restore network conditions simulation.'); + return this.setNetworkConditions('NO_THROTTLING'); + } + + /** + * Set the network conditions for chromium browsers if available. + * + * __Sample Usage:__ + * + * browser.setNetworkConditions('FAST_3G') + * browser.setNetworkConditions('SLOW_3G') + * browser.setNetworkConditions('OFFLINE') + * browser.setNetworkConditions({ + * offline: false, + * latency: 5, // Additional latency (ms). + * download_throughput: 500 * 1024, // Maximal aggregated download throughput. + * upload_throughput: 500 * 1024, // Maximal aggregated upload throughput. + * }); + * + * https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/chrome_exports_Driver.html#setNetworkConditions + * + * @return {Promise} + */ + public async setNetworkConditions(profileOrOptions: NetworkProfile | NetworkOptions) { + const networkOptions = isString(profileOrOptions) + ? NETWORK_PROFILES[profileOrOptions] + : profileOrOptions; + + if (this.isChromium()) { + this.log.debug(`Set network conditions with profile "${profileOrOptions}".`); + return this.driver.setNetworkConditions(networkOptions); + } else { + const message = + 'WebDriver does not support the .setNetworkCondition method.\nProbably the browser in used is not chromium based.'; + this.log.error(message); + throw new Error(message); + } + } } export async function BrowserProvider(ctx: FtrProviderContext) { diff --git a/test/functional/services/remote/network_profiles.ts b/test/functional/services/remote/network_profiles.ts index cb4076686270c..29e4a0feeaace 100644 --- a/test/functional/services/remote/network_profiles.ts +++ b/test/functional/services/remote/network_profiles.ts @@ -6,10 +6,13 @@ * Side Public License, v 1. */ -interface NetworkOptions { - DOWNLOAD: number; - UPLOAD: number; - LATENCY: number; +export type NetworkProfile = 'NO_THROTTLING' | 'FAST_3G' | 'SLOW_3G' | 'OFFLINE' | 'CLOUD_USER'; + +export interface NetworkOptions { + offline: boolean; + latency: number; + download_throughput: number; + upload_throughput: number; } const sec = 10 ** 3; @@ -17,6 +20,36 @@ const MBps = 10 ** 6 / 8; // megabyte per second (MB/s) (can be abbreviated as M // Selenium uses B/s (bytes) for network throttling // Download (B/s) Upload (B/s) Latency (ms) -export const NETWORK_PROFILES: { [key: string]: NetworkOptions } = { - CLOUD_USER: { DOWNLOAD: 6 * MBps, UPLOAD: 6 * MBps, LATENCY: 0.1 * sec }, + +export const NETWORK_PROFILES: Record = { + NO_THROTTLING: { + offline: false, + latency: 0, + download_throughput: -1, + upload_throughput: -1, + }, + FAST_3G: { + offline: false, + latency: 0.56 * sec, + download_throughput: 1.44 * MBps, + upload_throughput: 0.7 * MBps, + }, + SLOW_3G: { + offline: false, + latency: 2 * sec, + download_throughput: 0.4 * MBps, + upload_throughput: 0.4 * MBps, + }, + OFFLINE: { + offline: true, + latency: 0, + download_throughput: 0, + upload_throughput: 0, + }, + CLOUD_USER: { + offline: false, + latency: 0.1 * sec, + download_throughput: 6 * MBps, + upload_throughput: 6 * MBps, + }, }; diff --git a/test/functional/services/remote/webdriver.ts b/test/functional/services/remote/webdriver.ts index 1486d02656d12..702f674b3c10d 100644 --- a/test/functional/services/remote/webdriver.ts +++ b/test/functional/services/remote/webdriver.ts @@ -30,7 +30,7 @@ import { createStdoutSocket } from './create_stdout_stream'; import { preventParallelCalls } from './prevent_parallel_calls'; import { Browsers } from './browsers'; -import { NETWORK_PROFILES } from './network_profiles'; +import { NetworkProfile, NETWORK_PROFILES } from './network_profiles'; const throttleOption: string = process.env.TEST_THROTTLE_NETWORK as string; const headlessBrowser: string = process.env.TEST_BROWSER_HEADLESS as string; @@ -300,22 +300,17 @@ async function attemptToCreateCommand( const { session, consoleLog$ } = await buildDriverInstance(); if (throttleOption === '1' && browserType === 'chrome') { - const { KBN_NETWORK_TEST_PROFILE = 'CLOUD_USER' } = process.env; + const KBN_NETWORK_TEST_PROFILE = (process.env.KBN_NETWORK_TEST_PROFILE ?? + 'CLOUD_USER') as NetworkProfile; const profile = - KBN_NETWORK_TEST_PROFILE in Object.keys(NETWORK_PROFILES) - ? KBN_NETWORK_TEST_PROFILE - : 'CLOUD_USER'; + KBN_NETWORK_TEST_PROFILE in NETWORK_PROFILES ? KBN_NETWORK_TEST_PROFILE : 'CLOUD_USER'; - const { - DOWNLOAD: downloadThroughput, - UPLOAD: uploadThroughput, - LATENCY: latency, - } = NETWORK_PROFILES[`${profile}`]; + const networkProfileOptions = NETWORK_PROFILES[profile]; // Only chrome supports this option. log.debug( - `NETWORK THROTTLED with profile ${profile}: ${downloadThroughput} B/s down, ${uploadThroughput} B/s up, ${latency} ms latency.` + `NETWORK THROTTLED with profile ${profile}: ${networkProfileOptions.download_throughput} B/s down, ${networkProfileOptions.upload_throughput} B/s up, ${networkProfileOptions.latency} ms latency.` ); if (noCache) { @@ -326,12 +321,7 @@ async function attemptToCreateCommand( } // @ts-expect-error - session.setNetworkConditions({ - offline: false, - latency, - download_throughput: downloadThroughput, - upload_throughput: uploadThroughput, - }); + session.setNetworkConditions(networkProfileOptions); } if (attemptId !== attemptCounter) { From 9bc2150c789fef4f8ddf5c3ee329a971e4e0c8cb Mon Sep 17 00:00:00 2001 From: amyjtechwriter <61687663+amyjtechwriter@users.noreply.github.com> Date: Fri, 11 Aug 2023 14:03:10 +0100 Subject: [PATCH 06/11] [DOCS] Adds the release notes for the 8.9.1 release. (#163578) ## Summary Adds the release notes for the 8.9.1 release. Closes:#163565 --- docs/CHANGELOG.asciidoc | 48 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc index 1e58e8a9e6135..5da373c8d9a51 100644 --- a/docs/CHANGELOG.asciidoc +++ b/docs/CHANGELOG.asciidoc @@ -10,6 +10,7 @@ Review important information about the {kib} 8.x releases. +* <> * <> * <> * <> @@ -45,6 +46,53 @@ Review important information about the {kib} 8.x releases. * <> -- +[[release-notes-8.9.1]] +== {kib} 8.9.1 + +coming::[8.9.1] + +Review the following information about the {kib} 8.9.1 release. + +[float] +[[breaking-changes-8.9.1]] +=== Breaking changes + +Breaking changes can prevent your application from optimal operation and performance. +Before you upgrade to 8.9.0, review the breaking changes, then mitigate the impact to your application. + +There are no breaking changes in the {kib} 8.9.1 release. + +To review the breaking changes in the previous release, check {kibana-ref-all}/8.9/release-notes-8.9.0.html#breaking-changes-8.9.0[8.9.0]. + +[float] +[[fixes-v8.9.1]] +=== Bug Fixes +APM:: +* Fixes flame graph rendering on the transaction detail page ({kibana-pull}162968[#162968]). +* Check if documents are missing `span.name` ({kibana-pull}162899[#162899]). +* Fixes transaction action menu for Trace Explorer and dependency operations ({kibana-pull}162213[#162213]). +Canvas:: +* Fixes embeddables not rendering in Canvas ({kibana-pull}163013[#163013]). +Discover:: +* Fixes grid styles to enable better content wrapping ({kibana-pull}162325[#162325]). +* Fixes search sessions using temporary data views ({kibana-pull}161029[#161029]). +* Make share links and search session information shorter for temporary data views ({kibana-pull}161180[#161180]). +Elastic Security:: +For the Elastic Security 8.9.1 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. +Fleet:: +* Fixes for query error on Agents list in the UI ({kibana-pull}162816[#162816]). +* Remove duplicate path being pushed to package archive ({kibana-pull}162724[#162724]). +Management:: +* Resolves potential errors present in v8.9.0 with data views that contain field filters that have been edited ({kibana-pull}162860[#162860]). +Uptime:: +* Fixes Monitor not found 404 message display ({kibana-pull}163501[#163501]). + +[float] +[[enhancement-v8.9.1]] +=== Enhancements +Discover:: +* Set legend width to extra large and enable text wrapping in legend labels ({kibana-pull}163009[#163009]). + [[release-notes-8.9.0]] == {kib} 8.9.0 From a86c016219bb9e6c9dd4eacd5db6f485a166dee5 Mon Sep 17 00:00:00 2001 From: Philippe Oberti Date: Fri, 11 Aug 2023 15:18:12 +0200 Subject: [PATCH 07/11] [Security Solution] expandable flyout - replace feature flag with advanced settings toggle (#161614) --- .../server/collectors/management/schema.ts | 4 + .../server/collectors/management/types.ts | 1 + src/plugins/telemetry/schema/oss_plugins.json | 6 + .../security_solution/common/constants.ts | 8 +- .../common/experimental_features.ts | 4 - .../detection_alerts/cti_enrichments.cy.ts | 2 + .../e2e/detection_alerts/enrichments.cy.ts | 2 + .../e2e/explore/guided_onboarding/tour.cy.ts | 2 + .../alerts/alerts_details.cy.ts | 5 + ...etails_left_panel_analyzer_graph_tab.cy.ts | 50 +- ..._details_left_panel_correlations_tab.cy.ts | 84 ++- ...lert_details_left_panel_entities_tab.cy.ts | 54 +- ...details_left_panel_investigation_tab.cy.ts | 40 +- ...rt_details_left_panel_prevalence_tab.cy.ts | 92 ++- ...lert_details_left_panel_response_tab.cy.ts | 34 +- ..._details_left_panel_session_view_tab.cy.ts | 54 +- ...s_left_panel_threat_intelligence_tab.cy.ts | 48 +- ...t_details_preview_panel_rule_preview.cy.ts | 101 ++-- .../alert_details_right_panel.cy.ts | 308 +++++----- .../alert_details_right_panel_json_tab.cy.ts | 38 +- ...ert_details_right_panel_overview_tab.cy.ts | 541 +++++++++--------- .../alert_details_right_panel_table_tab.cy.ts | 76 ++- .../alert_details_url_sync.cy.ts | 54 +- .../alerts/investigate_in_timeline.cy.ts | 2 + .../api_calls/kibana_advanced_settings.ts | 5 + .../control_columns/row_action/index.tsx | 5 +- .../alerts/alert_details_redirect.test.tsx | 2 +- .../pages/alerts/alert_details_redirect.tsx | 10 +- .../endpoint/automated_response_actions.cy.ts | 3 +- .../cypress/e2e/endpoint/isolate.cy.ts | 3 +- .../no_license.cy.ts | 2 + .../automated_response_actions/results.cy.ts | 3 + .../cypress/e2e/mocked_data/isolate.cy.ts | 3 +- .../public/management/cypress/tasks/common.ts | 20 + .../security_solution/server/ui_settings.ts | 17 + .../apps/endpoint/responder.ts | 6 + 36 files changed, 852 insertions(+), 837 deletions(-) diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts index 84079703affc9..27fef2a017824 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts @@ -110,6 +110,10 @@ export const stackManagementSchema: MakeSchemaFrom = { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, }, + 'securitySolution:enableExpandableFlyout': { + type: 'boolean', + _meta: { description: 'Non-default value of setting.' }, + }, 'securitySolution:enableCcsWarning': { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts index a48a3905c8705..81f7b0eb957f1 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts @@ -63,6 +63,7 @@ export interface UsageStats { 'securitySolution:defaultAnomalyScore': number; 'securitySolution:refreshIntervalDefaults': string; 'securitySolution:enableNewsFeed': boolean; + 'securitySolution:enableExpandableFlyout': boolean; 'securitySolution:enableCcsWarning': boolean; 'search:includeFrozen': boolean; 'courier:maxConcurrentShardRequests': number; diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 52a54eb0335b1..00d8a96a59e0e 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -9207,6 +9207,12 @@ "description": "Non-default value of setting." } }, + "securitySolution:enableExpandableFlyout": { + "type": "boolean", + "_meta": { + "description": "Non-default value of setting." + } + }, "securitySolution:enableCcsWarning": { "type": "boolean", "_meta": { diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 60f6e597306b1..47c7cb7b39f70 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -163,6 +163,9 @@ export const DEFAULT_INDEX_PATTERN = [...INCLUDE_INDEX_PATTERN, ...EXCLUDE_ELAST /** This Kibana Advanced Setting enables the `Security news` feed widget */ export const ENABLE_NEWS_FEED_SETTING = 'securitySolution:enableNewsFeed' as const; +/** This Kibana Advanced Setting allows users to enable/disable the Expandable Flyout */ +export const ENABLE_EXPANDABLE_FLYOUT_SETTING = 'securitySolution:enableExpandableFlyout' as const; + /** This Kibana Advanced Setting enables the warnings for CCS read permissions */ export const ENABLE_CCS_READ_WARNING_SETTING = 'securitySolution:enableCcsWarning' as const; @@ -210,7 +213,6 @@ export const UPDATE_OR_CREATE_LEGACY_ACTIONS = '/internal/api/detection/legacy/n /** * Exceptions management routes */ - export const SHARED_EXCEPTION_LIST_URL = `/api${EXCEPTIONS_PATH}/shared` as const; /** @@ -322,12 +324,12 @@ export const ALERTS_AS_DATA_FIND_URL = `${ALERTS_AS_DATA_URL}/find` as const; */ export const UNAUTHENTICATED_USER = 'Unauthenticated' as const; -/* +/** Licensing requirements */ export const MINIMUM_ML_LICENSE = 'platinum' as const; -/* +/** Machine Learning constants */ export const ML_GROUP_ID = 'security' as const; diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index b2e1cae53d97e..449092e86130a 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -76,10 +76,6 @@ export const allowedExperimentalValues = Object.freeze({ */ alertsPageChartsEnabled: true, alertTypeEnabled: false, - /** - * Enables the new security flyout over the current alert details flyout - */ - securityFlyoutEnabled: false, /* * Enables new Set of filters on the Alerts page. diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/cti_enrichments.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/cti_enrichments.cy.ts index fc424eb192e05..0a626f2fff8d4 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/cti_enrichments.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/cti_enrichments.cy.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { disableExpandableFlyout } from '../../tasks/api_calls/kibana_advanced_settings'; import { getNewThreatIndicatorRule, indicatorRuleMatchingDoc } from '../../objects/rule'; import { cleanKibana } from '../../tasks/common'; import { login, visitWithoutDateRange } from '../../tasks/login'; @@ -34,6 +35,7 @@ describe('CTI Enrichment', () => { cy.task('esArchiverLoad', 'suspicious_source_event'); login(); createRule({ ...getNewThreatIndicatorRule(), rule_id: 'rule_testing', enabled: true }); + disableExpandableFlyout(); }); after(() => { diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/enrichments.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/enrichments.cy.ts index 2eacd9ece4865..95481b23174f1 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/enrichments.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/enrichments.cy.ts @@ -24,6 +24,7 @@ import { scrollAlertTableColumnIntoView, closeAlertFlyout, } from '../../tasks/alerts'; +import { disableExpandableFlyout } from '../../tasks/api_calls/kibana_advanced_settings'; import { login, visit } from '../../tasks/login'; @@ -41,6 +42,7 @@ describe('Enrichment', () => { describe('Custom query rule', () => { beforeEach(() => { + disableExpandableFlyout(); cy.task('esArchiverLoad', 'risk_hosts'); deleteAlertsAndRules(); createRule(getNewRule({ rule_id: 'rule1' })); diff --git a/x-pack/plugins/security_solution/cypress/e2e/explore/guided_onboarding/tour.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/explore/guided_onboarding/tour.cy.ts index 33799309fd3a6..1c180857c00b9 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/explore/guided_onboarding/tour.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/explore/guided_onboarding/tour.cy.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { disableExpandableFlyout } from '../../../tasks/api_calls/kibana_advanced_settings'; import { navigateFromHeaderTo } from '../../../tasks/security_header'; import { ALERTS, TIMELINES } from '../../../screens/security_header'; import { closeAlertFlyout, expandFirstAlert } from '../../../tasks/alerts'; @@ -36,6 +37,7 @@ describe('Guided onboarding tour', () => { }); beforeEach(() => { login(); + disableExpandableFlyout(); startAlertsCasesTour(); visit(ALERTS_URL); waitForAlertsToPopulate(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/alerts_details.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/alerts_details.cy.ts index 7c2ec7a31e12a..7f5e0cde93b10 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/alerts_details.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/alerts_details.cy.ts @@ -6,6 +6,7 @@ */ import type { DataTableModel } from '@kbn/securitysolution-data-table'; +import { disableExpandableFlyout } from '../../../tasks/api_calls/kibana_advanced_settings'; import { ALERT_FLYOUT, CELL_TEXT, @@ -39,6 +40,7 @@ describe('Alert details flyout', () => { before(() => { cleanKibana(); login(); + disableExpandableFlyout(); createRule(getNewRule()); visitWithoutDateRange(ALERTS_URL); waitForAlertsToPopulate(); @@ -64,6 +66,7 @@ describe('Alert details flyout', () => { beforeEach(() => { login(); + disableExpandableFlyout(); visitWithoutDateRange(ALERTS_URL); waitForAlertsToPopulate(); expandFirstAlert(); @@ -128,6 +131,7 @@ describe('Alert details flyout', () => { beforeEach(() => { login(); + disableExpandableFlyout(); visit(ALERTS_URL); waitForAlertsToPopulate(); expandFirstAlert(); @@ -173,6 +177,7 @@ describe('Alert details flyout', () => { beforeEach(() => { login(); + disableExpandableFlyout(); visit(ALERTS_URL); waitForAlertsToPopulate(); expandFirstAlert(); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_analyzer_graph_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_analyzer_graph_tab.cy.ts index 43531feb67d73..573286b921d56 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_analyzer_graph_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_analyzer_graph_tab.cy.ts @@ -24,34 +24,30 @@ import { getNewRule } from '../../../../objects/rule'; import { ALERTS_URL } from '../../../../urls/navigation'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; -describe( - 'Alert details expandable flyout left panel analyzer graph', - { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, - () => { - beforeEach(() => { - cleanKibana(); - login(); - createRule(getNewRule()); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); - expandDocumentDetailsExpandableFlyoutLeftSection(); - openGraphAnalyzerTab(); - }); +describe('Alert details expandable flyout left panel analyzer graph', () => { + beforeEach(() => { + cleanKibana(); + login(); + createRule(getNewRule()); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + expandDocumentDetailsExpandableFlyoutLeftSection(); + openGraphAnalyzerTab(); + }); - it('should display analyzer graph and node list under visualize', () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB) - .should('be.visible') - .and('have.text', 'Visualize'); + it('should display analyzer graph and node list under visualize', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB) + .should('be.visible') + .and('have.text', 'Visualize'); - cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_BUTTON_GROUP).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_BUTTON_GROUP).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_BUTTON) - .should('be.visible') - .and('have.text', 'Analyzer Graph'); + cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_BUTTON) + .should('be.visible') + .and('have.text', 'Analyzer Graph'); - cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_CONTENT).should('be.visible'); - cy.get(ANALYZER_NODE).first().should('be.visible'); - }); - } -); + cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_CONTENT).should('be.visible'); + cy.get(ANALYZER_NODE).first().should('be.visible'); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_correlations_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_correlations_tab.cy.ts index da6dccc082c7f..0b02939f5ca4f 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_correlations_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_correlations_tab.cy.ts @@ -34,60 +34,54 @@ import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; import { login, visit } from '../../../../tasks/login'; import { ALERTS_URL } from '../../../../urls/navigation'; -describe( - 'Expandable flyout left panel correlations', - { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, - () => { - beforeEach(() => { - cleanKibana(); - login(); - createRule(getNewRule()); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); - expandDocumentDetailsExpandableFlyoutLeftSection(); - createNewCaseFromExpandableFlyout(); - openInsightsTab(); - openCorrelationsTab(); - }); +describe('Expandable flyout left panel correlations', () => { + beforeEach(() => { + cleanKibana(); + login(); + createRule(getNewRule()); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + expandDocumentDetailsExpandableFlyoutLeftSection(); + createNewCaseFromExpandableFlyout(); + openInsightsTab(); + openCorrelationsTab(); + }); - it('should render correlations details correctly', () => { - cy.log('link the alert to a new case'); + it('should render correlations details correctly', () => { + cy.log('link the alert to a new case'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB).scrollIntoView(); - cy.log('should render the Insights header'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB) - .should('be.visible') - .and('have.text', 'Insights'); + cy.log('should render the Insights header'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB).should('be.visible').and('have.text', 'Insights'); - cy.log('should render the inner tab switch'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_BUTTON_GROUP).should('be.visible'); + cy.log('should render the inner tab switch'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_BUTTON_GROUP).should('be.visible'); - cy.log('should render correlations tab activator / button'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_CORRELATIONS_BUTTON) - .should('be.visible') - .and('have.text', 'Correlations'); + cy.log('should render correlations tab activator / button'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_CORRELATIONS_BUTTON) + .should('be.visible') + .and('have.text', 'Correlations'); - cy.log('should render all the correlations sections'); + cy.log('should render all the correlations sections'); - cy.get(CORRELATIONS_ANCESTRY_SECTION) - .should('be.visible') - .and('have.text', '1 alert related by ancestry'); + cy.get(CORRELATIONS_ANCESTRY_SECTION) + .should('be.visible') + .and('have.text', '1 alert related by ancestry'); - cy.get(CORRELATIONS_SOURCE_SECTION) - .should('be.visible') - .and('have.text', '0 alerts related by source event'); + cy.get(CORRELATIONS_SOURCE_SECTION) + .should('be.visible') + .and('have.text', '0 alerts related by source event'); - cy.get(CORRELATIONS_SESSION_SECTION) - .should('be.visible') - .and('have.text', '1 alert related by session'); + cy.get(CORRELATIONS_SESSION_SECTION) + .should('be.visible') + .and('have.text', '1 alert related by session'); - cy.get(CORRELATIONS_CASES_SECTION).should('be.visible').and('have.text', '1 related case'); + cy.get(CORRELATIONS_CASES_SECTION).should('be.visible').and('have.text', '1 related case'); - expandCorrelationsSection(CORRELATIONS_ANCESTRY_SECTION); + expandCorrelationsSection(CORRELATIONS_ANCESTRY_SECTION); - cy.get(CORRELATIONS_ANCESTRY_TABLE).should('be.visible'); - }); - } -); + cy.get(CORRELATIONS_ANCESTRY_TABLE).should('be.visible'); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_entities_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_entities_tab.cy.ts index f48611f12af09..a680f783af336 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_entities_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_entities_tab.cy.ts @@ -25,38 +25,32 @@ import { getNewRule } from '../../../../objects/rule'; import { ALERTS_URL } from '../../../../urls/navigation'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; -describe( - 'Alert details expandable flyout left panel entities', - { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, - () => { - beforeEach(() => { - cleanKibana(); - login(); - createRule(getNewRule()); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); - expandDocumentDetailsExpandableFlyoutLeftSection(); - openInsightsTab(); - openEntitiesTab(); - }); +describe('Alert details expandable flyout left panel entities', () => { + beforeEach(() => { + cleanKibana(); + login(); + createRule(getNewRule()); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + expandDocumentDetailsExpandableFlyoutLeftSection(); + openInsightsTab(); + openEntitiesTab(); + }); - it('should display analyzer graph and node list under Insights Entities', () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB) - .should('be.visible') - .and('have.text', 'Insights'); + it('should display analyzer graph and node list under Insights Entities', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB).should('be.visible').and('have.text', 'Insights'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_BUTTON_GROUP).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_BUTTON_GROUP).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_BUTTON) - .should('be.visible') - .and('have.text', 'Entities'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_BUTTON) + .should('be.visible') + .and('have.text', 'Entities'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_HOST_DETAILS).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_HOST_DETAILS).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_HOST_DETAILS).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_HOST_DETAILS).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_USER_DETAILS).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_USER_DETAILS).should('be.visible'); - }); - } -); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_USER_DETAILS).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_USER_DETAILS).should('be.visible'); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_investigation_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_investigation_tab.cy.ts index 62d4932a017b8..833d591344f57 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_investigation_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_investigation_tab.cy.ts @@ -19,27 +19,23 @@ import { getNewRule } from '../../../../objects/rule'; import { ALERTS_URL } from '../../../../urls/navigation'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; -describe( - 'Alert details expandable flyout left panel investigation', - { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, - () => { - beforeEach(() => { - cleanKibana(); - login(); - createRule(getNewRule()); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); - expandDocumentDetailsExpandableFlyoutLeftSection(); - openInvestigationTab(); - }); +describe('Alert details expandable flyout left panel investigation', () => { + beforeEach(() => { + cleanKibana(); + login(); + createRule(getNewRule()); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + expandDocumentDetailsExpandableFlyoutLeftSection(); + openInvestigationTab(); + }); - it('should display investigation guide', () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB) - .should('be.visible') - .and('have.text', 'Investigation'); + it('should display investigation guide', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB) + .should('be.visible') + .and('have.text', 'Investigation'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB_CONTENT).should('be.visible'); - }); - } -); + cy.get(DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB_CONTENT).should('be.visible'); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_prevalence_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_prevalence_tab.cy.ts index 3cfe58f22893c..ab06e9fea187e 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_prevalence_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_prevalence_tab.cy.ts @@ -30,56 +30,50 @@ import { getNewRule } from '../../../../objects/rule'; import { ALERTS_URL } from '../../../../urls/navigation'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; -describe( - 'Alert details expandable flyout left panel prevalence', - { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, - () => { - beforeEach(() => { - cleanKibana(); - login(); - createRule(getNewRule()); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); - expandDocumentDetailsExpandableFlyoutLeftSection(); - openInsightsTab(); - openPrevalenceTab(); - }); +describe('Alert details expandable flyout left panel prevalence', () => { + beforeEach(() => { + cleanKibana(); + login(); + createRule(getNewRule()); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + expandDocumentDetailsExpandableFlyoutLeftSection(); + openInsightsTab(); + openPrevalenceTab(); + }); - it('should display prevalence tab', () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB) - .should('be.visible') - .and('have.text', 'Insights'); + it('should display prevalence tab', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB).should('be.visible').and('have.text', 'Insights'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_BUTTON_GROUP).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_BUTTON_GROUP).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_BUTTON) - .should('be.visible') - .and('have.text', 'Prevalence'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_BUTTON) + .should('be.visible') + .and('have.text', 'Prevalence'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_TABLE).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_TABLE_TYPE_CELL) - .should('contain.text', 'host.name') - .and('contain.text', 'user.name'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_TABLE_NAME_CELL) - .should('contain.text', 'siem-kibana') - .and('contain.text', 'test'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_TABLE_ALERT_COUNT_CELL).should( - 'contain.text', - 2 - ); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_TABLE_DOC_COUNT_CELL).should( - 'contain.text', - 0 - ); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_TABLE_HOST_PREVALENCE_CELL).should( - 'contain.text', - 100 - ); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_TABLE_USER_PREVALENCE_CELL).should( - 'contain.text', - 100 - ); - }); - } -); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_TABLE).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_TABLE_TYPE_CELL) + .should('contain.text', 'host.name') + .and('contain.text', 'user.name'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_TABLE_NAME_CELL) + .should('contain.text', 'siem-kibana') + .and('contain.text', 'test'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_TABLE_ALERT_COUNT_CELL).should( + 'contain.text', + 2 + ); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_TABLE_DOC_COUNT_CELL).should( + 'contain.text', + 0 + ); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_TABLE_HOST_PREVALENCE_CELL).should( + 'contain.text', + 100 + ); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_PREVALENCE_TABLE_USER_PREVALENCE_CELL).should( + 'contain.text', + 100 + ); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_response_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_response_tab.cy.ts index 19b9eac037a4c..19ed92dbff60f 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_response_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_response_tab.cy.ts @@ -16,23 +16,19 @@ import { getNewRule } from '../../../../objects/rule'; import { ALERTS_URL } from '../../../../urls/navigation'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; -describe( - 'Alert details expandable flyout left panel investigation', - { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, - () => { - beforeEach(() => { - cleanKibana(); - login(); - createRule(getNewRule()); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); - expandDocumentDetailsExpandableFlyoutLeftSection(); - openResponseTab(); - }); +describe('Alert details expandable flyout left panel investigation', () => { + beforeEach(() => { + cleanKibana(); + login(); + createRule(getNewRule()); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + expandDocumentDetailsExpandableFlyoutLeftSection(); + openResponseTab(); + }); - it('should display empty response message', () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_RESPONSE_EMPTY).should('be.visible'); - }); - } -); + it('should display empty response message', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_RESPONSE_EMPTY).should('be.visible'); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_session_view_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_session_view_tab.cy.ts index 762b5cf307b4f..afc3ac1b0b918 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_session_view_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_session_view_tab.cy.ts @@ -22,36 +22,32 @@ import { getNewRule } from '../../../../objects/rule'; import { ALERTS_URL } from '../../../../urls/navigation'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; -describe( - 'Alert details expandable flyout left panel session view', - { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, - () => { - beforeEach(() => { - cleanKibana(); - login(); - createRule(getNewRule()); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); - expandDocumentDetailsExpandableFlyoutLeftSection(); - }); +describe('Alert details expandable flyout left panel session view', () => { + beforeEach(() => { + cleanKibana(); + login(); + createRule(getNewRule()); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + expandDocumentDetailsExpandableFlyoutLeftSection(); + }); - it('should display session view under visualize', () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB) - .should('be.visible') - .and('have.text', 'Visualize'); + it('should display session view under visualize', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB) + .should('be.visible') + .and('have.text', 'Visualize'); - cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_BUTTON_GROUP).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_BUTTON_GROUP).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_BUTTON) - .should('be.visible') - .and('have.text', 'Session View'); + cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_BUTTON) + .should('be.visible') + .and('have.text', 'Session View'); - // TODO ideally we would have a test for the session view component instead - cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_ERROR) - .should('be.visible') - .and('contain.text', 'Unable to display session view') - .and('contain.text', 'There was an error displaying session view'); - }); - } -); + // TODO ideally we would have a test for the session view component instead + cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_ERROR) + .should('be.visible') + .and('contain.text', 'Unable to display session view') + .and('contain.text', 'There was an error displaying session view'); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_threat_intelligence_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_threat_intelligence_tab.cy.ts index d79c59ed71440..c5cd168836179 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_threat_intelligence_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_threat_intelligence_tab.cy.ts @@ -22,34 +22,28 @@ import { } from '../../../../screens/expandable_flyout/alert_details_left_panel'; import { DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_THREAT_INTELLIGENCE_BUTTON } from '../../../../screens/expandable_flyout/alert_details_left_panel_threat_intelligence_tab'; -describe( - 'Expandable flyout left panel threat intelligence', - { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, - () => { - beforeEach(() => { - cleanKibana(); - login(); - createRule(getNewRule()); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); - expandDocumentDetailsExpandableFlyoutLeftSection(); - openInsightsTab(); - openThreatIntelligenceTab(); - }); +describe('Expandable flyout left panel threat intelligence', () => { + beforeEach(() => { + cleanKibana(); + login(); + createRule(getNewRule()); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + expandDocumentDetailsExpandableFlyoutLeftSection(); + openInsightsTab(); + openThreatIntelligenceTab(); + }); - it('should serialize its state to url', () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB) - .should('be.visible') - .and('have.text', 'Insights'); + it('should serialize its state to url', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB).should('be.visible').and('have.text', 'Insights'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_BUTTON_GROUP).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_BUTTON_GROUP).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_THREAT_INTELLIGENCE_BUTTON) - .should('be.visible') - .and('have.text', 'Threat Intelligence'); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_THREAT_INTELLIGENCE_BUTTON) + .should('be.visible') + .and('have.text', 'Threat Intelligence'); - cy.get(INDICATOR_MATCH_ENRICHMENT_SECTION).should('be.visible'); - }); - } -); + cy.get(INDICATOR_MATCH_ENRICHMENT_SECTION).should('be.visible'); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_preview_panel_rule_preview.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_preview_panel_rule_preview.cy.ts index 116162983f0b2..cc48e4568d908 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_preview_panel_rule_preview.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_preview_panel_rule_preview.cy.ts @@ -34,68 +34,63 @@ import { getNewRule } from '../../../../objects/rule'; import { ALERTS_URL } from '../../../../urls/navigation'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; -describe( - 'Alert details expandable flyout rule preview panel', - { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, - () => { - const rule = getNewRule(); +describe('Alert details expandable flyout rule preview panel', () => { + const rule = getNewRule(); - beforeEach(() => { - cleanKibana(); - login(); - createRule(rule); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); - clickRuleSummaryButton(); - }); + beforeEach(() => { + cleanKibana(); + login(); + createRule(rule); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + clickRuleSummaryButton(); + }); + + describe('rule preview', () => { + it('should display rule preview and its sub sections', () => { + cy.log('rule preview panel'); - describe('rule preview', () => { - it('should display rule preview and its sub sections', () => { - cy.log('rule preview panel'); + cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_SECTION).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_HEADER).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_BODY).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_SECTION).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_HEADER).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_BODY).should('be.visible'); + cy.log('title'); - cy.log('title'); - cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_TITLE).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_TITLE).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_CREATED_BY).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_UPDATED_BY).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_TITLE).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_TITLE).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_CREATED_BY).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_UPDATED_BY).should('be.visible'); - cy.log('about'); + cy.log('about'); - cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_ABOUT_SECTION_HEADER) - .should('be.visible') - .and('contain.text', 'About'); - cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_ABOUT_SECTION_CONTENT).should('be.visible'); - toggleRulePreviewAboutSection(); + cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_ABOUT_SECTION_HEADER) + .should('be.visible') + .and('contain.text', 'About'); + cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_ABOUT_SECTION_CONTENT).should('be.visible'); + toggleRulePreviewAboutSection(); - cy.log('definition'); + cy.log('definition'); - toggleRulePreviewDefinitionSection(); - cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_DEFINITION_SECTION_HEADER) - .should('be.visible') - .and('contain.text', 'Definition'); - cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_DEFINITION_SECTION_CONTENT).should( - 'be.visible' - ); - toggleRulePreviewDefinitionSection(); + toggleRulePreviewDefinitionSection(); + cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_DEFINITION_SECTION_HEADER) + .should('be.visible') + .and('contain.text', 'Definition'); + cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_DEFINITION_SECTION_CONTENT).should('be.visible'); + toggleRulePreviewDefinitionSection(); - cy.log('schedule'); + cy.log('schedule'); - toggleRulePreviewScheduleSection(); - cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_SCHEDULE_SECTION_HEADER) - .should('be.visible') - .and('contain.text', 'Schedule'); - cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_SCHEDULE_SECTION_CONTENT).should('be.visible'); - toggleRulePreviewScheduleSection(); + toggleRulePreviewScheduleSection(); + cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_SCHEDULE_SECTION_HEADER) + .should('be.visible') + .and('contain.text', 'Schedule'); + cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_SCHEDULE_SECTION_CONTENT).should('be.visible'); + toggleRulePreviewScheduleSection(); - cy.log('footer'); - cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_FOOTER).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_FOOTER).should('be.visible'); - }); + cy.log('footer'); + cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_FOOTER).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_FOOTER).should('be.visible'); }); - } -); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel.cy.ts index 11fc62c7f68f7..a166a72148fd3 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel.cy.ts @@ -65,173 +65,167 @@ import { getNewRule } from '../../../../objects/rule'; import { ALERTS_URL } from '../../../../urls/navigation'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; -describe( - 'Alert details expandable flyout right panel', - { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, - () => { - const rule = getNewRule(); - - beforeEach(() => { - cleanKibana(); - login(); - createRule(rule); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - }); - - it('should display header and footer basics', () => { - expandFirstAlertExpandableFlyout(); - - cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE).should('be.visible').and('have.text', rule.name); - - cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_CHAT_BUTTON).should('be.visible'); - - cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_STATUS).should('be.visible'); - - cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_RISK_SCORE).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_RISK_SCORE_VALUE) - .should('be.visible') - .and('have.text', rule.risk_score); - - cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_SEVERITY).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_SEVERITY_VALUE) - .should('be.visible') - .and('have.text', upperFirst(rule.severity)); - - cy.log('Verify all 3 tabs are visible'); - - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB) - .should('be.visible') - .and('have.text', 'Overview'); - cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB).should('be.visible').and('have.text', 'Table'); - cy.get(DOCUMENT_DETAILS_FLYOUT_JSON_TAB).should('be.visible').and('have.text', 'JSON'); - - cy.log('Verify the expand/collapse button is visible and functionality works'); - - expandDocumentDetailsExpandableFlyoutLeftSection(); - cy.get(DOCUMENT_DETAILS_FLYOUT_COLLAPSE_DETAILS_BUTTON) - .should('be.visible') - .and('have.text', 'Collapse details'); - - collapseDocumentDetailsExpandableFlyoutLeftSection(); - cy.get(DOCUMENT_DETAILS_FLYOUT_EXPAND_DETAILS_BUTTON) - .should('be.visible') - .and('have.text', 'Expand details'); - - cy.log('Verify the take action button is visible on all tabs'); - - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON).should('be.visible'); - - openTableTab(); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON).should('be.visible'); - - openJsonTab(); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON).should('be.visible'); - }); - - // TODO this will change when add to existing case is improved - // https://github.com/elastic/security-team/issues/6298 - it('should add to existing case', () => { - navigateToCasesPage(); - createNewCaseFromCases(); - - cy.get(CASE_DETAILS_PAGE_TITLE).should('be.visible').and('have.text', 'case'); - navigateToAlertsPage(); - expandFirstAlertExpandableFlyout(); - openTakeActionButtonAndSelectItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_EXISTING_CASE); - - cy.get(EXISTING_CASE_SELECT_BUTTON).should('be.visible').contains('Select').click(); - cy.get(VIEW_CASE_TOASTER_LINK).should('be.visible').and('contain.text', 'View case'); - }); - - // TODO this will change when add to new case is improved - // https://github.com/elastic/security-team/issues/6298 - it('should add to new case', () => { - expandFirstAlertExpandableFlyout(); - openTakeActionButtonAndSelectItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE); - - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_NAME_INPUT).type('case'); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_DESCRIPTION_INPUT).type( - 'case description' - ); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_CREATE_BUTTON).click(); - - cy.get(VIEW_CASE_TOASTER_LINK).should('be.visible').and('contain.text', 'View case'); - }); - - it('should mark as acknowledged', () => { - cy.get(ALERT_CHECKBOX).should('have.length', 2); - - expandFirstAlertExpandableFlyout(); - openTakeActionButtonAndSelectItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_MARK_AS_ACKNOWLEDGED); +describe('Alert details expandable flyout right panel', () => { + const rule = getNewRule(); - // TODO figure out how to verify the toasts pops up - // cy.get(KIBANA_TOAST) - // .should('be.visible') - // .and('have.text', 'Successfully marked 1 alert as acknowledged.'); - cy.get(ALERT_CHECKBOX).should('have.length', 1); - }); + beforeEach(() => { + cleanKibana(); + login(); + createRule(rule); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + }); - it('should mark as closed', () => { - cy.get(ALERT_CHECKBOX).should('have.length', 2); + it('should display header and footer basics', () => { + expandFirstAlertExpandableFlyout(); - expandFirstAlertExpandableFlyout(); - openTakeActionButtonAndSelectItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_MARK_AS_CLOSED); + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE).should('be.visible').and('have.text', rule.name); + + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_CHAT_BUTTON).should('be.visible'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_STATUS).should('be.visible'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_RISK_SCORE).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_RISK_SCORE_VALUE) + .should('be.visible') + .and('have.text', rule.risk_score); - // TODO figure out how to verify the toasts pops up - // cy.get(KIBANA_TOAST).should('be.visible').and('have.text', 'Successfully closed 1 alert.'); - cy.get(ALERT_CHECKBOX).should('have.length', 1); - }); + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_SEVERITY).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_SEVERITY_VALUE) + .should('be.visible') + .and('have.text', upperFirst(rule.severity)); - // these actions are now grouped together as we're not really testing their functionality but just the existence of the option in the dropdown - it('should test other action within take action dropdown', () => { - expandFirstAlertExpandableFlyout(); + cy.log('Verify all 3 tabs are visible'); - cy.log('should add endpoint exception'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB).should('be.visible').and('have.text', 'Overview'); + cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB).should('be.visible').and('have.text', 'Table'); + cy.get(DOCUMENT_DETAILS_FLYOUT_JSON_TAB).should('be.visible').and('have.text', 'JSON'); + + cy.log('Verify the expand/collapse button is visible and functionality works'); + + expandDocumentDetailsExpandableFlyoutLeftSection(); + cy.get(DOCUMENT_DETAILS_FLYOUT_COLLAPSE_DETAILS_BUTTON) + .should('be.visible') + .and('have.text', 'Collapse details'); - // TODO figure out why this option is disabled in Cypress but not running the app locally - // https://github.com/elastic/security-team/issues/6300 - openTakeActionButton(); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_ENDPOINT_EXCEPTION).should('be.disabled'); + collapseDocumentDetailsExpandableFlyoutLeftSection(); + cy.get(DOCUMENT_DETAILS_FLYOUT_EXPAND_DETAILS_BUTTON) + .should('be.visible') + .and('have.text', 'Expand details'); + + cy.log('Verify the take action button is visible on all tabs'); - cy.log('should add rule exception'); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON).should('be.visible'); - // TODO this isn't fully testing the add rule exception yet - // https://github.com/elastic/security-team/issues/6301 - selectTakeActionItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION_FLYOUT_HEADER).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION_FLYOUT_CANCEL_BUTTON) - .should('be.visible') - .click(); + openTableTab(); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON).should('be.visible'); - // cy.log('should isolate host'); + openJsonTab(); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_TAKE_ACTION_BUTTON).should('be.visible'); + }); + + // TODO this will change when add to existing case is improved + // https://github.com/elastic/security-team/issues/6298 + it('should add to existing case', () => { + navigateToCasesPage(); + createNewCaseFromCases(); + + cy.get(CASE_DETAILS_PAGE_TITLE).should('be.visible').and('have.text', 'case'); + navigateToAlertsPage(); + expandFirstAlertExpandableFlyout(); + openTakeActionButtonAndSelectItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_EXISTING_CASE); + + cy.get(EXISTING_CASE_SELECT_BUTTON).should('be.visible').contains('Select').click(); + cy.get(VIEW_CASE_TOASTER_LINK).should('be.visible').and('contain.text', 'View case'); + }); + + // TODO this will change when add to new case is improved + // https://github.com/elastic/security-team/issues/6298 + it('should add to new case', () => { + expandFirstAlertExpandableFlyout(); + openTakeActionButtonAndSelectItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE); + + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_NAME_INPUT).type('case'); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_DESCRIPTION_INPUT).type( + 'case description' + ); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_CREATE_BUTTON).click(); + + cy.get(VIEW_CASE_TOASTER_LINK).should('be.visible').and('contain.text', 'View case'); + }); + + it('should mark as acknowledged', () => { + cy.get(ALERT_CHECKBOX).should('have.length', 2); + + expandFirstAlertExpandableFlyout(); + openTakeActionButtonAndSelectItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_MARK_AS_ACKNOWLEDGED); + + // TODO figure out how to verify the toasts pops up + // cy.get(KIBANA_TOAST) + // .should('be.visible') + // .and('have.text', 'Successfully marked 1 alert as acknowledged.'); + cy.get(ALERT_CHECKBOX).should('have.length', 1); + }); - // TODO figure out why isolate host isn't showing up in the dropdown - // https://github.com/elastic/security-team/issues/6302 - // openTakeActionButton(); - // cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ISOLATE_HOST).should('be.visible'); + it('should mark as closed', () => { + cy.get(ALERT_CHECKBOX).should('have.length', 2); - cy.log('should respond'); + expandFirstAlertExpandableFlyout(); + openTakeActionButtonAndSelectItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_MARK_AS_CLOSED); - // TODO this will change when respond is improved - // https://github.com/elastic/security-team/issues/6303 - openTakeActionButton(); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_RESPOND).should('be.disabled'); - - cy.log('should investigate in timeline'); - - selectTakeActionItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE); - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE_SECTION) - .first() - .within(() => - cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE_ENTRY).should('be.visible') - ); - }); - } -); + // TODO figure out how to verify the toasts pops up + // cy.get(KIBANA_TOAST).should('be.visible').and('have.text', 'Successfully closed 1 alert.'); + cy.get(ALERT_CHECKBOX).should('have.length', 1); + }); + + // these actions are now grouped together as we're not really testing their functionality but just the existence of the option in the dropdown + it('should test other action within take action dropdown', () => { + expandFirstAlertExpandableFlyout(); + + cy.log('should add endpoint exception'); + + // TODO figure out why this option is disabled in Cypress but not running the app locally + // https://github.com/elastic/security-team/issues/6300 + openTakeActionButton(); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_ENDPOINT_EXCEPTION).should('be.disabled'); + + cy.log('should add rule exception'); + + // TODO this isn't fully testing the add rule exception yet + // https://github.com/elastic/security-team/issues/6301 + selectTakeActionItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION_FLYOUT_HEADER).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_RULE_EXCEPTION_FLYOUT_CANCEL_BUTTON) + .should('be.visible') + .click(); + + // cy.log('should isolate host'); + + // TODO figure out why isolate host isn't showing up in the dropdown + // https://github.com/elastic/security-team/issues/6302 + // openTakeActionButton(); + // cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ISOLATE_HOST).should('be.visible'); + + cy.log('should respond'); + + // TODO this will change when respond is improved + // https://github.com/elastic/security-team/issues/6303 + openTakeActionButton(); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_RESPOND).should('be.disabled'); + + cy.log('should investigate in timeline'); + + selectTakeActionItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE_SECTION) + .first() + .within(() => + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE_ENTRY).should('be.visible') + ); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_json_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_json_tab.cy.ts index 7bd86e509ac18..5a1c9703ae83d 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_json_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_json_tab.cy.ts @@ -16,25 +16,21 @@ import { getNewRule } from '../../../../objects/rule'; import { ALERTS_URL } from '../../../../urls/navigation'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; -describe( - 'Alert details expandable flyout right panel json tab', - { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, - () => { - beforeEach(() => { - cleanKibana(); - login(); - createRule(getNewRule()); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); - openJsonTab(); - }); +describe('Alert details expandable flyout right panel json tab', () => { + beforeEach(() => { + cleanKibana(); + login(); + createRule(getNewRule()); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + openJsonTab(); + }); - it('should display the json component', () => { - // the json component is rendered within a dom element with overflow, so Cypress isn't finding it - // this next line is a hack that vertically scrolls down to ensure Cypress finds it - scrollWithinDocumentDetailsExpandableFlyoutRightSection(0, 7000); - cy.get(DOCUMENT_DETAILS_FLYOUT_JSON_TAB_CONTENT).should('be.visible'); - }); - } -); + it('should display the json component', () => { + // the json component is rendered within a dom element with overflow, so Cypress isn't finding it + // this next line is a hack that vertically scrolls down to ensure Cypress finds it + scrollWithinDocumentDetailsExpandableFlyoutRightSection(0, 7000); + cy.get(DOCUMENT_DETAILS_FLYOUT_JSON_TAB_CONTENT).should('be.visible'); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts index 9dc5dccbddcc3..ec8328cbc961f 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts @@ -69,292 +69,287 @@ import { DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_USER_DETAILS, } from '../../../../screens/expandable_flyout/alert_details_left_panel_entities_tab'; -describe( - 'Alert details expandable flyout right panel overview tab', - { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, - () => { - const rule = getNewRule(); - - beforeEach(() => { - cleanKibana(); - login(); - createRule(rule); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); +describe('Alert details expandable flyout right panel overview tab', () => { + const rule = getNewRule(); + + beforeEach(() => { + cleanKibana(); + login(); + createRule(rule); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + }); + + describe('about section', () => { + it('should display about section', () => { + cy.log('header and content'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ABOUT_SECTION_HEADER) + .should('be.visible') + .and('have.text', 'About'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ABOUT_SECTION_CONTENT).should('be.visible'); + + cy.log('description'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_TITLE) + .should('be.visible') + .and('contain.text', 'Rule description'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_TITLE) + .should('be.visible') + .within(() => { + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_OPEN_RULE_PREVIEW_BUTTON) + .should('be.visible') + .and('have.text', 'Rule summary'); + }); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_DETAILS) + .should('be.visible') + .and('have.text', rule.description); + + cy.log('reason'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_TITLE) + .should('be.visible') + .and('have.text', 'Alert reason'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_DETAILS) + .should('be.visible') + .and('contain.text', rule.name); + + cy.log('mitre attack'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_TITLE) + .should('be.visible') + // @ts-ignore + .and('contain.text', rule.threat[0].framework); + + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_DETAILS) + .should('be.visible') + // @ts-ignore + .and('contain.text', rule.threat[0].technique[0].name) + // @ts-ignore + .and('contain.text', rule.threat[0].tactic.name); }); + }); - describe('about section', () => { - it('should display about section', () => { - cy.log('header and content'); - - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ABOUT_SECTION_HEADER) - .should('be.visible') - .and('have.text', 'About'); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ABOUT_SECTION_CONTENT).should('be.visible'); - - cy.log('description'); - - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_TITLE) - .should('be.visible') - .and('contain.text', 'Rule description'); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_TITLE) - .should('be.visible') - .within(() => { - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_OPEN_RULE_PREVIEW_BUTTON) - .should('be.visible') - .and('have.text', 'Rule summary'); - }); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_DETAILS) - .should('be.visible') - .and('have.text', rule.description); - - cy.log('reason'); - - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_TITLE) - .should('be.visible') - .and('have.text', 'Alert reason'); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_DETAILS) - .should('be.visible') - .and('contain.text', rule.name); - - cy.log('mitre attack'); - - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_TITLE) - .should('be.visible') - // @ts-ignore - .and('contain.text', rule.threat[0].framework); - - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_DETAILS) - .should('be.visible') - // @ts-ignore - .and('contain.text', rule.threat[0].technique[0].name) - // @ts-ignore - .and('contain.text', rule.threat[0].tactic.name); - }); + describe('visualizations section', () => { + it('should display analyzer and session previews', () => { + toggleOverviewTabAboutSection(); + toggleOverviewTabVisualizationsSection(); + + cy.log('analyzer graph preview'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ANALYZER_TREE).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ANALYZER_TREE).should('be.visible'); + + cy.log('session view preview'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_SESSION_PREVIEW).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_SESSION_PREVIEW).should('be.visible'); }); + }); + + describe('investigation section', () => { + it('should display investigation section', () => { + toggleOverviewTabAboutSection(); + toggleOverviewTabInvestigationSection(); + + cy.log('header and content'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_SECTION_HEADER) + .should('be.visible') + .and('have.text', 'Investigation'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_SECTION_CONTENT).should( + 'be.visible' + ); + + cy.log('investigation guide button'); - describe('visualizations section', () => { - it('should display analyzer and session previews', () => { - toggleOverviewTabAboutSection(); - toggleOverviewTabVisualizationsSection(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_GUIDE_BUTTON) + .should('be.visible') + .and('have.text', 'Investigation guide'); - cy.log('analyzer graph preview'); + cy.log('should navigate to left Investigation tab'); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ANALYZER_TREE).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_ANALYZER_TREE).should('be.visible'); + clickInvestigationGuideButton(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB_CONTENT).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB_CONTENT).should('be.visible'); - cy.log('session view preview'); + cy.log('highlighted fields'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_HEADER_TITLE) + .should('be.visible') + .and('have.text', 'Highlighted fields'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_DETAILS).should('be.visible'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_TABLE_FIELD_CELL) + .should('be.visible') + .and('contain.text', 'host.name'); + const hostNameCell = + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_TABLE_VALUE_CELL('siem-kibana'); + cy.get(hostNameCell).should('be.visible').and('have.text', 'siem-kibana'); + + cy.get(hostNameCell).click(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_HOST_DETAILS).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_HOST_DETAILS).should('be.visible'); + + collapseDocumentDetailsExpandableFlyoutLeftSection(); + + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_TABLE_FIELD_CELL) + .should('be.visible') + .and('contain.text', 'user.name'); + const userNameCell = + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_TABLE_VALUE_CELL('test'); + cy.get(userNameCell).should('be.visible').and('have.text', 'test'); + + cy.get(userNameCell).click(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_USER_DETAILS).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_USER_DETAILS).should('be.visible'); + }); + }); + + describe('insights section', () => { + it('should display entities section', () => { + toggleOverviewTabAboutSection(); + toggleOverviewTabInsightsSection(); + + cy.log('header and content'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITIES_HEADER).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITIES_HEADER) + .should('be.visible') + .and('have.text', 'Entities'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITIES_CONTENT).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITY_PANEL_HEADER).should( + 'be.visible' + ); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITY_PANEL_CONTENT).should( + 'be.visible' + ); + + cy.log('should navigate to left panel Entities tab'); + + clickEntitiesViewAllButton(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT).should('be.visible'); + }); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_SESSION_PREVIEW).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_SESSION_PREVIEW).should('be.visible'); - }); + // TODO: skipping this due to flakiness + it.skip('should display threat intelligence section', () => { + toggleOverviewTabAboutSection(); + toggleOverviewTabInsightsSection(); + + cy.log('header and content'); + + cy.get( + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_HEADER + ).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_HEADER) + .should('be.visible') + .and('have.text', 'Threat Intelligence'); + cy.get( + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_CONTENT + ).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_CONTENT) + .should('be.visible') + .within(() => { + // threat match detected + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_VALUES) + .eq(0) + .should('be.visible') + .and('have.text', '0 threat match detected'); // TODO work on getting proper IoC data to get proper data here + + // field with threat enrichement + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_VALUES) + .eq(1) + .should('be.visible') + .and('have.text', '0 field enriched with threat intelligence'); // TODO work on getting proper IoC data to get proper data here + }); + + cy.log('should navigate to left panel Threat Intelligence tab'); + + clickThreatIntelligenceViewAllButton(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT).should('be.visible'); // TODO update when we can navigate to Threat Intelligence sub tab directly }); - describe('investigation section', () => { - it('should display investigation section', () => { - toggleOverviewTabAboutSection(); - toggleOverviewTabInvestigationSection(); - - cy.log('header and content'); - - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_SECTION_HEADER) - .should('be.visible') - .and('have.text', 'Investigation'); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_SECTION_CONTENT).should( - 'be.visible' - ); - - cy.log('investigation guide button'); - - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INVESTIGATION_GUIDE_BUTTON) - .should('be.visible') - .and('have.text', 'Investigation guide'); - - cy.log('should navigate to left Investigation tab'); - - clickInvestigationGuideButton(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB_CONTENT).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INVESTIGATION_TAB_CONTENT).should('be.visible'); - - cy.log('highlighted fields'); - - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_HEADER_TITLE) - .should('be.visible') - .and('have.text', 'Highlighted fields'); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_DETAILS).should( - 'be.visible' - ); - - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_TABLE_FIELD_CELL) - .should('be.visible') - .and('contain.text', 'host.name'); - const hostNameCell = - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_TABLE_VALUE_CELL('siem-kibana'); - cy.get(hostNameCell).should('be.visible').and('have.text', 'siem-kibana'); - - cy.get(hostNameCell).click(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_HOST_DETAILS).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_HOST_DETAILS).should('be.visible'); - - collapseDocumentDetailsExpandableFlyoutLeftSection(); - - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_TABLE_FIELD_CELL) - .should('be.visible') - .and('contain.text', 'user.name'); - const userNameCell = - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_HIGHLIGHTED_FIELDS_TABLE_VALUE_CELL('test'); - cy.get(userNameCell).should('be.visible').and('have.text', 'test'); - - cy.get(userNameCell).click(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_USER_DETAILS).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_USER_DETAILS).should('be.visible'); - }); + // TODO: skipping this due to flakiness + it.skip('should display correlations section', () => { + cy.log('link the alert to a new case'); + + createNewCaseFromExpandableFlyout(); + + toggleOverviewTabAboutSection(); + toggleOverviewTabInsightsSection(); + + cy.log('header and content'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_HEADER).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_HEADER) + .should('be.visible') + .and('have.text', 'Correlations'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_CONTENT).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_CONTENT) + .should('be.visible') + .within(() => { + // TODO the order in which these appear is not deterministic currently, hence this can cause flakiness + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES) + .eq(0) + .should('be.visible') + .and('have.text', '1 alert related by ancestry'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES) + .eq(1) + .should('be.visible') + .and('have.text', '1 related case'); + // cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES) + // .eq(2) + // .should('be.visible') + // .and('have.text', '1 alert related by the same source event'); // TODO work on getting proper data to display some same source data here + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES) + .eq(2) + .should('be.visible') + .and('have.text', '1 alert related by session'); + }); + + cy.log('should navigate to left panel Correlations tab'); + + clickCorrelationsViewAllButton(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT).should('be.visible'); // TODO update when we can navigate to Correlations sub tab directly }); - describe('insights section', () => { - it('should display entities section', () => { - toggleOverviewTabAboutSection(); - toggleOverviewTabInsightsSection(); - - cy.log('header and content'); - - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITIES_HEADER).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITIES_HEADER) - .should('be.visible') - .and('have.text', 'Entities'); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITIES_CONTENT).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITY_PANEL_HEADER).should( - 'be.visible' - ); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_ENTITY_PANEL_CONTENT).should( - 'be.visible' - ); - - cy.log('should navigate to left panel Entities tab'); - - clickEntitiesViewAllButton(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT).should('be.visible'); - }); - - it('should display threat intelligence section', () => { - toggleOverviewTabAboutSection(); - toggleOverviewTabInsightsSection(); - - cy.log('header and content'); - - cy.get( - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_HEADER - ).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_HEADER) - .should('be.visible') - .and('have.text', 'Threat Intelligence'); - cy.get( - DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_CONTENT - ).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_CONTENT) - .should('be.visible') - .within(() => { - // threat match detected - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_VALUES) - .eq(0) - .should('be.visible') - .and('have.text', '0 threat match detected'); // TODO work on getting proper IoC data to get proper data here - - // field with threat enrichement - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_THREAT_INTELLIGENCE_VALUES) - .eq(1) - .should('be.visible') - .and('have.text', '0 field enriched with threat intelligence'); // TODO work on getting proper IoC data to get proper data here - }); - - cy.log('should navigate to left panel Threat Intelligence tab'); - - clickThreatIntelligenceViewAllButton(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT).should('be.visible'); // TODO update when we can navigate to Threat Intelligence sub tab directly - }); - - // TODO: skipping this due to flakiness - it.skip('should display correlations section', () => { - cy.log('link the alert to a new case'); - - createNewCaseFromExpandableFlyout(); - - toggleOverviewTabAboutSection(); - toggleOverviewTabInsightsSection(); - - cy.log('header and content'); - - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_HEADER).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_HEADER) - .should('be.visible') - .and('have.text', 'Correlations'); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_CONTENT).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_CONTENT) - .should('be.visible') - .within(() => { - // TODO the order in which these appear is not deterministic currently, hence this can cause flakiness - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES) - .eq(0) - .should('be.visible') - .and('have.text', '1 alert related by ancestry'); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES) - .eq(1) - .should('be.visible') - .and('have.text', '1 related case'); - // cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES) - // .eq(2) - // .should('be.visible') - // .and('have.text', '1 alert related by the same source event'); // TODO work on getting proper data to display some same source data here - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_CORRELATIONS_VALUES) - .eq(2) - .should('be.visible') - .and('have.text', '1 alert related by session'); - }); - - cy.log('should navigate to left panel Correlations tab'); - - clickCorrelationsViewAllButton(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT).should('be.visible'); // TODO update when we can navigate to Correlations sub tab directly - }); - - // TODO work on getting proper data to make the prevalence section work here - // we need to generate enough data to have at least one field with prevalence - it.skip('should display prevalence section', () => { - toggleOverviewTabAboutSection(); - toggleOverviewTabInsightsSection(); - - cy.log('header and content'); - - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_HEADER).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_HEADER) - .should('be.visible') - .and('have.text', 'Prevalence'); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_CONTENT).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_CONTENT) - .should('be.visible') - .within(() => { - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_VALUES) - .should('be.visible') - .and('have.text', 'is uncommon'); - }); - - cy.log('should navigate to left panel Prevalence tab'); - - clickPrevalenceViewAllButton(); - cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT).should('be.visible'); // TODO update when we can navigate to Prevalence sub tab directly - }); + // TODO work on getting proper data to make the prevalence section work here + // we need to generate enough data to have at least one field with prevalence + it.skip('should display prevalence section', () => { + toggleOverviewTabAboutSection(); + toggleOverviewTabInsightsSection(); + + cy.log('header and content'); + + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_HEADER).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_HEADER) + .should('be.visible') + .and('have.text', 'Prevalence'); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_CONTENT).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_CONTENT) + .should('be.visible') + .within(() => { + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_INSIGHTS_PREVALENCE_VALUES) + .should('be.visible') + .and('have.text', 'is uncommon'); + }); + + cy.log('should navigate to left panel Prevalence tab'); + + clickPrevalenceViewAllButton(); + cy.get(DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB_ENTITIES_CONTENT).should('be.visible'); // TODO update when we can navigate to Prevalence sub tab directly }); + }); - describe('response section', () => { - it('should display empty message', () => { - toggleOverviewTabAboutSection(); - toggleOverviewTabResponseSection(); + describe('response section', () => { + it('should display empty message', () => { + toggleOverviewTabAboutSection(); + toggleOverviewTabResponseSection(); - cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_RESPONSE_SECTION_EMPTY_RESPONSE).should( - 'be.visible' - ); - }); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_RESPONSE_SECTION_EMPTY_RESPONSE).should( + 'be.visible' + ); }); - } -); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_table_tab.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_table_tab.cy.ts index 9e30ba52b3cdd..ff89e16a02b03 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_table_tab.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_table_tab.cy.ts @@ -31,52 +31,48 @@ import { getNewRule } from '../../../../objects/rule'; import { ALERTS_URL } from '../../../../urls/navigation'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; -describe( - 'Alert details expandable flyout right panel table tab', - { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, - () => { - beforeEach(() => { - cleanKibana(); - login(); - createRule(getNewRule()); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - expandFirstAlertExpandableFlyout(); - openTableTab(); - }); +describe('Alert details expandable flyout right panel table tab', () => { + beforeEach(() => { + cleanKibana(); + login(); + createRule(getNewRule()); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + openTableTab(); + }); - it('should display and filter the table', () => { - cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_TIMESTAMP_ROW).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ID_ROW).should('be.visible'); - filterTableTabTable('timestamp'); - cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_TIMESTAMP_ROW).should('be.visible'); - clearFilterTableTabTable(); - }); + it('should display and filter the table', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_TIMESTAMP_ROW).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ID_ROW).should('be.visible'); + filterTableTabTable('timestamp'); + cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_TIMESTAMP_ROW).should('be.visible'); + clearFilterTableTabTable(); + }); - it('should test cell actions', () => { - cy.log('cell actions filter in'); + it('should test cell actions', () => { + cy.log('cell actions filter in'); - filterInTableTabTable(); - cy.get(FILTER_BADGE).first().should('contain.text', '@timestamp:'); - removeKqlFilter(); + filterInTableTabTable(); + cy.get(FILTER_BADGE).first().should('contain.text', '@timestamp:'); + removeKqlFilter(); - cy.log('cell actions filter out'); + cy.log('cell actions filter out'); - filterOutTableTabTable(); - cy.get(FILTER_BADGE).first().should('contain.text', 'NOT @timestamp:'); - removeKqlFilter(); + filterOutTableTabTable(); + cy.get(FILTER_BADGE).first().should('contain.text', 'NOT @timestamp:'); + removeKqlFilter(); - cy.log('cell actions add to timeline'); + cy.log('cell actions add to timeline'); - addToTimelineTableTabTable(); - openActiveTimeline(); - cy.get(PROVIDER_BADGE).first().should('contain.text', '@timestamp'); - closeTimeline(); + addToTimelineTableTabTable(); + openActiveTimeline(); + cy.get(PROVIDER_BADGE).first().should('contain.text', '@timestamp'); + closeTimeline(); - cy.log('cell actions copy to clipboard'); + cy.log('cell actions copy to clipboard'); - copyToClipboardTableTabTable(); - cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_COPY_TO_CLIPBOARD).should('be.visible'); - }); - } -); + copyToClipboardTableTabTable(); + cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_ROW_CELL_COPY_TO_CLIPBOARD).should('be.visible'); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_url_sync.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_url_sync.cy.ts index fa268bfdfa341..e926e93e63301 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_url_sync.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_url_sync.cy.ts @@ -15,43 +15,39 @@ import { closeFlyout } from '../../../../tasks/expandable_flyout/alert_details_r import { expandFirstAlertExpandableFlyout } from '../../../../tasks/expandable_flyout/common'; import { DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE } from '../../../../screens/expandable_flyout/alert_details_right_panel'; -describe( - 'Expandable flyout state sync', - { env: { ftrConfig: { enableExperimental: ['securityFlyoutEnabled'] } } }, - () => { - const rule = getNewRule(); +describe('Expandable flyout state sync', () => { + const rule = getNewRule(); - beforeEach(() => { - cleanKibana(); - login(); - createRule(rule); - visit(ALERTS_URL); - waitForAlertsToPopulate(); - }); + beforeEach(() => { + cleanKibana(); + login(); + createRule(rule); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + }); - it('should test flyout url sync', () => { - cy.url().should('not.include', 'eventFlyout'); + it('should test flyout url sync', () => { + cy.url().should('not.include', 'eventFlyout'); - expandFirstAlertExpandableFlyout(); + expandFirstAlertExpandableFlyout(); - cy.log('should serialize its state to url'); + cy.log('should serialize its state to url'); - cy.url().should('include', 'eventFlyout'); - cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE).should('be.visible').and('have.text', rule.name); + cy.url().should('include', 'eventFlyout'); + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE).should('be.visible').and('have.text', rule.name); - cy.log('should reopen the flyout after browser refresh'); + cy.log('should reopen the flyout after browser refresh'); - cy.reload(); - waitForAlertsToPopulate(); + cy.reload(); + waitForAlertsToPopulate(); - cy.url().should('include', 'eventFlyout'); - cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE).should('be.visible').and('have.text', rule.name); + cy.url().should('include', 'eventFlyout'); + cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE).should('be.visible').and('have.text', rule.name); - cy.log('should clear the url state when flyout is closed'); + cy.log('should clear the url state when flyout is closed'); - closeFlyout(); + closeFlyout(); - cy.url().should('not.include', 'eventFlyout'); - }); - } -); + cy.url().should('not.include', 'eventFlyout'); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/investigate_in_timeline.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/investigate_in_timeline.cy.ts index 5a8c382bdf4af..5ef959899178a 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/investigate_in_timeline.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/investigate_in_timeline.cy.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { disableExpandableFlyout } from '../../../tasks/api_calls/kibana_advanced_settings'; import { getNewRule } from '../../../objects/rule'; import { PROVIDER_BADGE, QUERY_TAB_BUTTON, TIMELINE_TITLE } from '../../../screens/timeline'; import { FILTER_BADGE } from '../../../screens/alerts'; @@ -53,6 +54,7 @@ describe('Investigate in timeline', () => { describe('From alerts details flyout', () => { beforeEach(() => { login(); + disableExpandableFlyout(); visit(ALERTS_URL); waitForAlertsToPopulate(); expandFirstAlert(); diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/kibana_advanced_settings.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/kibana_advanced_settings.ts index 0cadb50d72fe1..6256539beca1d 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/api_calls/kibana_advanced_settings.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/kibana_advanced_settings.ts @@ -27,3 +27,8 @@ export const enableRelatedIntegrations = () => { export const disableRelatedIntegrations = () => { kibanaSettings(relatedIntegrationsBody(false)); }; + +export const disableExpandableFlyout = () => { + const body = { changes: { 'securitySolution:enableExpandableFlyout': false } }; + kibanaSettings(body); +}; diff --git a/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx b/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx index cd226d4347af6..2e9493dc6ff42 100644 --- a/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/control_columns/row_action/index.tsx @@ -10,6 +10,8 @@ import React, { useCallback, useMemo } from 'react'; import { useDispatch } from 'react-redux'; import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; import { dataTableActions } from '@kbn/securitysolution-data-table'; +import { useUiSetting$ } from '@kbn/kibana-react-plugin/public'; +import { ENABLE_EXPANDABLE_FLYOUT_SETTING } from '../../../../../common/constants'; import { RightPanelKey } from '../../../../flyout/right'; import type { SetEventsDeleted, @@ -21,7 +23,6 @@ import { getMappedNonEcsValue } from '../../../../timelines/components/timeline/ import type { TimelineItem, TimelineNonEcsData } from '../../../../../common/search_strategy'; import type { ColumnHeaderOptions, OnRowSelected } from '../../../../../common/types/timeline'; -import { useIsExperimentalFeatureEnabled } from '../../../hooks/use_experimental_features'; type Props = EuiDataGridCellValueElementProps & { columnHeaders: ColumnHeaderOptions[]; @@ -70,7 +71,7 @@ const RowActionComponent = ({ const { openFlyout } = useExpandableFlyoutContext(); const dispatch = useDispatch(); - const isSecurityFlyoutEnabled = useIsExperimentalFeatureEnabled('securityFlyoutEnabled'); + const [isSecurityFlyoutEnabled] = useUiSetting$(ENABLE_EXPANDABLE_FLYOUT_SETTING); const columnValues = useMemo( () => diff --git a/x-pack/plugins/security_solution/public/detections/pages/alerts/alert_details_redirect.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/alerts/alert_details_redirect.test.tsx index 620f562a7bf5a..94a96a0958725 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/alerts/alert_details_redirect.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/alerts/alert_details_redirect.test.tsx @@ -156,7 +156,7 @@ describe('AlertDetailsRedirect', () => { const [{ search, pathname }] = historyMock.replace.mock.lastCall; - expect(search as string).toMatch(/eventFlyout.*right/); + expect(search as string).toMatch(/eventFlyout.*/); expect(pathname).toEqual(ALERTS_PATH); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/alerts/alert_details_redirect.tsx b/x-pack/plugins/security_solution/public/detections/pages/alerts/alert_details_redirect.tsx index 0786b4cdf3824..41516d06942ff 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/alerts/alert_details_redirect.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/alerts/alert_details_redirect.tsx @@ -12,12 +12,16 @@ import { Redirect, useLocation, useParams } from 'react-router-dom'; import moment from 'moment'; import { encode } from '@kbn/rison'; import { ALERT_WORKFLOW_STATUS } from '@kbn/rule-data-utils'; +import { useUiSetting$ } from '@kbn/kibana-react-plugin/public'; import type { FilterItemObj } from '../../../common/components/filter_group/types'; -import { ALERTS_PATH, DEFAULT_ALERTS_INDEX } from '../../../../common/constants'; +import { + ALERTS_PATH, + DEFAULT_ALERTS_INDEX, + ENABLE_EXPANDABLE_FLYOUT_SETTING, +} from '../../../../common/constants'; import { URL_PARAM_KEY } from '../../../common/hooks/use_url_state'; import { inputsSelectors } from '../../../common/store'; import { formatPageFilterSearchParam } from '../../../../common/utils/format_page_filter_search_param'; -import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; import { resolveFlyoutParams } from './utils'; import { FLYOUT_URL_PARAM } from '../../../flyout/shared/hooks/url/use_sync_flyout_state_with_url'; @@ -70,7 +74,7 @@ export const AlertDetailsRedirect = () => { const currentFlyoutParams = searchParams.get(FLYOUT_URL_PARAM); - const isSecurityFlyoutEnabled = useIsExperimentalFeatureEnabled('securityFlyoutEnabled'); + const [isSecurityFlyoutEnabled] = useUiSetting$(ENABLE_EXPANDABLE_FLYOUT_SETTING); const urlParams = new URLSearchParams({ [URL_PARAM_KEY.appQuery]: kqlAppQuery, diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/automated_response_actions.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/automated_response_actions.cy.ts index a62e877f018e4..5bb5021a3229c 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/automated_response_actions.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/automated_response_actions.cy.ts @@ -11,7 +11,7 @@ import { closeAllToasts } from '../../tasks/toasts'; import { toggleRuleOffAndOn, visitRuleAlerts } from '../../tasks/isolate'; import { cleanupRule, loadRule } from '../../tasks/api_fixtures'; import { login } from '../../tasks/login'; -import { loadPage } from '../../tasks/common'; +import { disableExpandableFlyoutAdvancedSettings, loadPage } from '../../tasks/common'; import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy'; import { createAgentPolicyTask, getEndpointIntegrationVersion } from '../../tasks/fleet'; import { changeAlertsFilter } from '../../tasks/alerts'; @@ -60,6 +60,7 @@ describe('Automated Response Actions', () => { beforeEach(() => { login(); + disableExpandableFlyoutAdvancedSettings(); }); describe('From alerts', () => { diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/isolate.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/isolate.cy.ts index ff4adacc9b73f..5ec6ed11c80a4 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/isolate.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint/isolate.cy.ts @@ -23,7 +23,7 @@ import { } from '../../tasks/isolate'; import { cleanupCase, cleanupRule, loadCase, loadRule } from '../../tasks/api_fixtures'; import { login } from '../../tasks/login'; -import { loadPage } from '../../tasks/common'; +import { disableExpandableFlyoutAdvancedSettings, loadPage } from '../../tasks/common'; import type { IndexedFleetEndpointPolicyResponse } from '../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy'; import { createAgentPolicyTask, getEndpointIntegrationVersion } from '../../tasks/fleet'; import type { CreateAndEnrollEndpointHostResponse } from '../../../../../scripts/endpoint/common/endpoint_host_services'; @@ -72,6 +72,7 @@ describe('Isolate command', () => { beforeEach(() => { login(); + disableExpandableFlyoutAdvancedSettings(); }); describe('From manage', () => { diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/automated_response_actions/no_license.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/automated_response_actions/no_license.cy.ts index 4d830c959399a..3ef371b1c847b 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/automated_response_actions/no_license.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/automated_response_actions/no_license.cy.ts @@ -6,6 +6,7 @@ */ import { generateRandomStringName } from '@kbn/osquery-plugin/cypress/tasks/integrations'; +import { disableExpandableFlyoutAdvancedSettings } from '../../../tasks/common'; import { APP_ALERTS_PATH } from '../../../../../../common/constants'; import { closeAllToasts } from '../../../tasks/toasts'; import { fillUpNewRule } from '../../../tasks/response_actions'; @@ -39,6 +40,7 @@ describe('No License', { env: { ftrConfig: { license: 'basic' } } }, () => { const [endpointAgentId, endpointHostname] = generateRandomStringName(2); before(() => { login(); + disableExpandableFlyoutAdvancedSettings(); indexEndpointRuleAlerts({ endpointAgentId, endpointHostname, diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/automated_response_actions/results.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/automated_response_actions/results.cy.ts index bb3be124418f8..f0cac7527c19e 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/automated_response_actions/results.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/automated_response_actions/results.cy.ts @@ -6,6 +6,7 @@ */ import { generateRandomStringName } from '@kbn/osquery-plugin/cypress/tasks/integrations'; +import { disableExpandableFlyoutAdvancedSettings } from '../../../tasks/common'; import { APP_ALERTS_PATH } from '../../../../../../common/constants'; import { closeAllToasts } from '../../../tasks/toasts'; import { indexEndpointHosts } from '../../../tasks/index_endpoint_hosts'; @@ -52,6 +53,7 @@ describe('Results', () => { describe('see results when has RBAC', () => { before(() => { login(ROLE.endpoint_response_actions_access); + disableExpandableFlyoutAdvancedSettings(); }); it('see endpoint action', () => { @@ -67,6 +69,7 @@ describe('Results', () => { describe('do not see results results when does not have RBAC', () => { before(() => { login(ROLE.endpoint_response_actions_no_access); + disableExpandableFlyoutAdvancedSettings(); }); it('show the permission denied callout', () => { diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/isolate.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/isolate.cy.ts index f633fd25abdcc..8e3811c93ee0e 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/isolate.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/isolate.cy.ts @@ -24,7 +24,7 @@ import type { ReturnTypeFromChainable } from '../../types'; import { addAlertsToCase } from '../../tasks/add_alerts_to_case'; import { APP_ALERTS_PATH, APP_CASES_PATH, APP_PATH } from '../../../../../common/constants'; import { login } from '../../tasks/login'; -import { loadPage } from '../../tasks/common'; +import { disableExpandableFlyoutAdvancedSettings, loadPage } from '../../tasks/common'; import { indexNewCase } from '../../tasks/index_new_case'; import { indexEndpointHosts } from '../../tasks/index_endpoint_hosts'; import { indexEndpointRuleAlerts } from '../../tasks/index_endpoint_rule_alerts'; @@ -95,6 +95,7 @@ describe('Isolate command', () => { let hostname: string; before(() => { + disableExpandableFlyoutAdvancedSettings(); indexEndpointHosts({ withResponseActions: false, isolation: false }).then( (indexEndpoints) => { endpointData = indexEndpoints; diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/common.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/common.ts index fb879fa5244b0..38866ec5b5d29 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/tasks/common.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/common.ts @@ -34,3 +34,23 @@ export const request = ({ headers: { ...COMMON_API_HEADERS, ...headers }, ...options, }); + +const API_HEADERS = Object.freeze({ 'kbn-xsrf': 'cypress' }); +export const rootRequest = ( + options: Partial +): Cypress.Chainable> => + cy.request({ + auth: API_AUTH, + headers: API_HEADERS, + ...options, + }); + +export const disableExpandableFlyoutAdvancedSettings = () => { + const body = { changes: { 'securitySolution:enableExpandableFlyout': false } }; + rootRequest({ + method: 'POST', + url: 'internal/kibana/settings', + body, + headers: { 'kbn-xsrf': 'cypress-creds' }, + }); +}; diff --git a/x-pack/plugins/security_solution/server/ui_settings.ts b/x-pack/plugins/security_solution/server/ui_settings.ts index f5ff542a7833f..6a41844e9e032 100644 --- a/x-pack/plugins/security_solution/server/ui_settings.ts +++ b/x-pack/plugins/security_solution/server/ui_settings.ts @@ -36,6 +36,7 @@ import { EXTENDED_RULE_EXECUTION_LOGGING_MIN_LEVEL_SETTING, DEFAULT_ALERT_TAGS_KEY, DEFAULT_ALERT_TAGS_VALUE, + ENABLE_EXPANDABLE_FLYOUT_SETTING, } from '../common/constants'; import type { ExperimentalFeatures } from '../common/experimental_features'; import { LogLevelSetting } from '../common/api/detection_engine/rule_monitoring'; @@ -163,6 +164,22 @@ export const initUiSettings = ( requiresPageReload: true, schema: schema.boolean(), }, + [ENABLE_EXPANDABLE_FLYOUT_SETTING]: { + name: i18n.translate('xpack.securitySolution.uiSettings.enableExpandableFlyoutLabel', { + defaultMessage: 'Expandable flyout', + }), + value: true, + description: i18n.translate( + 'xpack.securitySolution.uiSettings.enableExpandableFlyoutDescription', + { + defaultMessage: '

Enables the expandable flyout

', + } + ), + type: 'boolean', + category: [APP_ID], + requiresPageReload: true, + schema: schema.boolean(), + }, [DEFAULT_RULES_TABLE_REFRESH_SETTING]: { name: i18n.translate('xpack.securitySolution.uiSettings.rulesTableRefresh', { defaultMessage: 'Rules auto refresh', diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/responder.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/responder.ts index 586b07261e76c..dc2183d6f0fee 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/responder.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/responder.ts @@ -197,6 +197,12 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { let indexedAlerts: IndexedEndpointRuleAlerts; before(async () => { + await getService('kibanaServer').request({ + path: `internal/kibana/settings`, + method: 'POST', + body: { changes: { 'securitySolution:enableExpandableFlyout': false } }, + }); + indexedAlerts = await detectionsTestService.loadEndpointRuleAlerts(endpointAgentId); await detectionsTestService.waitForAlerts( From 2ba659d09102e48385422df26e692b44ba426978 Mon Sep 17 00:00:00 2001 From: Juan Pablo Djeredjian Date: Fri, 11 Aug 2023 15:30:40 +0200 Subject: [PATCH 08/11] =?UTF-8?q?[Security=20Solution]=20Fix=20flaky=20tes?= =?UTF-8?q?t:=20x-pack/test/detection=5Fengine=5Fapi=5Fintegration/securit?= =?UTF-8?q?y=5Fand=5Fspaces/update=5Fprebuilt=5Frules=5Fpackage/update=5Fp?= =?UTF-8?q?rebuilt=5Frules=5Fpackage=C2=B7ts=20-=20update=5Fprebuilt=5Frul?= =?UTF-8?q?es=5Fpackage=20should=20allow=20user=20to=20install=20prebuilt?= =?UTF-8?q?=20rules=20from=20scratch,=20then=20install=20new=20rules=20and?= =?UTF-8?q?=20upgrade=20existing=20rules=20from=20the=20new=20package=20(#?= =?UTF-8?q?163241)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: https://github.com/elastic/kibana/issues/162658 ## Summary - Fixes flaky test: `x-pack/test/detection_engine_api_integration/security_and_spaces/update_prebuilt_rules_package/update_prebuilt_rules_package·ts` - Test title: `update_prebuilt_rules_package should allow user to install prebuilt rules from scratch, then install new rules and upgrade existing rules from the new package` ## Passing flaky test runner 300 runs: https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/2799 ## Root cause and what this PR does - On initial investigation, the flaky test runner was executed for this test with 300 iterations and all of them succeeded. This gives us great confidence that the test is not actually flaky. - Further investigation showed that @kibanamachine reported this tests as failing when running the CI for two backport PRs to `8.9`: - https://buildkite.com/elastic/kibana-on-merge/builds/33282#0189987d-3a80-49c2-8332-3105ec3c2109 - https://buildkite.com/elastic/kibana-on-merge/builds/33444#0189b1fa-4bc4-4422-9ce9-5c9a24f11ad5 - These flakiness was caused **by a race condition** between the writing of rules into indeces, and them being made available for reading. This is a known issue, already and solved for other integration test, by manually refreshing ES's indeces. - In order to reduce the probability of flakiness in a similar scenario, this PR adds code to refresh the indices after each rule installation or upgrade during the test. ## Refactor - Moves the refreshing of the indexes within the utility function that write to indexes: - `installPrebuiltRules` - `upgradePrebuiltRules` - `installPrebuiltRulesAndTimelines` (legacy, but still used) - `installPrebuiltRulesFleetPackage` - Creates 2 new utils: - `installPrebuiltRulesPackageByVersion`, which installs `security_detection_engine` package via Fleet API, with its version passed in as param - `getInstalledRules`, reusable function to fetch all installed rules - --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../basic/tests/coverage_overview.ts | 2 +- .../install_latest_bundled_prebuilt_rules.ts | 18 ++-- .../prerelease_packages.ts | 12 +-- .../install_large_prebuilt_rules_package.ts | 4 +- .../prebuilt_rules/fleet_integration.ts | 27 +----- .../get_prebuilt_rules_status.ts | 40 ++++----- .../get_prebuilt_timelines_status.ts | 2 +- .../install_and_upgrade_prebuilt_rules.ts | 88 +++++++++---------- .../update_prebuilt_rules_package.ts | 61 ++++++------- .../prebuilt_rules/get_installed_rules.ts | 30 +++++++ .../install_fleet_package_by_url.ts | 50 +++++++++++ .../install_mock_prebuilt_rules.ts | 2 +- .../prebuilt_rules/install_prebuilt_rules.ts | 15 ++++ .../install_prebuilt_rules_and_timelines.ts | 15 ++++ .../install_prebuilt_rules_fleet_package.ts | 20 +++++ .../prebuilt_rules/upgrade_prebuilt_rules.ts | 16 ++++ 16 files changed, 249 insertions(+), 153 deletions(-) create mode 100644 x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/get_installed_rules.ts create mode 100644 x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_fleet_package_by_url.ts diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/coverage_overview.ts b/x-pack/test/detection_engine_api_integration/basic/tests/coverage_overview.ts index d7427a24657fa..d5e827d545a68 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/coverage_overview.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/coverage_overview.ts @@ -351,7 +351,7 @@ export default ({ getService }: FtrProviderContext): void => { threat: generateThreatArray(1), }), ]); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); const expectedRule = await createRule(supertest, log, { ...getSimpleRule('rule-1'), diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/install_latest_bundled_prebuilt_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/install_latest_bundled_prebuilt_rules.ts index 97717f00773d9..d9f710ba6afcf 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/install_latest_bundled_prebuilt_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/install_latest_bundled_prebuilt_rules.ts @@ -15,6 +15,7 @@ import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { deleteAllPrebuiltRuleAssets, deleteAllRules } from '../../utils'; import { getPrebuiltRulesStatus } from '../../utils/prebuilt_rules/get_prebuilt_rules_status'; +import { installPrebuiltRulesPackageByVersion } from '../../utils/prebuilt_rules/install_fleet_package_by_url'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { @@ -55,18 +56,17 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusBeforePackageInstallation.stats.num_prebuilt_rules_to_install).toBe(0); expect(statusBeforePackageInstallation.stats.num_prebuilt_rules_to_upgrade).toBe(0); - const EPM_URL = `/api/fleet/epm/packages/security_detection_engine/99.0.0`; - - const bundledInstallResponse = await supertest - .post(EPM_URL) - .set('kbn-xsrf', 'xxxx') - .type('application/json') - .send({ force: true }) - .expect(200); + const bundledInstallResponse = await installPrebuiltRulesPackageByVersion( + es, + supertest, + '99.0.0' + ); // As opposed to "registry" - expect(bundledInstallResponse.body._meta.install_source).toBe('bundled'); + expect(bundledInstallResponse._meta.install_source).toBe('bundled'); + // Refresh ES indices to avoid race conditions between write and reading of indeces + // See implementation utility function at x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules_fleet_package.ts await es.indices.refresh({ index: ALL_SAVED_OBJECT_INDICES }); // Verify that status is updated after package installation diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/prerelease_packages.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/prerelease_packages.ts index b1c32bf0e245e..fd69e3128c3e7 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/prerelease_packages.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/bundled_prebuilt_rules_package/prerelease_packages.ts @@ -4,11 +4,10 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; -import { DETECTION_ENGINE_RULES_URL_FIND } from '@kbn/security-solution-plugin/common/constants'; import expect from 'expect'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { deleteAllPrebuiltRuleAssets, deleteAllRules } from '../../utils'; +import { getInstalledRules } from '../../utils/prebuilt_rules/get_installed_rules'; import { getPrebuiltRulesStatus } from '../../utils/prebuilt_rules/get_prebuilt_rules_status'; import { installPrebuiltRules } from '../../utils/prebuilt_rules/install_prebuilt_rules'; @@ -38,8 +37,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusBeforePackageInstallation.stats.num_prebuilt_rules_to_install).toBe(0); expect(statusBeforePackageInstallation.stats.num_prebuilt_rules_to_upgrade).toBe(0); - await installPrebuiltRules(supertest); - await es.indices.refresh({ index: ALL_SAVED_OBJECT_INDICES }); + await installPrebuiltRules(es, supertest); // Verify that status is updated after package installation const statusAfterPackageInstallation = await getPrebuiltRulesStatus(supertest); @@ -48,11 +46,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusAfterPackageInstallation.stats.num_prebuilt_rules_to_upgrade).toBe(0); // Get installed rules - const { body: rulesResponse } = await supertest - .get(DETECTION_ENGINE_RULES_URL_FIND) - .set('kbn-xsrf', 'true') - .send() - .expect(200); + const rulesResponse = await getInstalledRules(supertest); // Assert that installed rules are from package 99.0.0 and not from prerelease (beta) package expect(rulesResponse.data.length).toBe(1); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/large_prebuilt_rules_package/install_large_prebuilt_rules_package.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/large_prebuilt_rules_package/install_large_prebuilt_rules_package.ts index 9dd5e695c3772..c047413bdb90a 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/large_prebuilt_rules_package/install_large_prebuilt_rules_package.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/large_prebuilt_rules_package/install_large_prebuilt_rules_package.ts @@ -5,7 +5,6 @@ * 2.0. */ import expect from 'expect'; -import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { deleteAllRules, getPrebuiltRulesAndTimelinesStatus } from '../../utils'; import { deleteAllPrebuiltRuleAssets } from '../../utils/prebuilt_rules/delete_all_prebuilt_rule_assets'; @@ -36,8 +35,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusBeforePackageInstallation.rules_not_updated).toBe(0); // Install the package with 15000 prebuilt historical version of rules rules and 750 unique rules - await installPrebuiltRulesAndTimelines(supertest); - await es.indices.refresh({ index: ALL_SAVED_OBJECT_INDICES }); + await installPrebuiltRulesAndTimelines(es, supertest); // Verify that status is updated after package installation const statusAfterPackageInstallation = await getPrebuiltRulesAndTimelinesStatus(supertest); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/fleet_integration.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/fleet_integration.ts index e48530ad16513..1433cb7cac2ff 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/fleet_integration.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/fleet_integration.ts @@ -5,7 +5,6 @@ * 2.0. */ import expect from 'expect'; -import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { deleteAllRules, @@ -43,24 +42,11 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusBeforePackageInstallation.rules_not_updated).toBe(0); await installPrebuiltRulesFleetPackage({ + es, supertest, overrideExistingPackage: true, }); - // Before we proceed, we need to refresh saved object indices. This comment will explain why. - // At the previous step we installed the Fleet package with prebuilt detection rules. - // Prebuilt rules are assets that Fleet indexes as saved objects of a certain type. - // Fleet does this via a savedObjectsClient.import() call with explicit `refresh: false`. - // So, despite of the fact that the endpoint waits until the prebuilt rule assets will be - // successfully indexed, it doesn't wait until they become "visible" for subsequent read - // operations. Which is what we do next: we read these SOs in getPrebuiltRulesAndTimelinesStatus(). - // Now, the time left until the next refresh can be anything from 0 to the default value, and - // it depends on the time when savedObjectsClient.import() call happens relative to the time of - // the next refresh. Also, probably the refresh time can be delayed when ES is under load? - // Anyway, here we have a race condition between a write and subsequent read operation, and to - // fix it deterministically we have to refresh saved object indices and wait until it's done. - await es.indices.refresh({ index: ALL_SAVED_OBJECT_INDICES }); - // Verify that status is updated after package installation const statusAfterPackageInstallation = await getPrebuiltRulesAndTimelinesStatus(supertest); expect(statusAfterPackageInstallation.rules_installed).toBe(0); @@ -68,19 +54,10 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusAfterPackageInstallation.rules_not_updated).toBe(0); // Verify that all previously not installed rules were installed - const response = await installPrebuiltRulesAndTimelines(supertest); + const response = await installPrebuiltRulesAndTimelines(es, supertest); expect(response.rules_installed).toBe(statusAfterPackageInstallation.rules_not_installed); expect(response.rules_updated).toBe(0); - // Similar to the previous refresh, we need to do it again between the two operations: - // - previous write operation: install prebuilt rules and timelines - // - subsequent read operation: get prebuilt rules and timelines status - // You may ask why? I'm not sure, probably because the write operation can install the Fleet - // package under certain circumstances, and it all works with `refresh: false` again. - // Anyway, there were flaky runs failing specifically at one of the next assertions, - // which means some kind of the same race condition we have here too. - await es.indices.refresh({ index: ALL_SAVED_OBJECT_INDICES }); - // Verify that status is updated after rules installation const statusAfterRuleInstallation = await getPrebuiltRulesAndTimelinesStatus(supertest); expect(statusAfterRuleInstallation.rules_installed).toBe(response.rules_installed); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/get_prebuilt_rules_status.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/get_prebuilt_rules_status.ts index ee5730cee39a8..ae43e3bdd5098 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/get_prebuilt_rules_status.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/get_prebuilt_rules_status.ts @@ -82,7 +82,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return the number of installed prebuilt rules after installing them', async () => { await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); const { stats } = await getPrebuiltRulesStatus(supertest); expect(stats).toMatchObject({ @@ -95,7 +95,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should notify the user again that a rule is available for install after it is deleted', async () => { await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); await deleteRule(supertest, 'rule-1'); const { stats } = await getPrebuiltRulesStatus(supertest); @@ -110,7 +110,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return available rule updates', async () => { const ruleAssetSavedObjects = getRuleAssetSavedObjects(); await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); // Clear previous rule assets await deleteAllPrebuiltRuleAssets(es); @@ -130,7 +130,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should not return any available update if rule has been successfully upgraded', async () => { const ruleAssetSavedObjects = getRuleAssetSavedObjects(); await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); // Clear previous rule assets await deleteAllPrebuiltRuleAssets(es); @@ -138,7 +138,7 @@ export default ({ getService }: FtrProviderContext): void => { ruleAssetSavedObjects[0]['security-rule'].version += 1; await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); // Upgrade all rules - await upgradePrebuiltRules(supertest); + await upgradePrebuiltRules(es, supertest); const { stats } = await getPrebuiltRulesStatus(supertest); expect(stats).toMatchObject({ @@ -152,7 +152,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should not return any updates if none are available', async () => { const ruleAssetSavedObjects = getRuleAssetSavedObjects(); await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); // Clear previous rule assets await deleteAllPrebuiltRuleAssets(es); @@ -193,7 +193,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return the number of installed prebuilt rules after installing them', async () => { await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); const { stats } = await getPrebuiltRulesStatus(supertest); expect(stats).toMatchObject({ @@ -206,7 +206,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should notify the user again that a rule is available for install after it is deleted', async () => { await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); await deleteRule(supertest, 'rule-1'); const { stats } = await getPrebuiltRulesStatus(supertest); @@ -220,7 +220,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return available rule updates when previous historical versions available', async () => { await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); // Add a new version of one of the installed rules await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ @@ -238,7 +238,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return available rule updates when previous historical versions unavailable', async () => { await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); // Delete the previous versions of rule assets await deleteAllPrebuiltRuleAssets(es); @@ -261,7 +261,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should not return available rule updates after rule has been upgraded', async () => { await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); // Delete the previous versions of rule assets await deleteAllPrebuiltRuleAssets(es); @@ -272,7 +272,7 @@ export default ({ getService }: FtrProviderContext): void => { ]); // Upgrade the rule - await upgradePrebuiltRules(supertest); + await upgradePrebuiltRules(es, supertest); const { stats } = await getPrebuiltRulesStatus(supertest); expect(stats).toMatchObject({ @@ -339,7 +339,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return the number of installed prebuilt rules after installing them', async () => { await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); const body = await getPrebuiltRulesAndTimelinesStatus(supertest); expect(body).toMatchObject({ @@ -352,7 +352,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should notify the user again that a rule is available for install after it is deleted', async () => { await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); await deleteRule(supertest, 'rule-1'); const body = await getPrebuiltRulesAndTimelinesStatus(supertest); @@ -367,7 +367,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return available rule updates', async () => { const ruleAssetSavedObjects = getRuleAssetSavedObjects(); await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); // Clear previous rule assets await deleteAllPrebuiltRuleAssets(es); @@ -387,7 +387,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should not return any updates if none are available', async () => { const ruleAssetSavedObjects = getRuleAssetSavedObjects(); await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); // Clear previous rule assets await deleteAllPrebuiltRuleAssets(es); @@ -428,7 +428,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return the number of installed prebuilt rules after installing them', async () => { await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); const body = await getPrebuiltRulesAndTimelinesStatus(supertest); expect(body).toMatchObject({ @@ -441,7 +441,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should notify the user again that a rule is available for install after it is deleted', async () => { await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); await deleteRule(supertest, 'rule-1'); const body = await getPrebuiltRulesAndTimelinesStatus(supertest); @@ -455,7 +455,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return available rule updates when previous historical versions available', async () => { await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); // Add a new version of one of the installed rules await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ @@ -473,7 +473,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should return available rule updates when previous historical versions unavailable', async () => { await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); // Delete the previous versions of rule assets await deleteAllPrebuiltRuleAssets(es); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/get_prebuilt_timelines_status.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/get_prebuilt_timelines_status.ts index 04275afe20dc9..05b34ffa98ed7 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/get_prebuilt_timelines_status.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/get_prebuilt_timelines_status.ts @@ -35,7 +35,7 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should return the number of installed timeline templates after installing them', async () => { - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); const body = await getPrebuiltRulesAndTimelinesStatus(supertest); expect(body).toMatchObject({ diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/install_and_upgrade_prebuilt_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/install_and_upgrade_prebuilt_rules.ts index c36b81f93cf7c..85af64415c95e 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/install_and_upgrade_prebuilt_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/prebuilt_rules/install_and_upgrade_prebuilt_rules.ts @@ -6,7 +6,6 @@ */ import expect from 'expect'; -import { DETECTION_ENGINE_RULES_URL_FIND } from '@kbn/security-solution-plugin/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { deleteAllRules, @@ -24,6 +23,7 @@ import { installPrebuiltRulesAndTimelines } from '../../utils/prebuilt_rules/ins import { installPrebuiltRules } from '../../utils/prebuilt_rules/install_prebuilt_rules'; import { getPrebuiltRulesStatus } from '../../utils/prebuilt_rules/get_prebuilt_rules_status'; import { upgradePrebuiltRules } from '../../utils/prebuilt_rules/upgrade_prebuilt_rules'; +import { getInstalledRules } from '../../utils/prebuilt_rules/get_installed_rules'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { @@ -50,7 +50,7 @@ export default ({ getService }: FtrProviderContext): void => { describe('using legacy endpoint', () => { it('should install prebuilt rules', async () => { await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - const body = await installPrebuiltRulesAndTimelines(supertest); + const body = await installPrebuiltRulesAndTimelines(es, supertest); expect(body.rules_installed).toBe(RULES_COUNT); expect(body.rules_updated).toBe(0); @@ -58,14 +58,10 @@ export default ({ getService }: FtrProviderContext): void => { it('should install correct prebuilt rule versions', async () => { await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); // Get installed rules - const { body: rulesResponse } = await supertest - .get(DETECTION_ENGINE_RULES_URL_FIND) - .set('kbn-xsrf', 'true') - .send() - .expect(200); + const rulesResponse = await getInstalledRules(supertest); // Check that all prebuilt rules were actually installed and their versions match the latest expect(rulesResponse.total).toBe(RULES_COUNT); @@ -82,7 +78,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should install missing prebuilt rules', async () => { // Install all prebuilt detection rules await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); // Delete one of the installed rules await deleteRule(supertest, 'rule-1'); @@ -92,7 +88,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusResponse.rules_not_installed).toBe(1); // Call the install prebuilt rules again and check that the missing rule was installed - const response = await installPrebuiltRulesAndTimelines(supertest); + const response = await installPrebuiltRulesAndTimelines(es, supertest); expect(response.rules_installed).toBe(1); expect(response.rules_updated).toBe(0); }); @@ -101,7 +97,7 @@ export default ({ getService }: FtrProviderContext): void => { // Install all prebuilt detection rules const ruleAssetSavedObjects = getRuleAssetSavedObjects(); await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); // Clear previous rule assets await deleteAllPrebuiltRuleAssets(es); @@ -114,7 +110,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusResponse.rules_not_updated).toBe(1); // Call the install prebuilt rules again and check that the outdated rule was updated - const response = await installPrebuiltRulesAndTimelines(supertest); + const response = await installPrebuiltRulesAndTimelines(es, supertest); expect(response.rules_installed).toBe(0); expect(response.rules_updated).toBe(1); }); @@ -122,7 +118,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should not install prebuilt rules if they are up to date', async () => { // Install all prebuilt detection rules await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); // Check that all prebuilt rules were installed const statusResponse = await getPrebuiltRulesAndTimelinesStatus(supertest); @@ -130,7 +126,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusResponse.rules_not_updated).toBe(0); // Call the install prebuilt rules again and check that no rules were installed - const response = await installPrebuiltRulesAndTimelines(supertest); + const response = await installPrebuiltRulesAndTimelines(es, supertest); expect(response.rules_installed).toBe(0); expect(response.rules_updated).toBe(0); }); @@ -139,7 +135,7 @@ export default ({ getService }: FtrProviderContext): void => { describe('using current endpoint', () => { it('should install prebuilt rules', async () => { await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - const body = await installPrebuiltRules(supertest); + const body = await installPrebuiltRules(es, supertest); expect(body.summary.succeeded).toBe(RULES_COUNT); expect(body.summary.failed).toBe(0); @@ -148,7 +144,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should install correct prebuilt rule versions', async () => { await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - const body = await installPrebuiltRules(supertest); + const body = await installPrebuiltRules(es, supertest); // Check that all prebuilt rules were actually installed and their versions match the latest expect(body.results.created).toEqual( @@ -164,7 +160,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should install missing prebuilt rules', async () => { // Install all prebuilt detection rules await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); // Delete one of the installed rules await deleteRule(supertest, 'rule-1'); @@ -174,7 +170,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusResponse.stats.num_prebuilt_rules_to_install).toBe(1); // Call the install prebuilt rules again and check that the missing rule was installed - const response = await installPrebuiltRules(supertest); + const response = await installPrebuiltRules(es, supertest); expect(response.summary.succeeded).toBe(1); }); @@ -182,7 +178,7 @@ export default ({ getService }: FtrProviderContext): void => { // Install all prebuilt detection rules const ruleAssetSavedObjects = getRuleAssetSavedObjects(); await createPrebuiltRuleAssetSavedObjects(es, ruleAssetSavedObjects); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); // Clear previous rule assets await deleteAllPrebuiltRuleAssets(es); @@ -196,7 +192,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusResponse.stats.num_prebuilt_rules_to_upgrade).toBe(1); // Call the install prebuilt rules again and check that the outdated rule was updated - const response = await upgradePrebuiltRules(supertest); + const response = await upgradePrebuiltRules(es, supertest); expect(response.summary.succeeded).toBe(1); expect(response.summary.skipped).toBe(0); }); @@ -204,7 +200,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should not install prebuilt rules if they are up to date', async () => { // Install all prebuilt detection rules await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); // Check that all prebuilt rules were installed const statusResponse = await getPrebuiltRulesStatus(supertest); @@ -212,12 +208,12 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusResponse.stats.num_prebuilt_rules_to_upgrade).toBe(0); // Call the install prebuilt rules again and check that no rules were installed - const installResponse = await installPrebuiltRules(supertest); + const installResponse = await installPrebuiltRules(es, supertest); expect(installResponse.summary.succeeded).toBe(0); expect(installResponse.summary.skipped).toBe(0); // Call the upgrade prebuilt rules endpoint and check that no rules were updated - const upgradeResponse = await upgradePrebuiltRules(supertest); + const upgradeResponse = await upgradePrebuiltRules(es, supertest); expect(upgradeResponse.summary.succeeded).toBe(0); expect(upgradeResponse.summary.skipped).toBe(0); }); @@ -237,7 +233,7 @@ export default ({ getService }: FtrProviderContext): void => { describe('using legacy endpoint', () => { it('should install prebuilt rules', async () => { await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - const body = await installPrebuiltRulesAndTimelines(supertest); + const body = await installPrebuiltRulesAndTimelines(es, supertest); expect(body.rules_installed).toBe(RULES_COUNT); expect(body.rules_updated).toBe(0); @@ -245,14 +241,10 @@ export default ({ getService }: FtrProviderContext): void => { it('should install correct prebuilt rule versions', async () => { await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); // Get installed rules - const { body: rulesResponse } = await supertest - .get(DETECTION_ENGINE_RULES_URL_FIND) - .set('kbn-xsrf', 'true') - .send() - .expect(200); + const rulesResponse = await getInstalledRules(supertest); // Check that all prebuilt rules were actually installed and their versions match the latest expect(rulesResponse.total).toBe(RULES_COUNT); @@ -267,14 +259,14 @@ export default ({ getService }: FtrProviderContext): void => { it('should not install prebuilt rules if they are up to date', async () => { // Install all prebuilt detection rules await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); // Check that all prebuilt rules were installed const statusResponse = await getPrebuiltRulesAndTimelinesStatus(supertest); expect(statusResponse.rules_not_installed).toBe(0); // Call the install prebuilt rules again and check that no rules were installed - const response = await installPrebuiltRulesAndTimelines(supertest); + const response = await installPrebuiltRulesAndTimelines(es, supertest); expect(response.rules_installed).toBe(0); expect(response.rules_updated).toBe(0); }); @@ -282,7 +274,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should install missing prebuilt rules', async () => { // Install all prebuilt detection rules await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); // Delete one of the installed rules await deleteRule(supertest, 'rule-1'); @@ -292,7 +284,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusResponse.rules_not_installed).toBe(1); // Call the install prebuilt rules endpoint again and check that the missing rule was installed - const response = await installPrebuiltRulesAndTimelines(supertest); + const response = await installPrebuiltRulesAndTimelines(es, supertest); expect(response.rules_installed).toBe(1); expect(response.rules_updated).toBe(0); }); @@ -300,7 +292,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should update outdated prebuilt rules when previous historical versions available', async () => { // Install all prebuilt detection rules await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); // Add a new version of one of the installed rules await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ @@ -312,7 +304,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusResponse.rules_not_updated).toBe(1); // Call the install prebuilt rules again and check that the outdated rule was updated - const response = await installPrebuiltRulesAndTimelines(supertest); + const response = await installPrebuiltRulesAndTimelines(es, supertest); expect(response.rules_installed).toBe(0); expect(response.rules_updated).toBe(1); @@ -324,7 +316,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should update outdated prebuilt rules when previous historical versions unavailable', async () => { // Install all prebuilt detection rules await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRulesAndTimelines(supertest); + await installPrebuiltRulesAndTimelines(es, supertest); // Clear previous rule assets await deleteAllPrebuiltRuleAssets(es); @@ -340,7 +332,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusResponse.rules_not_installed).toBe(0); // Call the install prebuilt rules again and check that the outdated rule was updated - const response = await installPrebuiltRulesAndTimelines(supertest); + const response = await installPrebuiltRulesAndTimelines(es, supertest); expect(response.rules_installed).toBe(0); expect(response.rules_updated).toBe(1); @@ -353,14 +345,14 @@ export default ({ getService }: FtrProviderContext): void => { describe('using current endpoint', () => { it('should install prebuilt rules', async () => { await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - const body = await installPrebuiltRules(supertest); + const body = await installPrebuiltRules(es, supertest); expect(body.summary.succeeded).toBe(RULES_COUNT); }); it('should install correct prebuilt rule versions', async () => { await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - const response = await installPrebuiltRules(supertest); + const response = await installPrebuiltRules(es, supertest); // Check that all prebuilt rules were actually installed and their versions match the latest expect(response.summary.succeeded).toBe(RULES_COUNT); @@ -375,14 +367,14 @@ export default ({ getService }: FtrProviderContext): void => { it('should not install prebuilt rules if they are up to date', async () => { // Install all prebuilt detection rules await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); // Check that all prebuilt rules were installed const statusResponse = await getPrebuiltRulesStatus(supertest); expect(statusResponse.stats.num_prebuilt_rules_to_install).toBe(0); // Call the install prebuilt rules again and check that no rules were installed - const response = await installPrebuiltRules(supertest); + const response = await installPrebuiltRules(es, supertest); expect(response.summary.succeeded).toBe(0); expect(response.summary.total).toBe(0); }); @@ -390,7 +382,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should install missing prebuilt rules', async () => { // Install all prebuilt detection rules await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); // Delete one of the installed rules await deleteRule(supertest, 'rule-1'); @@ -400,7 +392,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusResponse.stats.num_prebuilt_rules_to_install).toBe(1); // Call the install prebuilt rules endpoint again and check that the missing rule was installed - const response = await installPrebuiltRules(supertest); + const response = await installPrebuiltRules(es, supertest); expect(response.summary.succeeded).toBe(1); expect(response.summary.total).toBe(1); }); @@ -408,7 +400,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should update outdated prebuilt rules when previous historical versions available', async () => { // Install all prebuilt detection rules await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); // Add a new version of one of the installed rules await createHistoricalPrebuiltRuleAssetSavedObjects(es, [ @@ -420,7 +412,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusResponse.stats.num_prebuilt_rules_to_upgrade).toBe(1); // Call the upgrade prebuilt rules endpoint and check that the outdated rule was updated - const response = await upgradePrebuiltRules(supertest); + const response = await upgradePrebuiltRules(es, supertest); expect(response.summary.succeeded).toBe(1); expect(response.summary.total).toBe(1); @@ -432,7 +424,7 @@ export default ({ getService }: FtrProviderContext): void => { it('should update outdated prebuilt rules when previous historical versions unavailable', async () => { // Install all prebuilt detection rules await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); - await installPrebuiltRules(supertest); + await installPrebuiltRules(es, supertest); // Clear previous rule assets await deleteAllPrebuiltRuleAssets(es); @@ -448,7 +440,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusResponse.stats.num_prebuilt_rules_to_install).toBe(0); // Call the upgrade prebuilt rules endpoint and check that the outdated rule was updated - const response = await upgradePrebuiltRules(supertest); + const response = await upgradePrebuiltRules(es, supertest); expect(response.summary.succeeded).toBe(1); expect(response.summary.total).toBe(1); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/update_prebuilt_rules_package/update_prebuilt_rules_package.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/update_prebuilt_rules_package/update_prebuilt_rules_package.ts index 0e25999a37e9b..1d7939e83f9ab 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/update_prebuilt_rules_package/update_prebuilt_rules_package.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/update_prebuilt_rules_package/update_prebuilt_rules_package.ts @@ -13,7 +13,6 @@ import { REPO_ROOT } from '@kbn/repo-info'; import JSON5 from 'json5'; import expect from 'expect'; import { PackageSpecManifest } from '@kbn/fleet-plugin/common'; -import { DETECTION_ENGINE_RULES_URL_FIND } from '@kbn/security-solution-plugin/common/constants'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { deleteAllPrebuiltRuleAssets, @@ -24,6 +23,8 @@ import { } from '../../utils'; import { reviewPrebuiltRulesToInstall } from '../../utils/prebuilt_rules/review_install_prebuilt_rules'; import { reviewPrebuiltRulesToUpgrade } from '../../utils/prebuilt_rules/review_upgrade_prebuilt_rules'; +import { installPrebuiltRulesPackageByVersion } from '../../utils/prebuilt_rules/install_fleet_package_by_url'; +import { getInstalledRules } from '../../utils/prebuilt_rules/get_installed_rules'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { @@ -103,17 +104,14 @@ export default ({ getService }: FtrProviderContext): void => { expect(statusBeforePackageInstallation.stats.num_prebuilt_rules_to_upgrade).toBe(0); expect(statusBeforePackageInstallation.stats.num_prebuilt_rules_total_in_package).toBe(0); - const EPM_URL_FOR_PREVIOUS_VERSION = `/api/fleet/epm/packages/security_detection_engine/${previousVersion}`; - - const installPreviousPackageResponse = await supertest - .post(EPM_URL_FOR_PREVIOUS_VERSION) - .set('kbn-xsrf', 'xxxx') - .type('application/json') - .send({ force: true }) - .expect(200); + const installPreviousPackageResponse = await installPrebuiltRulesPackageByVersion( + es, + supertest, + previousVersion + ); - expect(installPreviousPackageResponse.body._meta.install_source).toBe('registry'); - expect(installPreviousPackageResponse.body.items.length).toBeGreaterThan(0); + expect(installPreviousPackageResponse._meta.install_source).toBe('registry'); + expect(installPreviousPackageResponse.items.length).toBeGreaterThan(0); // Verify that status is updated after the installation of package "N-1" const statusAfterPackageInstallation = await getPrebuiltRulesStatus(supertest); @@ -132,7 +130,8 @@ export default ({ getService }: FtrProviderContext): void => { // Verify that the _perform endpoint returns the same number of installed rules as the status endpoint // and the _review endpoint - const installPrebuiltRulesResponse = await installPrebuiltRules(supertest); + const installPrebuiltRulesResponse = await installPrebuiltRules(es, supertest); + expect(installPrebuiltRulesResponse.summary.succeeded).toBe( statusAfterPackageInstallation.stats.num_prebuilt_rules_to_install ); @@ -141,11 +140,7 @@ export default ({ getService }: FtrProviderContext): void => { ); // Get installed rules - const { body: rulesResponse } = await supertest - .get(`${DETECTION_ENGINE_RULES_URL_FIND}?per_page=10000`) - .set('kbn-xsrf', 'true') - .send() - .expect(200); + const rulesResponse = await getInstalledRules(supertest); // Check that all prebuilt rules were actually installed expect(rulesResponse.total).toBe(installPrebuiltRulesResponse.summary.succeeded); @@ -160,16 +155,13 @@ export default ({ getService }: FtrProviderContext): void => { ); // PART 2: Now install the lastest (current) package, defined in fleet_packages.json - const EPM_URL_FOR_CURRENT_VERSION = `/api/fleet/epm/packages/security_detection_engine/${currentVersion}`; - - const installLatestPackageResponse = await supertest - .post(EPM_URL_FOR_CURRENT_VERSION) - .set('kbn-xsrf', 'xxxx') - .type('application/json') - .send({ force: true }) - .expect(200); - expect(installLatestPackageResponse.body.items.length).toBeGreaterThanOrEqual(0); + const installLatestPackageResponse = await installPrebuiltRulesPackageByVersion( + es, + supertest, + currentVersion + ); + expect(installLatestPackageResponse.items.length).toBeGreaterThanOrEqual(0); // Verify status after intallation of the latest package const statusAfterLatestPackageInstallation = await getPrebuiltRulesStatus(supertest); @@ -196,8 +188,10 @@ export default ({ getService }: FtrProviderContext): void => { // Install available rules and verify that the _perform endpoint returns the same number of // installed rules as the status endpoint and the _review endpoint const installPrebuiltRulesResponseAfterLatestPackageInstallation = await installPrebuiltRules( + es, supertest ); + expect(installPrebuiltRulesResponseAfterLatestPackageInstallation.summary.succeeded).toBe( statusAfterLatestPackageInstallation.stats.num_prebuilt_rules_to_install ); @@ -208,11 +202,7 @@ export default ({ getService }: FtrProviderContext): void => { ); // Get installed rules - const { body: rulesResponseAfterPackageUpdate } = await supertest - .get(`${DETECTION_ENGINE_RULES_URL_FIND}?per_page=10000`) - .set('kbn-xsrf', 'true') - .send() - .expect(200); + const rulesResponseAfterPackageUpdate = await getInstalledRules(supertest); // Check that the expected new prebuilt rules from the latest package were actually installed expect( @@ -239,8 +229,10 @@ export default ({ getService }: FtrProviderContext): void => { // Call the upgrade _perform endpoint and verify that the number of upgraded rules is the same as the one // returned by the _review endpoint and the status endpoint const upgradePrebuiltRulesResponseAfterLatestPackageInstallation = await upgradePrebuiltRules( + es, supertest ); + expect(upgradePrebuiltRulesResponseAfterLatestPackageInstallation.summary.succeeded).toEqual( statusAfterLatestPackageInstallation.stats.num_prebuilt_rules_to_upgrade ); @@ -249,11 +241,8 @@ export default ({ getService }: FtrProviderContext): void => { ); // Get installed rules - const { body: rulesResponseAfterPackageUpdateAndRuleUpgrades } = await supertest - .get(`${DETECTION_ENGINE_RULES_URL_FIND}?per_page=10000`) - .set('kbn-xsrf', 'true') - .send() - .expect(200); + + const rulesResponseAfterPackageUpdateAndRuleUpgrades = await getInstalledRules(supertest); // Check that the expected new prebuilt rules from the latest package were actually installed expect( diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/get_installed_rules.ts b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/get_installed_rules.ts new file mode 100644 index 0000000000000..85eaee80ed3e8 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/get_installed_rules.ts @@ -0,0 +1,30 @@ +/* + * 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 SuperTest from 'supertest'; +import { DETECTION_ENGINE_RULES_URL_FIND } from '@kbn/security-solution-plugin/common/constants'; +import { FindRulesResponse } from '@kbn/security-solution-plugin/common/api/detection_engine'; + +/** + * Get all installed security rules (both prebuilt + custom) + * + * @param es Elasticsearch client + * @param supertest SuperTest instance + * @param version Semver version of the `security_detection_engine` package to install + * @returns Fleet install package response + */ + +export const getInstalledRules = async ( + supertest: SuperTest.SuperTest +): Promise => { + const { body: rulesResponse } = await supertest + .get(`${DETECTION_ENGINE_RULES_URL_FIND}?per_page=10000`) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + return rulesResponse; +}; diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_fleet_package_by_url.ts b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_fleet_package_by_url.ts new file mode 100644 index 0000000000000..802626881b8e6 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_fleet_package_by_url.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 { Client } from '@elastic/elasticsearch'; +import type SuperTest from 'supertest'; +import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; +import { InstallPackageResponse } from '@kbn/fleet-plugin/common/types'; + +/** + * Installs prebuilt rules package `security_detection_engine` by version. + * + * @param es Elasticsearch client + * @param supertest SuperTest instance + * @param version Semver version of the `security_detection_engine` package to install + * @returns Fleet install package response + */ + +export const installPrebuiltRulesPackageByVersion = async ( + es: Client, + supertest: SuperTest.SuperTest, + version: string +): Promise => { + const fleetResponse = await supertest + .post(`/api/fleet/epm/packages/security_detection_engine/${version}`) + .set('kbn-xsrf', 'xxxx') + .type('application/json') + .send({ force: true }) + .expect(200); + + // Before we proceed, we need to refresh saved object indices. + // At the previous step we installed the Fleet package with prebuilt detection rules. + // Prebuilt rules are assets that Fleet indexes as saved objects of a certain type. + // Fleet does this via a savedObjectsClient.import() call with explicit `refresh: false`. + // So, despite of the fact that the endpoint waits until the prebuilt rule assets will be + // successfully indexed, it doesn't wait until they become "visible" for subsequent read + // operations. + // And this is usually what we do next in integration tests: we read these SOs with utility + // function such as getPrebuiltRulesAndTimelinesStatus(). + // Now, the time left until the next refresh can be anything from 0 to the default value, and + // it depends on the time when savedObjectsClient.import() call happens relative to the time of + // the next refresh. Also, probably the refresh time can be delayed when ES is under load? + // Anyway, this can cause race condition between a write and subsequent read operation, and to + // fix it deterministically we have to refresh saved object indices and wait until it's done. + await es.indices.refresh({ index: ALL_SAVED_OBJECT_INDICES }); + + return fleetResponse.body as InstallPackageResponse; +}; diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_mock_prebuilt_rules.ts b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_mock_prebuilt_rules.ts index 6f9726ae6a194..0e15f416e1238 100644 --- a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_mock_prebuilt_rules.ts +++ b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_mock_prebuilt_rules.ts @@ -24,5 +24,5 @@ export const installMockPrebuiltRules = async ( ): Promise => { // Ensure there are prebuilt rule saved objects before installing rules await createPrebuiltRuleAssetSavedObjects(es); - return installPrebuiltRulesAndTimelines(supertest); + return installPrebuiltRulesAndTimelines(es, supertest); }; diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules.ts b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules.ts index c11ccb7b37abd..f05ea093cfc5d 100644 --- a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules.ts +++ b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules.ts @@ -10,7 +10,9 @@ import { RuleVersionSpecifier, PerformRuleInstallationResponseBody, } from '@kbn/security-solution-plugin/common/api/detection_engine/prebuilt_rules'; +import type { Client } from '@elastic/elasticsearch'; import type SuperTest from 'supertest'; +import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; /** * Installs available prebuilt rules in Kibana. Rules are @@ -27,6 +29,7 @@ import type SuperTest from 'supertest'; * @returns Install prebuilt rules response */ export const installPrebuiltRules = async ( + es: Client, supertest: SuperTest.SuperTest, rules?: RuleVersionSpecifier[] ): Promise => { @@ -42,5 +45,17 @@ export const installPrebuiltRules = async ( .send(payload) .expect(200); + // Before we proceed, we need to refresh saved object indices. + // At the previous step we installed the prebuilt detection rules SO of type 'security-rule'. + // The savedObjectsClient does this with a call with explicit `refresh: false`. + // So, despite of the fact that the endpoint waits until the prebuilt rule will be + // successfully indexed, it doesn't wait until they become "visible" for subsequent read + // operations. + // And this is usually what we do next in integration tests: we read these SOs with utility + // function such as getPrebuiltRulesAndTimelinesStatus(). + // This can cause race conditions between a write and subsequent read operation, and to + // fix it deterministically we have to refresh saved object indices and wait until it's done. + await es.indices.refresh({ index: ALL_SAVED_OBJECT_INDICES }); + return response.body; }; diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules_and_timelines.ts b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules_and_timelines.ts index 7954e2b47bbac..fdf87a94391c9 100644 --- a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules_and_timelines.ts +++ b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules_and_timelines.ts @@ -9,7 +9,9 @@ import { InstallPrebuiltRulesAndTimelinesResponse, PREBUILT_RULES_URL, } from '@kbn/security-solution-plugin/common/api/detection_engine/prebuilt_rules'; +import type { Client } from '@elastic/elasticsearch'; import type SuperTest from 'supertest'; +import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; /** * (LEGACY) @@ -28,6 +30,7 @@ import type SuperTest from 'supertest'; * @returns Install prebuilt rules response */ export const installPrebuiltRulesAndTimelines = async ( + es: Client, supertest: SuperTest.SuperTest ): Promise => { const response = await supertest @@ -36,5 +39,17 @@ export const installPrebuiltRulesAndTimelines = async ( .send() .expect(200); + // Before we proceed, we need to refresh saved object indices. + // At the previous step we installed the prebuilt detection rules SO of type 'security-rule'. + // The savedObjectsClient does this with a call with explicit `refresh: false`. + // So, despite of the fact that the endpoint waits until the prebuilt rule will be + // successfully indexed, it doesn't wait until they become "visible" for subsequent read + // operations. + // And this is usually what we do next in integration tests: we read these SOs with utility + // function such as getPrebuiltRulesAndTimelinesStatus(). + // This can cause race condition between a write and subsequent read operation, and to + // fix it deterministically we have to refresh saved object indices and wait until it's done. + await es.indices.refresh({ index: ALL_SAVED_OBJECT_INDICES }); + return response.body; }; diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules_fleet_package.ts b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules_fleet_package.ts index 30435caa5a7c3..cc899ecc1dccc 100644 --- a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules_fleet_package.ts +++ b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/install_prebuilt_rules_fleet_package.ts @@ -5,7 +5,9 @@ * 2.0. */ +import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; import { epmRouteService } from '@kbn/fleet-plugin/common'; +import type { Client } from '@elastic/elasticsearch'; import type SuperTest from 'supertest'; /** @@ -17,10 +19,12 @@ import type SuperTest from 'supertest'; * @param overrideExistingPackage Whether or not to force the install */ export const installPrebuiltRulesFleetPackage = async ({ + es, supertest, version, overrideExistingPackage, }: { + es: Client; supertest: SuperTest.SuperTest; version?: string; overrideExistingPackage: boolean; @@ -46,6 +50,22 @@ export const installPrebuiltRulesFleetPackage = async ({ }) .expect(200); } + + // Before we proceed, we need to refresh saved object indices. + // At the previous step we installed the Fleet package with prebuilt detection rules. + // Prebuilt rules are assets that Fleet indexes as saved objects of a certain type. + // Fleet does this via a savedObjectsClient.import() call with explicit `refresh: false`. + // So, despite of the fact that the endpoint waits until the prebuilt rule assets will be + // successfully indexed, it doesn't wait until they become "visible" for subsequent read + // operations. + // And this is usually what we do next in integration tests: we read these SOs with utility + // function such as getPrebuiltRulesAndTimelinesStatus(). + // Now, the time left until the next refresh can be anything from 0 to the default value, and + // it depends on the time when savedObjectsClient.import() call happens relative to the time of + // the next refresh. Also, probably the refresh time can be delayed when ES is under load? + // Anyway, this can cause race condition between a write and subsequent read operation, and to + // fix it deterministically we have to refresh saved object indices and wait until it's done. + await es.indices.refresh({ index: ALL_SAVED_OBJECT_INDICES }); }; /** diff --git a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/upgrade_prebuilt_rules.ts b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/upgrade_prebuilt_rules.ts index bb3299cb5dd9e..d9ea277fb1421 100644 --- a/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/upgrade_prebuilt_rules.ts +++ b/x-pack/test/detection_engine_api_integration/utils/prebuilt_rules/upgrade_prebuilt_rules.ts @@ -10,7 +10,9 @@ import { RuleVersionSpecifier, PerformRuleUpgradeResponseBody, } from '@kbn/security-solution-plugin/common/api/detection_engine/prebuilt_rules'; +import type { Client } from '@elastic/elasticsearch'; import type SuperTest from 'supertest'; +import { ALL_SAVED_OBJECT_INDICES } from '@kbn/core-saved-objects-server'; /** * Upgrades available prebuilt rules in Kibana. @@ -23,6 +25,7 @@ import type SuperTest from 'supertest'; * @returns Upgrade prebuilt rules response */ export const upgradePrebuiltRules = async ( + es: Client, supertest: SuperTest.SuperTest, rules?: RuleVersionSpecifier[] ): Promise => { @@ -38,5 +41,18 @@ export const upgradePrebuiltRules = async ( .send(payload) .expect(200); + // Before we proceed, we need to refresh saved object indices. + // At the previous step we upgraded the prebuilt rules, which, under the hoods, installs new versions + // of the prebuilt detection rules SO of type 'security-rule'. + // The savedObjectsClient does this with a call with explicit `refresh: false`. + // So, despite of the fact that the endpoint waits until the prebuilt rule will be + // successfully indexed, it doesn't wait until they become "visible" for subsequent read + // operations. + // And this is usually what we do next in integration tests: we read these SOs with utility + // function such as getPrebuiltRulesAndTimelinesStatus(). + // This can cause race conditions between a write and subsequent read operation, and to + // fix it deterministically we have to refresh saved object indices and wait until it's done. + await es.indices.refresh({ index: ALL_SAVED_OBJECT_INDICES }); + return response.body; }; From 27c394c936991ce11c5cc1fdf7184c8fc4bdcd88 Mon Sep 17 00:00:00 2001 From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com> Date: Fri, 11 Aug 2023 09:32:25 -0400 Subject: [PATCH 09/11] [Security Solution][Endpoint] Add API checks to Endpoint Policy create/update for checking `endpointPolicyProtections` is enabled (#163429) ## Summary - Adds checks to both the Policy Create and Policy Update APIs (Fleet API extension points) and turns off all protections if `endpointPolicyProtections` appFeature is disabled - Adds migration of policies to the Plugin `start()` that will check if `endpointPolicyProtections` is disabled and updates all existing policies (if necessary) to disable protections. --- .../models/policy_config_helpers.test.ts | 93 +++++++++-- .../endpoint/models/policy_config_helpers.ts | 100 +++++++++++- .../common/endpoint/types/utility_types.ts | 12 ++ .../endpoint/endpoint_app_context_services.ts | 9 +- .../turn_off_policy_protections.test.ts | 145 ++++++++++++++++++ .../migrations/turn_off_policy_protections.ts | 107 +++++++++++++ .../server/endpoint/mocks.ts | 6 +- .../fleet/endpoint_fleet_services_factory.ts | 10 ++ .../create_internal_readonly_so_client.ts | 21 +-- .../utils/create_internal_so_client.ts | 27 ++++ .../fleet_integration.test.ts | 69 ++++++++- .../fleet_integration/fleet_integration.ts | 48 ++++-- .../handlers/create_default_policy.test.ts | 40 ++++- .../handlers/create_default_policy.ts | 26 +++- .../server/lib/app_features/app_features.ts | 2 +- .../server/lib/app_features/mocks.ts | 38 +++++ .../security_solution/server/plugin.ts | 33 ++-- 17 files changed, 715 insertions(+), 71 deletions(-) create mode 100644 x-pack/plugins/security_solution/common/endpoint/types/utility_types.ts create mode 100644 x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_policy_protections.test.ts create mode 100644 x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_policy_protections.ts create mode 100644 x-pack/plugins/security_solution/server/endpoint/utils/create_internal_so_client.ts create mode 100644 x-pack/plugins/security_solution/server/lib/app_features/mocks.ts diff --git a/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.test.ts b/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.test.ts index 1b01d477f1995..fe3fd8c2ebd6a 100644 --- a/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.test.ts +++ b/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.test.ts @@ -6,14 +6,19 @@ */ import type { PolicyConfig } from '../types'; -import { ProtectionModes } from '../types'; +import { PolicyOperatingSystem, ProtectionModes } from '../types'; import { policyFactory } from './policy_config'; -import { disableProtections } from './policy_config_helpers'; +import { + disableProtections, + isPolicySetToEventCollectionOnly, + ensureOnlyEventCollectionIsAllowed, +} from './policy_config_helpers'; +import { set } from 'lodash'; describe('Policy Config helpers', () => { describe('disableProtections', () => { it('disables all the protections in the default policy', () => { - expect(disableProtections(policyFactory())).toEqual(eventsOnlyPolicy); + expect(disableProtections(policyFactory())).toEqual(eventsOnlyPolicy()); }); it('does not enable supported fields', () => { @@ -51,20 +56,20 @@ describe('Policy Config helpers', () => { }; const expectedPolicyWithoutSupportedProtections: PolicyConfig = { - ...eventsOnlyPolicy, + ...eventsOnlyPolicy(), windows: { - ...eventsOnlyPolicy.windows, + ...eventsOnlyPolicy().windows, memory_protection: notSupported, behavior_protection: notSupportedBehaviorProtection, ransomware: notSupported, }, mac: { - ...eventsOnlyPolicy.mac, + ...eventsOnlyPolicy().mac, memory_protection: notSupported, behavior_protection: notSupportedBehaviorProtection, }, linux: { - ...eventsOnlyPolicy.linux, + ...eventsOnlyPolicy().linux, memory_protection: notSupported, behavior_protection: notSupportedBehaviorProtection, }, @@ -104,10 +109,10 @@ describe('Policy Config helpers', () => { }; const expectedPolicy: PolicyConfig = { - ...eventsOnlyPolicy, - windows: { ...eventsOnlyPolicy.windows, events: { ...windowsEvents } }, - mac: { ...eventsOnlyPolicy.mac, events: { ...macEvents } }, - linux: { ...eventsOnlyPolicy.linux, events: { ...linuxEvents } }, + ...eventsOnlyPolicy(), + windows: { ...eventsOnlyPolicy().windows, events: { ...windowsEvents } }, + mac: { ...eventsOnlyPolicy().mac, events: { ...macEvents } }, + linux: { ...eventsOnlyPolicy().linux, events: { ...linuxEvents } }, }; const inputPolicy = { @@ -120,11 +125,73 @@ describe('Policy Config helpers', () => { expect(disableProtections(inputPolicy)).toEqual(expectedPolicy); }); }); + + describe('setPolicyToEventCollectionOnly()', () => { + it('should set the policy to event collection only', () => { + expect(ensureOnlyEventCollectionIsAllowed(policyFactory())).toEqual(eventsOnlyPolicy()); + }); + }); + + describe('isPolicySetToEventCollectionOnly', () => { + let policy: PolicyConfig; + + beforeEach(() => { + policy = ensureOnlyEventCollectionIsAllowed(policyFactory()); + }); + + it.each([ + { + keyPath: `${PolicyOperatingSystem.windows}.malware.mode`, + keyValue: ProtectionModes.prevent, + expectedResult: false, + }, + { + keyPath: `${PolicyOperatingSystem.mac}.malware.mode`, + keyValue: ProtectionModes.off, + expectedResult: true, + }, + { + keyPath: `${PolicyOperatingSystem.windows}.ransomware.mode`, + keyValue: ProtectionModes.prevent, + expectedResult: false, + }, + { + keyPath: `${PolicyOperatingSystem.linux}.memory_protection.mode`, + keyValue: ProtectionModes.off, + expectedResult: true, + }, + { + keyPath: `${PolicyOperatingSystem.mac}.behavior_protection.mode`, + keyValue: ProtectionModes.detect, + expectedResult: false, + }, + { + keyPath: `${PolicyOperatingSystem.windows}.attack_surface_reduction.credential_hardening.enabled`, + keyValue: true, + expectedResult: false, + }, + { + keyPath: `${PolicyOperatingSystem.windows}.antivirus_registration.enabled`, + keyValue: true, + expectedResult: false, + }, + ])( + 'should return `$expectedResult` if `$keyPath` is set to `$keyValue`', + ({ keyPath, keyValue, expectedResult }) => { + set(policy, keyPath, keyValue); + + expect(isPolicySetToEventCollectionOnly(policy)).toEqual({ + isOnlyCollectingEvents: expectedResult, + message: expectedResult ? undefined : `property [${keyPath}] is set to [${keyValue}]`, + }); + } + ); + }); }); // This constant makes sure that if the type `PolicyConfig` is ever modified, // the logic for disabling protections is also modified due to type check. -export const eventsOnlyPolicy: PolicyConfig = { +export const eventsOnlyPolicy = (): PolicyConfig => ({ meta: { license: '', cloud: false, license_uid: '', cluster_name: '', cluster_uuid: '' }, windows: { events: { @@ -187,4 +254,4 @@ export const eventsOnlyPolicy: PolicyConfig = { capture_env_vars: 'LD_PRELOAD,LD_LIBRARY_PATH', }, }, -}; +}); diff --git a/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.ts b/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.ts index 4bdca10547bc2..cb460e2f75f49 100644 --- a/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.ts +++ b/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.ts @@ -5,8 +5,63 @@ * 2.0. */ +import { get, set } from 'lodash'; import type { PolicyConfig } from '../types'; -import { ProtectionModes } from '../types'; +import { PolicyOperatingSystem, ProtectionModes } from '../types'; + +interface PolicyProtectionReference { + keyPath: string; + osList: PolicyOperatingSystem[]; + enableValue: unknown; + disableValue: unknown; +} + +const getPolicyProtectionsReference = (): PolicyProtectionReference[] => { + const allOsValues = [ + PolicyOperatingSystem.mac, + PolicyOperatingSystem.linux, + PolicyOperatingSystem.windows, + ]; + + return [ + { + keyPath: 'malware.mode', + osList: [...allOsValues], + disableValue: ProtectionModes.off, + enableValue: ProtectionModes.prevent, + }, + { + keyPath: 'ransomware.mode', + osList: [PolicyOperatingSystem.windows], + disableValue: ProtectionModes.off, + enableValue: ProtectionModes.prevent, + }, + { + keyPath: 'memory_protection.mode', + osList: [...allOsValues], + disableValue: ProtectionModes.off, + enableValue: ProtectionModes.prevent, + }, + { + keyPath: 'behavior_protection.mode', + osList: [...allOsValues], + disableValue: ProtectionModes.off, + enableValue: ProtectionModes.prevent, + }, + { + keyPath: 'attack_surface_reduction.credential_hardening.enabled', + osList: [PolicyOperatingSystem.windows], + disableValue: false, + enableValue: true, + }, + { + keyPath: 'antivirus_registration.enabled', + osList: [PolicyOperatingSystem.windows], + disableValue: false, + enableValue: true, + }, + ]; +}; /** * Returns a copy of the passed `PolicyConfig` with all protections set to disabled. @@ -106,3 +161,46 @@ const getDisabledWindowsSpecificPopups = (policy: PolicyConfig) => ({ enabled: false, }, }); + +/** + * Returns the provided with only event collection turned enabled + * @param policy + */ +export const ensureOnlyEventCollectionIsAllowed = (policy: PolicyConfig): PolicyConfig => { + const updatedPolicy = disableProtections(policy); + + set(updatedPolicy, 'windows.antivirus_registration.enabled', false); + + return updatedPolicy; +}; + +/** + * Checks to see if the provided policy is set to Event Collection only + */ +export const isPolicySetToEventCollectionOnly = ( + policy: PolicyConfig +): { isOnlyCollectingEvents: boolean; message?: string } => { + const protectionsRef = getPolicyProtectionsReference(); + let message: string | undefined; + + const hasEnabledProtection = protectionsRef.some(({ keyPath, osList, disableValue }) => { + const hasOsPropertyEnabled = osList.some((osValue) => { + const fullKeyPathForOs = `${osValue}.${keyPath}`; + const currentValue = get(policy, fullKeyPathForOs); + const isEnabled = currentValue !== disableValue; + + if (isEnabled) { + message = `property [${fullKeyPathForOs}] is set to [${currentValue}]`; + } + + return isEnabled; + }); + + return hasOsPropertyEnabled; + }); + + return { + isOnlyCollectingEvents: !hasEnabledProtection, + message, + }; +}; diff --git a/x-pack/plugins/security_solution/common/endpoint/types/utility_types.ts b/x-pack/plugins/security_solution/common/endpoint/types/utility_types.ts new file mode 100644 index 0000000000000..92880d9322191 --- /dev/null +++ b/x-pack/plugins/security_solution/common/endpoint/types/utility_types.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* eslint-disable @typescript-eslint/no-explicit-any */ + +export type PromiseResolvedValue> = T extends Promise + ? Value + : never; diff --git a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts index 50d4ae02eeb9b..058f8892013a0 100644 --- a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts +++ b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts @@ -17,6 +17,7 @@ import type { import type { PluginStartContract as AlertsPluginStartContract } from '@kbn/alerting-plugin/server'; import type { CloudSetup } from '@kbn/cloud-plugin/server'; import type { FleetActionsClientInterface } from '@kbn/fleet-plugin/server/services/actions/types'; +import type { AppFeatures } from '../lib/app_features'; import { getPackagePolicyCreateCallback, getPackagePolicyUpdateCallback, @@ -69,6 +70,7 @@ export interface EndpointAppContextServiceStartContract { actionCreateService: ActionCreateService | undefined; cloud: CloudSetup; esClient: ElasticsearchClient; + appFeatures: AppFeatures; } /** @@ -106,6 +108,7 @@ export class EndpointAppContextService { featureUsageService, endpointMetadataService, esClient, + appFeatures, } = dependencies; registerIngestCallback( @@ -117,7 +120,8 @@ export class EndpointAppContextService { alerting, licenseService, exceptionListsClient, - cloud + cloud, + appFeatures ) ); @@ -134,7 +138,8 @@ export class EndpointAppContextService { featureUsageService, endpointMetadataService, cloud, - esClient + esClient, + appFeatures ) ); diff --git a/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_policy_protections.test.ts b/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_policy_protections.test.ts new file mode 100644 index 0000000000000..1d39b72670b98 --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_policy_protections.test.ts @@ -0,0 +1,145 @@ +/* + * 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 { createMockEndpointAppContextServiceStartContract } from '../mocks'; +import type { Logger } from '@kbn/logging'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { EndpointInternalFleetServicesInterface } from '../services/fleet'; +import type { AppFeatures } from '../../lib/app_features'; +import { createAppFeaturesMock } from '../../lib/app_features/mocks'; +import { ALL_APP_FEATURE_KEYS } from '../../../common'; +import { turnOffPolicyProtectionsIfNotSupported } from './turn_off_policy_protections'; +import { FleetPackagePolicyGenerator } from '../../../common/endpoint/data_generators/fleet_package_policy_generator'; +import type { PolicyData } from '../../../common/endpoint/types'; +import type { PackagePolicyClient } from '@kbn/fleet-plugin/server'; +import type { PromiseResolvedValue } from '../../../common/endpoint/types/utility_types'; +import { ensureOnlyEventCollectionIsAllowed } from '../../../common/endpoint/models/policy_config_helpers'; + +describe('Turn Off Policy Protections Migration', () => { + let esClient: ElasticsearchClient; + let fleetServices: EndpointInternalFleetServicesInterface; + let appFeatures: AppFeatures; + let logger: Logger; + + const callTurnOffPolicyProtections = () => + turnOffPolicyProtectionsIfNotSupported(esClient, fleetServices, appFeatures, logger); + + beforeEach(() => { + const endpointContextStartContract = createMockEndpointAppContextServiceStartContract(); + + ({ esClient, appFeatures, logger } = endpointContextStartContract); + fleetServices = endpointContextStartContract.endpointFleetServicesFactory.asInternalUser(); + }); + + describe('and `endpointPolicyProtections` is enabled', () => { + it('should do nothing', async () => { + await callTurnOffPolicyProtections(); + + expect(fleetServices.packagePolicy.list as jest.Mock).not.toHaveBeenCalled(); + expect(logger.info).toHaveBeenLastCalledWith( + 'App feature [endpoint_policy_protections] is enabled. Nothing to do!' + ); + }); + }); + + describe('and `endpointPolicyProtections` is disabled', () => { + let policyGenerator: FleetPackagePolicyGenerator; + let page1Items: PolicyData[] = []; + let page2Items: PolicyData[] = []; + let bulkUpdateResponse: PromiseResolvedValue>; + + const generatePolicyMock = (withDisabledProtections = false): PolicyData => { + const policy = policyGenerator.generateEndpointPackagePolicy(); + + if (!withDisabledProtections) { + return policy; + } + + policy.inputs[0].config.policy.value = ensureOnlyEventCollectionIsAllowed( + policy.inputs[0].config.policy.value + ); + + return policy; + }; + + beforeEach(() => { + policyGenerator = new FleetPackagePolicyGenerator('seed'); + const packagePolicyListSrv = fleetServices.packagePolicy.list as jest.Mock; + + appFeatures = createAppFeaturesMock( + ALL_APP_FEATURE_KEYS.filter((key) => key !== 'endpoint_policy_protections') + ); + + page1Items = [generatePolicyMock(), generatePolicyMock(true)]; + page2Items = [generatePolicyMock(true), generatePolicyMock()]; + + packagePolicyListSrv + .mockImplementationOnce(async () => { + return { + total: 1500, + page: 1, + perPage: 1000, + items: page1Items, + }; + }) + .mockImplementationOnce(async () => { + return { + total: 1500, + page: 2, + perPage: 1000, + items: page2Items, + }; + }); + + bulkUpdateResponse = { + updatedPolicies: [page1Items[0], page2Items[1]], + failedPolicies: [], + }; + + (fleetServices.packagePolicy.bulkUpdate as jest.Mock).mockImplementation(async () => { + return bulkUpdateResponse; + }); + }); + + it('should update only policies that have protections turn on', async () => { + await callTurnOffPolicyProtections(); + + expect(fleetServices.packagePolicy.list as jest.Mock).toHaveBeenCalledTimes(2); + expect(fleetServices.packagePolicy.bulkUpdate as jest.Mock).toHaveBeenCalledWith( + fleetServices.internalSoClient, + esClient, + [ + expect.objectContaining({ id: bulkUpdateResponse.updatedPolicies![0].id }), + expect.objectContaining({ id: bulkUpdateResponse.updatedPolicies![1].id }), + ], + { user: { username: 'elastic' } } + ); + expect(logger.info).toHaveBeenCalledWith( + 'Found 2 policies that need updates:\n' + + `Policy [${bulkUpdateResponse.updatedPolicies![0].id}][${ + bulkUpdateResponse.updatedPolicies![0].name + }] updated to disable protections. Trigger: [property [mac.malware.mode] is set to [prevent]]\n` + + `Policy [${bulkUpdateResponse.updatedPolicies![1].id}][${ + bulkUpdateResponse.updatedPolicies![1].name + }] updated to disable protections. Trigger: [property [mac.malware.mode] is set to [prevent]]` + ); + expect(logger.info).toHaveBeenCalledWith('Done. All updates applied successfully'); + }); + + it('should log failures', async () => { + bulkUpdateResponse.failedPolicies.push({ + error: new Error('oh oh'), + packagePolicy: bulkUpdateResponse.updatedPolicies![0], + }); + await callTurnOffPolicyProtections(); + + expect(logger.error).toHaveBeenCalledWith( + expect.stringContaining('Done. 1 out of 2 failed to update:') + ); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_policy_protections.ts b/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_policy_protections.ts new file mode 100644 index 0000000000000..c4a63b8ec841c --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_policy_protections.ts @@ -0,0 +1,107 @@ +/* + * 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 { Logger, ElasticsearchClient } from '@kbn/core/server'; +import type { UpdatePackagePolicy } from '@kbn/fleet-plugin/common'; +import type { AuthenticatedUser } from '@kbn/security-plugin/common'; +import { + isPolicySetToEventCollectionOnly, + ensureOnlyEventCollectionIsAllowed, +} from '../../../common/endpoint/models/policy_config_helpers'; +import type { PolicyData } from '../../../common/endpoint/types'; +import { AppFeatureSecurityKey } from '../../../common/types/app_features'; +import type { EndpointInternalFleetServicesInterface } from '../services/fleet'; +import type { AppFeatures } from '../../lib/app_features'; +import { getPolicyDataForUpdate } from '../../../common/endpoint/service/policy'; + +export const turnOffPolicyProtectionsIfNotSupported = async ( + esClient: ElasticsearchClient, + fleetServices: EndpointInternalFleetServicesInterface, + appFeaturesService: AppFeatures, + logger: Logger +): Promise => { + const log = logger.get('endpoint', 'policyProtections'); + + if (appFeaturesService.isEnabled(AppFeatureSecurityKey.endpointPolicyProtections)) { + log.info( + `App feature [${AppFeatureSecurityKey.endpointPolicyProtections}] is enabled. Nothing to do!` + ); + + return; + } + + log.info( + `App feature [${AppFeatureSecurityKey.endpointPolicyProtections}] is disabled. Checking endpoint integration policies for compliance` + ); + + const { packagePolicy, internalSoClient, endpointPolicyKuery } = fleetServices; + const updates: UpdatePackagePolicy[] = []; + const messages: string[] = []; + const perPage = 1000; + let hasMoreData = true; + let total = 0; + let page = 1; + + do { + const currentPage = page++; + const { items, total: totalPolicies } = await packagePolicy.list(internalSoClient, { + page: currentPage, + kuery: endpointPolicyKuery, + perPage, + }); + + total = totalPolicies; + hasMoreData = currentPage * perPage < total; + + for (const item of items) { + const integrationPolicy = item as PolicyData; + const policySettings = integrationPolicy.inputs[0].config.policy.value; + const { message, isOnlyCollectingEvents } = isPolicySetToEventCollectionOnly(policySettings); + + if (!isOnlyCollectingEvents) { + messages.push( + `Policy [${integrationPolicy.id}][${integrationPolicy.name}] updated to disable protections. Trigger: [${message}]` + ); + + integrationPolicy.inputs[0].config.policy.value = + ensureOnlyEventCollectionIsAllowed(policySettings); + + updates.push({ + ...getPolicyDataForUpdate(integrationPolicy), + id: integrationPolicy.id, + }); + } + } + } while (hasMoreData); + + if (updates.length > 0) { + log.info(`Found ${updates.length} policies that need updates:\n${messages.join('\n')}`); + + const bulkUpdateResponse = await fleetServices.packagePolicy.bulkUpdate( + internalSoClient, + esClient, + updates, + { + user: { username: 'elastic' } as AuthenticatedUser, + } + ); + + log.debug(`Bulk update response:\n${JSON.stringify(bulkUpdateResponse, null, 2)}`); + + if (bulkUpdateResponse.failedPolicies.length > 0) { + log.error( + `Done. ${bulkUpdateResponse.failedPolicies.length} out of ${ + updates.length + } failed to update:\n${JSON.stringify(bulkUpdateResponse.failedPolicies, null, 2)}` + ); + } else { + log.info(`Done. All updates applied successfully`); + } + } else { + log.info(`Done. Checked ${total} policies and no updates needed`); + } +}; diff --git a/x-pack/plugins/security_solution/server/endpoint/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/mocks.ts index f38d96cbf7063..5a3c9ee2297ac 100644 --- a/x-pack/plugins/security_solution/server/endpoint/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/mocks.ts @@ -71,6 +71,7 @@ import type { EndpointAuthz } from '../../common/endpoint/types/authz'; import { EndpointFleetServicesFactory } from './services/fleet'; import { createLicenseServiceMock } from '../../common/license/mocks'; import { createFeatureUsageServiceMock } from './services/feature_usage/mocks'; +import { createAppFeaturesMock } from '../lib/app_features/mocks'; /** * Creates a mocked EndpointAppContext. @@ -163,6 +164,8 @@ export const createMockEndpointAppContextServiceStartContract = }, savedObjectsStart ); + const experimentalFeatures = config.experimentalFeatures; + const appFeatures = createAppFeaturesMock(undefined, experimentalFeatures, undefined, logger); packagePolicyService.list.mockImplementation(async (_, options) => { return { @@ -207,11 +210,12 @@ export const createMockEndpointAppContextServiceStartContract = cases: casesMock, cloud: cloudMock.createSetup(), featureUsageService: createFeatureUsageServiceMock(), - experimentalFeatures: createMockConfig().experimentalFeatures, + experimentalFeatures, messageSigningService: createMessageSigningServiceMock(), actionCreateService: undefined, createFleetActionsClient: jest.fn((_) => fleetActionsClientMock), esClient: elasticsearchClientMock.createElasticsearchClient(), + appFeatures, }; }; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/fleet/endpoint_fleet_services_factory.ts b/x-pack/plugins/security_solution/server/endpoint/services/fleet/endpoint_fleet_services_factory.ts index 658ff9f2a327e..1f3df9d6a67d3 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/fleet/endpoint_fleet_services_factory.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/fleet/endpoint_fleet_services_factory.ts @@ -13,6 +13,8 @@ import type { PackagePolicyClient, PackageClient, } from '@kbn/fleet-plugin/server'; +import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; +import { createInternalSoClient } from '../../utils/create_internal_so_client'; import { createInternalReadonlySoClient } from '../../utils/create_internal_readonly_so_client'; export interface EndpointFleetServicesFactoryInterface { @@ -42,7 +44,10 @@ export class EndpointFleetServicesFactory implements EndpointFleetServicesFactor packages: packageService.asInternalUser, packagePolicy, + endpointPolicyKuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: "endpoint"`, + internalReadonlySoClient: createInternalReadonlySoClient(this.savedObjectsStart), + internalSoClient: createInternalSoClient(this.savedObjectsStart), }; } } @@ -55,6 +60,8 @@ export interface EndpointFleetServicesInterface { agentPolicy: AgentPolicyServiceInterface; packages: PackageClient; packagePolicy: PackagePolicyClient; + /** The `kuery` that can be used to filter for Endpoint integration policies */ + endpointPolicyKuery: string; } export interface EndpointInternalFleetServicesInterface extends EndpointFleetServicesInterface { @@ -62,4 +69,7 @@ export interface EndpointInternalFleetServicesInterface extends EndpointFleetSer * An internal SO client (readonly) that can be used with the Fleet services that require it */ internalReadonlySoClient: SavedObjectsClientContract; + + /** Internal SO client. USE ONLY WHEN ABSOLUTELY NEEDED. Else, use the `internalReadonlySoClient` */ + internalSoClient: SavedObjectsClientContract; } diff --git a/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_readonly_so_client.ts b/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_readonly_so_client.ts index d8bf7badec846..b621222e79c0a 100644 --- a/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_readonly_so_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_readonly_so_client.ts @@ -5,12 +5,8 @@ * 2.0. */ -import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server'; -import type { - KibanaRequest, - SavedObjectsClientContract, - SavedObjectsServiceStart, -} from '@kbn/core/server'; +import type { SavedObjectsClientContract, SavedObjectsServiceStart } from '@kbn/core/server'; +import { createInternalSoClient } from './create_internal_so_client'; import { EndpointError } from '../../../common/endpoint/errors'; type SavedObjectsClientContractKeys = keyof SavedObjectsClientContract; @@ -37,18 +33,7 @@ export class InternalReadonlySoClientMethodNotAllowedError extends EndpointError export const createInternalReadonlySoClient = ( savedObjectsServiceStart: SavedObjectsServiceStart ): SavedObjectsClientContract => { - const fakeRequest = { - headers: {}, - getBasePath: () => '', - path: '/', - route: { settings: {} }, - url: { href: {} }, - raw: { req: { url: '/' } }, - } as unknown as KibanaRequest; - - const internalSoClient = savedObjectsServiceStart.getScopedClient(fakeRequest, { - excludedExtensions: [SECURITY_EXTENSION_ID], - }); + const internalSoClient = createInternalSoClient(savedObjectsServiceStart); return new Proxy(internalSoClient, { get( diff --git a/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_so_client.ts b/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_so_client.ts new file mode 100644 index 0000000000000..88e0d7a70a4c3 --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_so_client.ts @@ -0,0 +1,27 @@ +/* + * 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 { SavedObjectsServiceStart } from '@kbn/core-saved-objects-server'; +import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server'; +import type { KibanaRequest, SavedObjectsClientContract } from '@kbn/core/server'; + +export const createInternalSoClient = ( + savedObjectsServiceStart: SavedObjectsServiceStart +): SavedObjectsClientContract => { + const fakeRequest = { + headers: {}, + getBasePath: () => '', + path: '/', + route: { settings: {} }, + url: { href: {} }, + raw: { req: { url: '/' } }, + } as unknown as KibanaRequest; + + return savedObjectsServiceStart.getScopedClient(fakeRequest, { + excludedExtensions: [SECURITY_EXTENSION_ID], + }); +}; diff --git a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts index 54258638f1230..f26531296b6a2 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts @@ -54,6 +54,9 @@ import { createMockPolicyData } from '../endpoint/services/feature_usage/mocks'; import { ALL_ENDPOINT_ARTIFACT_LIST_IDS } from '../../common/endpoint/service/artifacts/constants'; import { ENDPOINT_EVENT_FILTERS_LIST_ID } from '@kbn/securitysolution-list-constants'; import { disableProtections } from '../../common/endpoint/models/policy_config_helpers'; +import type { AppFeatures } from '../lib/app_features'; +import { createAppFeaturesMock } from '../lib/app_features/mocks'; +import { ALL_APP_FEATURE_KEYS } from '../../common'; jest.mock('uuid', () => ({ v4: (): string => 'NEW_UUID', @@ -74,6 +77,7 @@ describe('ingest_integration tests ', () => { }); const generator = new EndpointDocGenerator(); const cloudService = cloudMock.createSetup(); + let appFeatures: AppFeatures; beforeEach(() => { endpointAppContextMock = createMockEndpointAppContextServiceStartContract(); @@ -82,6 +86,7 @@ describe('ingest_integration tests ', () => { licenseEmitter = new Subject(); licenseService = new LicenseService(); licenseService.start(licenseEmitter); + appFeatures = endpointAppContextMock.appFeatures; jest .spyOn(endpointAppContextMock.endpointMetadataService, 'getFleetEndpointPackagePolicy') @@ -129,7 +134,8 @@ describe('ingest_integration tests ', () => { endpointAppContextMock.alerting, licenseService, exceptionListClient, - cloudService + cloudService, + appFeatures ); return callback( @@ -363,6 +369,7 @@ describe('ingest_integration tests ', () => { ); }); }); + describe('package policy update callback (when the license is below platinum)', () => { const soClient = savedObjectsClientMock.create(); const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; @@ -379,7 +386,8 @@ describe('ingest_integration tests ', () => { endpointAppContextMock.featureUsageService, endpointAppContextMock.endpointMetadataService, cloudService, - esClient + esClient, + appFeatures ); const policyConfig = generator.generatePolicyPackagePolicy(); policyConfig.inputs[0]!.config!.policy.value = mockPolicy; @@ -397,7 +405,8 @@ describe('ingest_integration tests ', () => { endpointAppContextMock.featureUsageService, endpointAppContextMock.endpointMetadataService, cloudService, - esClient + esClient, + appFeatures ); const policyConfig = generator.generatePolicyPackagePolicy(); policyConfig.inputs[0]!.config!.policy.value = mockPolicy; @@ -419,6 +428,7 @@ describe('ingest_integration tests ', () => { beforeEach(() => { licenseEmitter.next(Platinum); // set license level to platinum }); + it('updates successfully when paid features are turned on', async () => { const mockPolicy = policyFactory(); mockPolicy.windows.popup.malware.message = 'paid feature'; @@ -429,7 +439,8 @@ describe('ingest_integration tests ', () => { endpointAppContextMock.featureUsageService, endpointAppContextMock.endpointMetadataService, cloudService, - esClient + esClient, + appFeatures ); const policyConfig = generator.generatePolicyPackagePolicy(); policyConfig.inputs[0]!.config!.policy.value = mockPolicy; @@ -442,6 +453,50 @@ describe('ingest_integration tests ', () => { ); expect(updatedPolicyConfig.inputs[0]!.config!.policy.value).toEqual(mockPolicy); }); + + it('should turn off protections if endpointPolicyProtections appFeature is disabled', async () => { + appFeatures = createAppFeaturesMock( + ALL_APP_FEATURE_KEYS.filter((key) => key !== 'endpoint_policy_protections') + ); + const callback = getPackagePolicyUpdateCallback( + endpointAppContextMock.logger, + licenseService, + endpointAppContextMock.featureUsageService, + endpointAppContextMock.endpointMetadataService, + cloudService, + esClient, + appFeatures + ); + + const updatedPolicy = await callback( + generator.generatePolicyPackagePolicy(), + soClient, + esClient, + requestContextMock.convertContext(ctx), + req + ); + + expect(updatedPolicy.inputs?.[0]?.config?.policy.value).toMatchObject({ + linux: { + behavior_protection: { mode: 'off' }, + malware: { mode: 'off' }, + memory_protection: { mode: 'off' }, + }, + mac: { + behavior_protection: { mode: 'off' }, + malware: { mode: 'off' }, + memory_protection: { mode: 'off' }, + }, + windows: { + antivirus_registration: { enabled: false }, + attack_surface_reduction: { credential_hardening: { enabled: false } }, + behavior_protection: { mode: 'off' }, + malware: { blocklist: false }, + memory_protection: { mode: 'off' }, + ransomware: { mode: 'off' }, + }, + }); + }); }); describe('package policy update callback when meta fields should be updated', () => { @@ -486,7 +541,8 @@ describe('ingest_integration tests ', () => { endpointAppContextMock.featureUsageService, endpointAppContextMock.endpointMetadataService, cloudService, - esClient + esClient, + appFeatures ); const policyConfig = generator.generatePolicyPackagePolicy(); @@ -520,7 +576,8 @@ describe('ingest_integration tests ', () => { endpointAppContextMock.featureUsageService, endpointAppContextMock.endpointMetadataService, cloudService, - esClient + esClient, + appFeatures ); const policyConfig = generator.generatePolicyPackagePolicy(); // values should be updated diff --git a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts index b897441fe1e04..04bc9afa6d3a1 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts @@ -22,6 +22,12 @@ import type { } from '@kbn/fleet-plugin/common'; import type { CloudSetup } from '@kbn/cloud-plugin/server'; import type { InfoResponse } from '@elastic/elasticsearch/lib/api/types'; +import { AppFeatureSecurityKey } from '../../common/types/app_features'; +import { + isPolicySetToEventCollectionOnly, + ensureOnlyEventCollectionIsAllowed, +} from '../../common/endpoint/models/policy_config_helpers'; +import type { AppFeatures } from '../lib/app_features'; import type { NewPolicyData, PolicyConfig } from '../../common/endpoint/types'; import type { LicenseService } from '../../common/license'; import type { ManifestManager } from '../endpoint/services'; @@ -72,7 +78,8 @@ export const getPackagePolicyCreateCallback = ( alerts: AlertsStartContract, licenseService: LicenseService, exceptionsClient: ExceptionListClient | undefined, - cloud: CloudSetup + cloud: CloudSetup, + appFeatures: AppFeatures ): PostPackagePolicyCreateCallback => { return async ( newPackagePolicy, @@ -140,7 +147,8 @@ export const getPackagePolicyCreateCallback = ( licenseService, endpointIntegrationConfig, cloud, - esClientInfo + esClientInfo, + appFeatures ); return { @@ -175,31 +183,38 @@ export const getPackagePolicyUpdateCallback = ( featureUsageService: FeatureUsageService, endpointMetadataService: EndpointMetadataService, cloud: CloudSetup, - esClient: ElasticsearchClient + esClient: ElasticsearchClient, + appFeatures: AppFeatures ): PutPackagePolicyUpdateCallback => { return async (newPackagePolicy: NewPackagePolicy): Promise => { if (!isEndpointPackagePolicy(newPackagePolicy)) { return newPackagePolicy; } + const endpointIntegrationData = newPackagePolicy as NewPolicyData; + // Validate that Endpoint Security policy is valid against current license validatePolicyAgainstLicense( // The cast below is needed in order to ensure proper typing for // the policy configuration specific for endpoint - newPackagePolicy.inputs[0].config?.policy?.value as PolicyConfig, + endpointIntegrationData.inputs[0].config?.policy?.value as PolicyConfig, licenseService, logger ); - notifyProtectionFeatureUsage(newPackagePolicy, featureUsageService, endpointMetadataService); + notifyProtectionFeatureUsage( + endpointIntegrationData, + featureUsageService, + endpointMetadataService + ); - const newEndpointPackagePolicy = newPackagePolicy.inputs[0].config?.policy + const newEndpointPackagePolicy = endpointIntegrationData.inputs[0].config?.policy ?.value as PolicyConfig; const esClientInfo: InfoResponse = await esClient.info(); if ( - newPackagePolicy.inputs[0].config?.policy?.value && + endpointIntegrationData.inputs[0].config?.policy?.value && shouldUpdateMetaValues( newEndpointPackagePolicy, licenseService.getLicenseType(), @@ -214,10 +229,25 @@ export const getPackagePolicyUpdateCallback = ( newEndpointPackagePolicy.meta.cluster_name = esClientInfo.cluster_name; newEndpointPackagePolicy.meta.cluster_uuid = esClientInfo.cluster_uuid; newEndpointPackagePolicy.meta.license_uid = licenseService.getLicenseUID(); - newPackagePolicy.inputs[0].config.policy.value = newEndpointPackagePolicy; + + endpointIntegrationData.inputs[0].config.policy.value = newEndpointPackagePolicy; + } + + // If no Policy Protection allowed (ex. serverless) + const eventsOnlyPolicy = isPolicySetToEventCollectionOnly(newEndpointPackagePolicy); + if ( + !appFeatures.isEnabled(AppFeatureSecurityKey.endpointPolicyProtections) && + !eventsOnlyPolicy.isOnlyCollectingEvents + ) { + logger.warn( + `Endpoint integration policy [${endpointIntegrationData.id}][${endpointIntegrationData.name}] adjusted due to [endpointPolicyProtections] appFeature not being enabled. Trigger [${eventsOnlyPolicy.message}]` + ); + + endpointIntegrationData.inputs[0].config.policy.value = + ensureOnlyEventCollectionIsAllowed(newEndpointPackagePolicy); } - return newPackagePolicy; + return endpointIntegrationData; }; }; diff --git a/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts b/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts index b707199aa4738..9208f9f7f22cd 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts @@ -19,6 +19,9 @@ import type { PolicyCreateCloudConfig, PolicyCreateEndpointConfig, } from '../types'; +import type { AppFeatures } from '../../lib/app_features'; +import { createAppFeaturesMock } from '../../lib/app_features/mocks'; +import { ALL_APP_FEATURE_KEYS } from '../../../common'; describe('Create Default Policy tests ', () => { const cloud = cloudMock.createSetup(); @@ -28,6 +31,7 @@ describe('Create Default Policy tests ', () => { const Gold = licenseMock.createLicense({ license: { type: 'gold', mode: 'gold', uid: '' } }); let licenseEmitter: Subject; let licenseService: LicenseService; + let appFeatures: AppFeatures; const createDefaultPolicyCallback = async ( config: AnyPolicyCreateConfig | undefined @@ -35,7 +39,7 @@ describe('Create Default Policy tests ', () => { const esClientInfo = await elasticsearchServiceMock.createClusterClient().asInternalUser.info(); esClientInfo.cluster_name = ''; esClientInfo.cluster_uuid = ''; - return createDefaultPolicy(licenseService, config, cloud, esClientInfo); + return createDefaultPolicy(licenseService, config, cloud, esClientInfo, appFeatures); }; beforeEach(() => { @@ -43,7 +47,9 @@ describe('Create Default Policy tests ', () => { licenseService = new LicenseService(); licenseService.start(licenseEmitter); licenseEmitter.next(Platinum); // set license level to platinum + appFeatures = createAppFeaturesMock(); }); + describe('When no config is set', () => { it('Should return PolicyConfig for events only when license is at least platinum', async () => { const defaultPolicy = policyFactory(); @@ -174,6 +180,7 @@ describe('Create Default Policy tests ', () => { }); }); }); + it('Should return process, file and network events enabled when preset is EDR Essential', async () => { const config = createEndpointConfig({ preset: 'EDREssential' }); const policy = await createDefaultPolicyCallback(config); @@ -190,6 +197,7 @@ describe('Create Default Policy tests ', () => { }); }); }); + it('Should return the default config when preset is EDR Complete', async () => { const config = createEndpointConfig({ preset: 'EDRComplete' }); const policy = await createDefaultPolicyCallback(config); @@ -199,7 +207,37 @@ describe('Create Default Policy tests ', () => { defaultPolicy.meta.cloud = true; expect(policy).toMatchObject(defaultPolicy); }); + + it('should set policy to event collection only if endpointPolicyProtections appFeature is disabled', async () => { + appFeatures = createAppFeaturesMock( + ALL_APP_FEATURE_KEYS.filter((key) => key !== 'endpoint_policy_protections') + ); + + await expect( + createDefaultPolicyCallback(createEndpointConfig({ preset: 'EDRComplete' })) + ).resolves.toMatchObject({ + linux: { + behavior_protection: { mode: 'off' }, + malware: { mode: 'off' }, + memory_protection: { mode: 'off' }, + }, + mac: { + behavior_protection: { mode: 'off' }, + malware: { mode: 'off' }, + memory_protection: { mode: 'off' }, + }, + windows: { + antivirus_registration: { enabled: false }, + attack_surface_reduction: { credential_hardening: { enabled: false } }, + behavior_protection: { mode: 'off' }, + malware: { blocklist: false }, + memory_protection: { mode: 'off' }, + ransomware: { mode: 'off' }, + }, + }); + }); }); + describe('When cloud config is set', () => { const createCloudConfig = (): PolicyCreateCloudConfig => ({ type: 'cloud', diff --git a/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.ts b/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.ts index d7c3994c05dc9..db053fd5c3b0e 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.ts @@ -7,6 +7,8 @@ import type { CloudSetup } from '@kbn/cloud-plugin/server'; import type { InfoResponse } from '@elastic/elasticsearch/lib/api/types'; +import { AppFeatureSecurityKey } from '../../../common/types/app_features'; +import type { AppFeatures } from '../../lib/app_features'; import { policyFactory as policyConfigFactory, policyFactoryWithoutPaidFeatures as policyConfigFactoryWithoutPaidFeatures, @@ -20,7 +22,10 @@ import { ENDPOINT_CONFIG_PRESET_NGAV, ENDPOINT_CONFIG_PRESET_DATA_COLLECTION, } from '../constants'; -import { disableProtections } from '../../../common/endpoint/models/policy_config_helpers'; +import { + disableProtections, + ensureOnlyEventCollectionIsAllowed, +} from '../../../common/endpoint/models/policy_config_helpers'; /** * Create the default endpoint policy based on the current license and configuration type @@ -29,7 +34,8 @@ export const createDefaultPolicy = ( licenseService: LicenseService, config: AnyPolicyCreateConfig | undefined, cloud: CloudSetup, - esClientInfo: InfoResponse + esClientInfo: InfoResponse, + appFeatures: AppFeatures ): PolicyConfig => { const factoryPolicy = policyConfigFactory(); @@ -44,15 +50,21 @@ export const createDefaultPolicy = ( : factoryPolicy.meta.cluster_uuid; factoryPolicy.meta.license_uid = licenseService.getLicenseUID(); - const defaultPolicyPerType = + let defaultPolicyPerType: PolicyConfig = config?.type === 'cloud' ? getCloudPolicyConfig(factoryPolicy) : getEndpointPolicyWithIntegrationConfig(factoryPolicy, config); - // Apply license limitations in the final step, so it's not overriden (see malware popup) - return licenseService.isPlatinumPlus() - ? defaultPolicyPerType - : policyConfigFactoryWithoutPaidFeatures(defaultPolicyPerType); + if (!licenseService.isPlatinumPlus()) { + defaultPolicyPerType = policyConfigFactoryWithoutPaidFeatures(defaultPolicyPerType); + } + + // If no Policy Protection allowed (ex. serverless) + if (!appFeatures.isEnabled(AppFeatureSecurityKey.endpointPolicyProtections)) { + defaultPolicyPerType = ensureOnlyEventCollectionIsAllowed(defaultPolicyPerType); + } + + return defaultPolicyPerType; }; /** diff --git a/x-pack/plugins/security_solution/server/lib/app_features/app_features.ts b/x-pack/plugins/security_solution/server/lib/app_features/app_features.ts index 69c6c33d335c3..edeec0d533a40 100644 --- a/x-pack/plugins/security_solution/server/lib/app_features/app_features.ts +++ b/x-pack/plugins/security_solution/server/lib/app_features/app_features.ts @@ -59,7 +59,7 @@ export class AppFeatures { return this.appFeatures.has(appFeatureKey); } - private registerEnabledKibanaFeatures() { + protected registerEnabledKibanaFeatures() { if (this.featuresSetup == null) { throw new Error( 'Cannot sync kibana features as featuresSetup is not present. Did you call init?' diff --git a/x-pack/plugins/security_solution/server/lib/app_features/mocks.ts b/x-pack/plugins/security_solution/server/lib/app_features/mocks.ts new file mode 100644 index 0000000000000..1a5efc9c64e37 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/app_features/mocks.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger } from '@kbn/core/server'; +import type { PluginSetupContract as FeaturesPluginSetup } from '@kbn/features-plugin/server'; +import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; +import { featuresPluginMock } from '@kbn/features-plugin/server/mocks'; +import { AppFeatures } from './app_features'; +import type { AppFeatureKeys, ExperimentalFeatures } from '../../../common'; +import { ALL_APP_FEATURE_KEYS, allowedExperimentalValues } from '../../../common'; + +class AppFeaturesMock extends AppFeatures { + protected registerEnabledKibanaFeatures() { + // NOOP + } +} + +export const createAppFeaturesMock = ( + /** What features keys should be enabled. Default is all */ + enabledFeatureKeys: AppFeatureKeys = [...ALL_APP_FEATURE_KEYS], + experimentalFeatures: ExperimentalFeatures = { ...allowedExperimentalValues }, + featuresPluginSetupContract: FeaturesPluginSetup = featuresPluginMock.createSetup(), + logger: Logger = loggingSystemMock.create().get('appFeatureMock') +) => { + const appFeatures = new AppFeaturesMock(logger, experimentalFeatures); + + appFeatures.init(featuresPluginSetupContract); + + if (enabledFeatureKeys) { + appFeatures.set(enabledFeatureKeys); + } + + return appFeatures; +}; diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index e64a5808eb3dd..4e7beb558d4de 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -17,6 +17,7 @@ import { Dataset } from '@kbn/rule-registry-plugin/server'; import type { ListPluginSetup } from '@kbn/lists-plugin/server'; import type { ILicense } from '@kbn/licensing-plugin/server'; +import { turnOffPolicyProtectionsIfNotSupported } from './endpoint/migrations/turn_off_policy_protections'; import { endpointSearchStrategyProvider } from './search_strategy/endpoint'; import { getScheduleNotificationResponseActionsService } from './lib/detection_engine/rule_response_actions/schedule_notification_response_actions'; import { siemGuideId, siemGuideConfig } from '../common/guided_onboarding/siem_guide_config'; @@ -438,6 +439,15 @@ export class Plugin implements ISecuritySolutionPlugin { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion plugins.fleet!; let manifestManager: ManifestManager | undefined; + const endpointFleetServicesFactory = new EndpointFleetServicesFactory( + { + agentService, + packageService, + packagePolicyService, + agentPolicyService, + }, + core.savedObjects + ); this.licensing$ = plugins.licensing.license$; @@ -459,17 +469,23 @@ export class Plugin implements ISecuritySolutionPlugin { esClient: core.elasticsearch.client.asInternalUser, }); - // Migrate artifacts to fleet and then start the minifest task after that is done + // Migrate artifacts to fleet and then start the manifest task after that is done plugins.fleet.fleetSetupCompleted().then(() => { - logger.info('Dependent plugin setup complete - Starting ManifestTask'); - if (this.manifestTask) { + logger.info('Dependent plugin setup complete - Starting ManifestTask'); this.manifestTask.start({ taskManager, }); } else { logger.error(new Error('User artifacts task not available.')); } + + turnOffPolicyProtectionsIfNotSupported( + core.elasticsearch.client.asInternalUser, + endpointFleetServicesFactory.asInternalUser(), + this.appFeatures, + logger + ); }); // License related start @@ -493,15 +509,7 @@ export class Plugin implements ISecuritySolutionPlugin { packagePolicyService, logger ), - endpointFleetServicesFactory: new EndpointFleetServicesFactory( - { - agentService, - packageService, - packagePolicyService, - agentPolicyService, - }, - core.savedObjects - ), + endpointFleetServicesFactory, security: plugins.security, alerting: plugins.alerting, config: this.config, @@ -522,6 +530,7 @@ export class Plugin implements ISecuritySolutionPlugin { ), createFleetActionsClient, esClient: core.elasticsearch.client.asInternalUser, + appFeatures: this.appFeatures, }); this.telemetryReceiver.start( From f09a5c976c8a8059a448b389738ea44b4fd1f60c Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Fri, 11 Aug 2023 15:32:42 +0200 Subject: [PATCH 10/11] [Log Explorer] Add test suite for Dataset Selector (#163079) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📓 Summary Closes #160627 This implementation adds the majority of the tests listed down here for the Log Explorer current implementation. ✅ [**Flaky Test Runner - x50 executions**](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/2844#_) ``` ↳ Discover Log-Explorer profile ↳ Columns selection initialization and update ↳ when the log explorer profile loads ↳ should initialize the table columns to logs' default selection ↳ should restore the table columns from the URL state if exists ↳ Customizations ↳ when Discover is loaded with the log-explorer profile ↳ DatasetSelector should replace the DataViewPicker ↳ the TopNav bar should hide the New, Open and Save options ↳ should add a searchable deep link to the profile page ↳ should render a filter controls section as part of the unified search bar ↳ DatasetSelection initialization and update ↳ when the "index" query param does not exist ↳ should initialize the "All log datasets" selection ↳ when the "index" query param exists ↳ should decode and restore the selection from a valid encoded index ↳ should fallback to the "All log datasets" selection and notify the user of an invalid encoded index ↳ when navigating back and forth on the page history ↳ should decode and restore the selection for the current index ↳ Dataset Selector ↳ without installed integrations or uncategorized data streams ↳ when open on the first navigation level ↳ should always display the "All log datasets" entry as the first item ↳ should always display the unmanaged datasets entry as the second item ↳ should display an error prompt if could not retrieve the integrations ↳ should display an empty prompt for no integrations ↳ when navigating into Uncategorized data streams ↳ should display a loading skeleton while loading ↳ should display an error prompt if could not retrieve the data streams ↳ should display an empty prompt for no data streams ↳ with installed integrations and uncategorized data streams ↳ when open on the first navigation level ↳ should always display the "All log datasets" entry as the first item ↳ should always display the unmanaged datasets entry as the second item ↳ should display a list of installed integrations ↳ should sort the integrations list by the clicked sorting option ↳ should filter the integrations list by the typed integration name ↳ should display an empty prompt when the search does not match any result ↳ should load more integrations by scrolling to the end of the list ↳ when clicking on integration and moving into the second navigation level ↳ should display a list of available datasets ↳ should sort the datasets list by the clicked sorting option ↳ should filter the datasets list by the typed dataset name ↳ should update the current selection with the clicked dataset ↳ when navigating into Uncategorized data streams ↳ should display a list of available datasets ↳ should sort the datasets list by the clicked sorting option ↳ should filter the datasets list by the typed dataset name ↳ should update the current selection with the clicked dataset ↳ when open/close the selector ↳ should restore the latest navigation panel ↳ should restore the latest search results ↳ when switching between integration panels ↳ should remember the latest search and restore its results for each integration ``` ## Note on serverless tests suite For testing the feature in a serverless environment, we are copying all the tests into the `x-pack/test_serverless` folder until https://github.com/elastic/kibana/pull/161574 is merged, which will provide a new space to write tests independently from the deployment type, avoiding then tests duplication. ## New `browser` service utils for Network conditions simulation To properly test that this feature works correctly under poor network conditions or offline scenarios, the `browser` service now exposes some new methods for altering network conditions on demand. Also, network profiles to match the [network profiles provided by Chrome debugger](https://github.com/ChromeDevTools/devtools-frontend/blob/da276a3faec9769cb55e442f0db77ebdce5cd178/front_end/core/sdk/NetworkManager.ts#L363-L393) have been created. In case the browser is not of `chromium` type and the driver does not support the network simulation, these methods throw an error that can be caught for skipping the affected test. --------- Co-authored-by: Marco Antonio Ghiani Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../dataset_selector/dataset_selector.tsx | 1 + .../sub_components/datasets_list.tsx | 2 + .../sub_components/datasets_popover.tsx | 12 +- .../sub_components/datasets_skeleton.tsx | 2 +- .../integrations_list_status.tsx | 1 + .../sub_components/search_controls.tsx | 7 +- .../components/dataset_selector/utils.tsx | 4 + .../customizations/custom_dataset_filters.tsx | 4 +- .../columns_selection.ts | 57 ++ .../discover_log_explorer/customization.ts | 16 +- .../dataset_selection_state.ts | 4 +- .../discover_log_explorer/dataset_selector.ts | 664 ++++++++++++++++++ .../apps/discover_log_explorer/index.ts | 2 + .../data_streams/data.json.gz | Bin 0 -> 29526 bytes .../data_streams/mappings.json | 419 +++++++++++ .../page_objects/discover_log_explorer.ts | 281 +++++++- .../columns_selection.ts | 57 ++ .../discover_log_explorer/customization.ts | 14 +- .../discover_log_explorer/dataset_selector.ts | 664 ++++++++++++++++++ .../discover_log_explorer/index.ts | 2 + 20 files changed, 2198 insertions(+), 15 deletions(-) create mode 100644 x-pack/test/functional/apps/discover_log_explorer/columns_selection.ts create mode 100644 x-pack/test/functional/apps/discover_log_explorer/dataset_selector.ts create mode 100644 x-pack/test/functional/es_archives/discover_log_explorer/data_streams/data.json.gz create mode 100644 x-pack/test/functional/es_archives/discover_log_explorer/data_streams/mappings.json create mode 100644 x-pack/test_serverless/functional/test_suites/observability/discover_log_explorer/columns_selection.ts create mode 100644 x-pack/test_serverless/functional/test_suites/observability/discover_log_explorer/dataset_selector.ts diff --git a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/dataset_selector.tsx b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/dataset_selector.tsx index aa1fb057bbd32..444ec69c9d266 100644 --- a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/dataset_selector.tsx +++ b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/dataset_selector.tsx @@ -168,6 +168,7 @@ export function DatasetSelector({ onPanelChange={changePanel} className="eui-yScroll" css={contextMenuStyles} + data-test-subj="datasetSelectorContextMenu" size="s" /> diff --git a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/datasets_list.tsx b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/datasets_list.tsx index 9decd3732fda4..134bd7616ac8a 100644 --- a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/datasets_list.tsx +++ b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/datasets_list.tsx @@ -44,6 +44,7 @@ export const DatasetsList = ({ if (hasError) { return ( {noDatasetsLabel}} diff --git a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/datasets_popover.tsx b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/datasets_popover.tsx index d6c9771da019d..7428ffbd47612 100644 --- a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/datasets_popover.tsx +++ b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/datasets_popover.tsx @@ -45,7 +45,8 @@ export const DatasetsPopover = ({ return ( {iconType ? ( @@ -74,7 +75,12 @@ export const DatasetsPopover = ({ {...(isMobile && { display: 'block' })} {...props} > - + <span>{selectDatasetLabel}</span> diff --git a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/datasets_skeleton.tsx b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/datasets_skeleton.tsx index be9464204497d..8a9ee61d8e434 100644 --- a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/datasets_skeleton.tsx +++ b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/datasets_skeleton.tsx @@ -10,7 +10,7 @@ import { EuiPanel, EuiSkeletonText } from '@elastic/eui'; import { uncategorizedLabel } from '../constants'; export const DatasetSkeleton = () => ( - + ); diff --git a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/integrations_list_status.tsx b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/integrations_list_status.tsx index 418ccbefffd00..42ff78fdd3e83 100644 --- a/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/integrations_list_status.tsx +++ b/x-pack/plugins/discover_log_explorer/public/components/dataset_selector/sub_components/integrations_list_status.tsx @@ -34,6 +34,7 @@ export const IntegrationsListStatus = ({ if (hasError) { return ( + , + 'data-test-subj': integration.id, panel: integration.id, ...(isLastIntegration && { buttonRef: spyRef }), }); @@ -85,6 +86,7 @@ export const createAllLogDatasetsItem = ({ onClick }: { onClick(): void }) => { const allLogDataset = Dataset.createAllLogsDataset(); return { name: allLogDataset.title, + 'data-test-subj': 'allLogDatasets', icon: allLogDataset.iconType && , onClick, }; @@ -93,6 +95,7 @@ export const createAllLogDatasetsItem = ({ onClick }: { onClick(): void }) => { export const createUnmanagedDatasetsItem = ({ onClick }: { onClick: LoadDatasets }) => { return { name: uncategorizedLabel, + 'data-test-subj': 'unmanagedDatasets', icon: , onClick, panel: UNMANAGED_STREAMS_PANEL_ID, @@ -103,5 +106,6 @@ export const createIntegrationStatusItem = (props: IntegrationsListStatusProps) return { disabled: true, name: , + 'data-test-subj': 'integrationStatusItem', }; }; diff --git a/x-pack/plugins/discover_log_explorer/public/customizations/custom_dataset_filters.tsx b/x-pack/plugins/discover_log_explorer/public/customizations/custom_dataset_filters.tsx index f2e1114eeefc7..a669ebea33942 100644 --- a/x-pack/plugins/discover_log_explorer/public/customizations/custom_dataset_filters.tsx +++ b/x-pack/plugins/discover_log_explorer/public/customizations/custom_dataset_filters.tsx @@ -12,6 +12,8 @@ import { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { useControlPanels } from '../hooks/use_control_panels'; import { LogExplorerProfileStateService } from '../state_machines/log_explorer_profile'; +const DATASET_FILTERS_CUSTOMIZATION_ID = 'datasetFiltersCustomization'; + interface CustomDatasetFiltersProps { logExplorerProfileStateService: LogExplorerProfileStateService; data: DataPublicPluginStart; @@ -27,7 +29,7 @@ const CustomDatasetFilters = ({ ); return ( - + { + before(async () => { + await esArchiver.load( + 'x-pack/test/functional/es_archives/discover_log_explorer/data_streams' + ); + }); + + after(async () => { + await esArchiver.unload( + 'x-pack/test/functional/es_archives/discover_log_explorer/data_streams' + ); + }); + + describe('when the log explorer profile loads', () => { + it("should initialize the table columns to logs' default selection", async () => { + await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); + + await PageObjects.discover.expandTimeRangeAsSuggestedInNoResultsMessage(); + + await retry.try(async () => { + expect(await PageObjects.discover.getColumnHeaders()).to.eql(defaultLogColumns); + }); + }); + + it('should restore the table columns from the URL state if exists', async () => { + await PageObjects.common.navigateToApp('discover', { + hash: '/p/log-explorer?_a=(columns:!(message,data_stream.namespace))', + }); + + await PageObjects.discover.expandTimeRangeAsSuggestedInNoResultsMessage(); + + await retry.try(async () => { + expect(await PageObjects.discover.getColumnHeaders()).to.eql([ + ...defaultLogColumns, + 'data_stream.namespace', + ]); + }); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/discover_log_explorer/customization.ts b/x-pack/test/functional/apps/discover_log_explorer/customization.ts index 2dba2ed30b695..6cd713a40f63a 100644 --- a/x-pack/test/functional/apps/discover_log_explorer/customization.ts +++ b/x-pack/test/functional/apps/discover_log_explorer/customization.ts @@ -25,14 +25,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('DatasetSelector should replace the DataViewPicker', async () => { // Assert does not render on discover app await PageObjects.common.navigateToApp('discover'); - await testSubjects.missingOrFail('dataset-selector-popover'); + await testSubjects.missingOrFail('datasetSelectorPopover'); // Assert it renders on log-explorer profile await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); - await testSubjects.existOrFail('dataset-selector-popover'); + await testSubjects.existOrFail('datasetSelectorPopover'); }); - it('the TopNav bar should hide New, Open and Save options', async () => { + it('the TopNav bar should hide then New, Open and Save options', async () => { // Assert does not render on discover app await PageObjects.common.navigateToApp('discover'); await testSubjects.existOrFail('discoverNewButton'); @@ -59,6 +59,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const results = await PageObjects.navigationalSearch.getDisplayedResults(); expect(results[0].label).to.eql('Discover / Logs Explorer'); }); + + it('should render a filter controls section as part of the unified search bar', async () => { + // Assert does not render on discover app + await PageObjects.common.navigateToApp('discover'); + await testSubjects.missingOrFail('datasetFiltersCustomization'); + + // Assert it renders on log-explorer profile + await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); + await testSubjects.existOrFail('datasetFiltersCustomization', { allowHidden: true }); + }); }); }); } diff --git a/x-pack/test/functional/apps/discover_log_explorer/dataset_selection_state.ts b/x-pack/test/functional/apps/discover_log_explorer/dataset_selection_state.ts index f795afb966493..c1c2b335358bc 100644 --- a/x-pack/test/functional/apps/discover_log_explorer/dataset_selection_state.ts +++ b/x-pack/test/functional/apps/discover_log_explorer/dataset_selection_state.ts @@ -23,7 +23,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - describe('when the "index" query param exist', () => { + describe('when the "index" query param exists', () => { it('should decode and restore the selection from a valid encoded index', async () => { const azureActivitylogsIndex = 'BQZwpgNmDGAuCWB7AdgLmAEwIay+W6yWAtmKgOQSIDmIAtFgF4CuATmAHRZzwBu8sAJ5VadAFTkANAlhRU3BPyEiQASklFS8lu2kC55AII6wAAgAyNEFN5hWIJGnIBGDgFYOAJgDM5deCgeFAAVQQAHMgdkaihVIA==='; @@ -37,7 +37,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(datasetSelectionTitle).to.be('[Azure Logs] activitylogs'); }); - it('should fallback to "All log datasets" selection and notify the user for an invalid encoded index', async () => { + it('should fallback to the "All log datasets" selection and notify the user of an invalid encoded index', async () => { const invalidEncodedIndex = 'invalid-encoded-index'; await PageObjects.common.navigateToApp('discover', { hash: `/p/log-explorer?_a=(index:${encodeURIComponent(invalidEncodedIndex)})`, diff --git a/x-pack/test/functional/apps/discover_log_explorer/dataset_selector.ts b/x-pack/test/functional/apps/discover_log_explorer/dataset_selector.ts new file mode 100644 index 0000000000000..b456da8bddb2a --- /dev/null +++ b/x-pack/test/functional/apps/discover_log_explorer/dataset_selector.ts @@ -0,0 +1,664 @@ +/* + * 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'; + +const initialPackageMap = { + apache: 'Apache HTTP Server', + aws: 'AWS', + system: 'System', +}; +const initialPackagesTexts = Object.values(initialPackageMap); + +const expectedUncategorized = ['logs-gaming-*', 'logs-manufacturing-*', 'logs-retail-*']; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const browser = getService('browser'); + const esArchiver = getService('esArchiver'); + const retry = getService('retry'); + const PageObjects = getPageObjects(['common', 'discoverLogExplorer']); + + describe('Dataset Selector', () => { + before(async () => { + await PageObjects.discoverLogExplorer.removeInstalledPackages(); + }); + + describe('without installed integrations or uncategorized data streams', () => { + before(async () => { + await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); + }); + + beforeEach(async () => { + await browser.refresh(); + await PageObjects.discoverLogExplorer.openDatasetSelector(); + }); + + describe('when open on the first navigation level', () => { + it('should always display the "All log datasets" entry as the first item', async () => { + const allLogDatasetButton = + await PageObjects.discoverLogExplorer.getAllLogDatasetsButton(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + const allLogDatasetTitle = await allLogDatasetButton.getVisibleText(); + const firstEntryTitle = await menuEntries[0].getVisibleText(); + + expect(allLogDatasetTitle).to.be('All log datasets'); + expect(allLogDatasetTitle).to.be(firstEntryTitle); + }); + + it('should always display the unmanaged datasets entry as the second item', async () => { + const unamanagedDatasetButton = + await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + const unmanagedDatasetTitle = await unamanagedDatasetButton.getVisibleText(); + const secondEntryTitle = await menuEntries[1].getVisibleText(); + + expect(unmanagedDatasetTitle).to.be('Uncategorized'); + expect(unmanagedDatasetTitle).to.be(secondEntryTitle); + }); + + it('should display an error prompt if could not retrieve the integrations', async function () { + // Skip the test in case network condition utils are not available + try { + await retry.try(async () => { + await PageObjects.discoverLogExplorer.assertNoIntegrationsPromptExists(); + }); + + await PageObjects.common.sleep(5000); + await browser.setNetworkConditions('OFFLINE'); + await PageObjects.discoverLogExplorer.typeSearchFieldWith('a'); + + await retry.try(async () => { + await PageObjects.discoverLogExplorer.assertNoIntegrationsErrorExists(); + }); + + await browser.restoreNetworkConditions(); + } catch (error) { + this.skip(); + } + }); + + it('should display an empty prompt for no integrations', async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations.length).to.be(0); + + await PageObjects.discoverLogExplorer.assertNoIntegrationsPromptExists(); + }); + }); + + describe('when navigating into Uncategorized data streams', () => { + it('should display a loading skeleton while loading', async function () { + // Skip the test in case network condition utils are not available + try { + await browser.setNetworkConditions('SLOW_3G'); // Almost stuck network conditions + const unamanagedDatasetButton = + await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + await unamanagedDatasetButton.click(); + + await PageObjects.discoverLogExplorer.assertLoadingSkeletonExists(); + + await browser.restoreNetworkConditions(); + } catch (error) { + this.skip(); + } + }); + + it('should display an error prompt if could not retrieve the data streams', async function () { + // Skip the test in case network condition utils are not available + try { + const unamanagedDatasetButton = + await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + await unamanagedDatasetButton.click(); + + await retry.try(async () => { + await PageObjects.discoverLogExplorer.assertNoDataStreamsPromptExists(); + }); + + await browser.setNetworkConditions('OFFLINE'); + await PageObjects.discoverLogExplorer.typeSearchFieldWith('a'); + + await retry.try(async () => { + await PageObjects.discoverLogExplorer.assertNoDataStreamsErrorExists(); + }); + + await browser.restoreNetworkConditions(); + } catch (error) { + this.skip(); + } + }); + + it('should display an empty prompt for no data streams', async () => { + const unamanagedDatasetButton = + await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + await unamanagedDatasetButton.click(); + + const unamanagedDatasetEntries = + await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(unamanagedDatasetEntries.length).to.be(0); + + await PageObjects.discoverLogExplorer.assertNoDataStreamsPromptExists(); + }); + }); + }); + + describe('with installed integrations and uncategorized data streams', () => { + let cleanupIntegrationsSetup: () => Promise; + + before(async () => { + await esArchiver.load( + 'x-pack/test/functional/es_archives/discover_log_explorer/data_streams' + ); + cleanupIntegrationsSetup = await PageObjects.discoverLogExplorer.setupInitialIntegrations(); + }); + + after(async () => { + await esArchiver.unload( + 'x-pack/test/functional/es_archives/discover_log_explorer/data_streams' + ); + await cleanupIntegrationsSetup(); + }); + + describe('when open on the first navigation level', () => { + before(async () => { + await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); + }); + + beforeEach(async () => { + await browser.refresh(); + await PageObjects.discoverLogExplorer.openDatasetSelector(); + }); + + it('should always display the "All log datasets" entry as the first item', async () => { + const allLogDatasetButton = + await PageObjects.discoverLogExplorer.getAllLogDatasetsButton(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + const allLogDatasetTitle = await allLogDatasetButton.getVisibleText(); + const firstEntryTitle = await menuEntries[0].getVisibleText(); + + expect(allLogDatasetTitle).to.be('All log datasets'); + expect(allLogDatasetTitle).to.be(firstEntryTitle); + }); + + it('should always display the unmanaged datasets entry as the second item', async () => { + const unamanagedDatasetButton = + await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + const unmanagedDatasetTitle = await unamanagedDatasetButton.getVisibleText(); + const secondEntryTitle = await menuEntries[1].getVisibleText(); + + expect(unmanagedDatasetTitle).to.be('Uncategorized'); + expect(unmanagedDatasetTitle).to.be(secondEntryTitle); + }); + + it('should display a list of installed integrations', async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + + expect(integrations.length).to.be(3); + expect(integrations).to.eql(initialPackagesTexts); + }); + + it('should sort the integrations list by the clicked sorting option', async () => { + // Test ascending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('asc'); + + await retry.try(async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql(initialPackagesTexts); + }); + + // Test descending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('desc'); + + await retry.try(async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql(initialPackagesTexts.slice().reverse()); + }); + + // Test back ascending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('asc'); + + await retry.try(async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql(initialPackagesTexts); + }); + }); + + it('should filter the integrations list by the typed integration name', async () => { + await PageObjects.discoverLogExplorer.typeSearchFieldWith('system'); + + await retry.try(async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql([initialPackageMap.system]); + }); + + await PageObjects.discoverLogExplorer.typeSearchFieldWith('a'); + + await retry.try(async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql([initialPackageMap.apache, initialPackageMap.aws]); + }); + }); + + it('should display an empty prompt when the search does not match any result', async () => { + await PageObjects.discoverLogExplorer.typeSearchFieldWith('no result search text'); + + await retry.try(async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations.length).to.be(0); + }); + + await PageObjects.discoverLogExplorer.assertNoIntegrationsPromptExists(); + }); + + it('should load more integrations by scrolling to the end of the list', async () => { + // Install more integrations and reload the page + const cleanupAdditionalSetup = + await PageObjects.discoverLogExplorer.setupAdditionalIntegrations(); + await browser.refresh(); + + await PageObjects.discoverLogExplorer.openDatasetSelector(); + + // Initially fetched integrations + await retry.try(async () => { + const { nodes } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(nodes.length).to.be(15); + await nodes.at(-1)?.scrollIntoViewIfNecessary(); + }); + + // Load more integrations + await retry.try(async () => { + const { nodes } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(nodes.length).to.be(20); + await nodes.at(-1)?.scrollIntoViewIfNecessary(); + }); + + // No other integrations to load after scrolling to last integration + await retry.try(async () => { + const { nodes } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(nodes.length).to.be(20); + }); + + cleanupAdditionalSetup(); + }); + }); + + describe('when clicking on integration and moving into the second navigation level', () => { + before(async () => { + await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); + }); + + beforeEach(async () => { + await browser.refresh(); + await PageObjects.discoverLogExplorer.openDatasetSelector(); + }); + + it('should display a list of available datasets', async () => { + await retry.try(async () => { + const { nodes } = await PageObjects.discoverLogExplorer.getIntegrations(); + await nodes[0].click(); + }); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); + }); + + it('should sort the datasets list by the clicked sorting option', async () => { + await retry.try(async () => { + const { nodes } = await PageObjects.discoverLogExplorer.getIntegrations(); + await nodes[0].click(); + }); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + }); + + // Test ascending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('asc'); + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); + + // Test descending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('desc'); + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be('error'); + expect(await menuEntries[1].getVisibleText()).to.be('access'); + }); + + // Test back ascending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('asc'); + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); + }); + + it('should filter the datasets list by the typed dataset name', async () => { + await retry.try(async () => { + const { nodes } = await PageObjects.discoverLogExplorer.getIntegrations(); + await nodes[0].click(); + }); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + }); + + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); + + await PageObjects.discoverLogExplorer.typeSearchFieldWith('err'); + + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(menuEntries.length).to.be(1); + expect(await menuEntries[0].getVisibleText()).to.be('error'); + }); + }); + + it('should update the current selection with the clicked dataset', async () => { + await retry.try(async () => { + const { nodes } = await PageObjects.discoverLogExplorer.getIntegrations(); + await nodes[0].click(); + }); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + }); + + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be('access'); + menuEntries[0].click(); + }); + + await retry.try(async () => { + const selectorButton = await PageObjects.discoverLogExplorer.getDatasetSelectorButton(); + + expect(await selectorButton.getVisibleText()).to.be('[Apache HTTP Server] access'); + }); + }); + }); + + describe('when navigating into Uncategorized data streams', () => { + before(async () => { + await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); + }); + + beforeEach(async () => { + await browser.refresh(); + await PageObjects.discoverLogExplorer.openDatasetSelector(); + }); + + it('should display a list of available datasets', async () => { + const button = await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + await button.click(); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await panelTitleNode.getVisibleText()).to.be('Uncategorized'); + expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); + expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); + expect(await menuEntries[2].getVisibleText()).to.be(expectedUncategorized[2]); + }); + }); + + it('should sort the datasets list by the clicked sorting option', async () => { + const button = await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + await button.click(); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + + expect(await panelTitleNode.getVisibleText()).to.be('Uncategorized'); + }); + + // Test ascending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('asc'); + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); + expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); + expect(await menuEntries[2].getVisibleText()).to.be(expectedUncategorized[2]); + }); + + // Test descending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('desc'); + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[2]); + expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); + expect(await menuEntries[2].getVisibleText()).to.be(expectedUncategorized[0]); + }); + + // Test back ascending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('asc'); + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); + expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); + expect(await menuEntries[2].getVisibleText()).to.be(expectedUncategorized[2]); + }); + }); + + it('should filter the datasets list by the typed dataset name', async () => { + const button = await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + await button.click(); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + + expect(await panelTitleNode.getVisibleText()).to.be('Uncategorized'); + }); + + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); + expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); + expect(await menuEntries[2].getVisibleText()).to.be(expectedUncategorized[2]); + }); + + await PageObjects.discoverLogExplorer.typeSearchFieldWith('retail'); + + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(menuEntries.length).to.be(1); + expect(await menuEntries[0].getVisibleText()).to.be('logs-retail-*'); + }); + }); + + it('should update the current selection with the clicked dataset', async () => { + const button = await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + await button.click(); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + + expect(await panelTitleNode.getVisibleText()).to.be('Uncategorized'); + }); + + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be('logs-gaming-*'); + menuEntries[0].click(); + }); + + await retry.try(async () => { + const selectorButton = await PageObjects.discoverLogExplorer.getDatasetSelectorButton(); + + expect(await selectorButton.getVisibleText()).to.be('logs-gaming-*'); + }); + }); + }); + + describe('when open/close the selector', () => { + before(async () => { + await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); + }); + + beforeEach(async () => { + await browser.refresh(); + await PageObjects.discoverLogExplorer.openDatasetSelector(); + }); + + it('should restore the latest navigation panel', async () => { + await retry.try(async () => { + const { nodes } = await PageObjects.discoverLogExplorer.getIntegrations(); + await nodes[0].click(); + }); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); + + await PageObjects.discoverLogExplorer.closeDatasetSelector(); + await PageObjects.discoverLogExplorer.openDatasetSelector(); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); + }); + + it('should restore the latest search results', async () => { + await PageObjects.discoverLogExplorer.typeSearchFieldWith('system'); + + await retry.try(async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql([initialPackageMap.system]); + }); + + await PageObjects.discoverLogExplorer.closeDatasetSelector(); + await PageObjects.discoverLogExplorer.openDatasetSelector(); + + await retry.try(async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql([initialPackageMap.system]); + }); + }); + }); + + describe('when switching between integration panels', () => { + before(async () => { + await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); + }); + + it('should remember the latest search and restore its results for each integration', async () => { + await PageObjects.discoverLogExplorer.openDatasetSelector(); + await PageObjects.discoverLogExplorer.clearSearchField(); + + await PageObjects.discoverLogExplorer.typeSearchFieldWith('apache'); + + await retry.try(async () => { + const { nodes, integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql([initialPackageMap.apache]); + nodes[0].click(); + }); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); + + await PageObjects.discoverLogExplorer.typeSearchFieldWith('err'); + + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(menuEntries.length).to.be(1); + expect(await menuEntries[0].getVisibleText()).to.be('error'); + }); + + // Navigate back to integrations + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + panelTitleNode.click(); + + await retry.try(async () => { + const { nodes, integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql([initialPackageMap.apache]); + + const searchValue = await PageObjects.discoverLogExplorer.getSearchFieldValue(); + expect(searchValue).to.eql('apache'); + + nodes[0].click(); + }); + + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + const searchValue = await PageObjects.discoverLogExplorer.getSearchFieldValue(); + expect(searchValue).to.eql('err'); + + expect(menuEntries.length).to.be(1); + expect(await menuEntries[0].getVisibleText()).to.be('error'); + }); + }); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/discover_log_explorer/index.ts b/x-pack/test/functional/apps/discover_log_explorer/index.ts index 719bd8a7fcb28..dd8b99db79ad0 100644 --- a/x-pack/test/functional/apps/discover_log_explorer/index.ts +++ b/x-pack/test/functional/apps/discover_log_explorer/index.ts @@ -9,7 +9,9 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('Discover Log-Explorer profile', function () { + loadTestFile(require.resolve('./columns_selection')); loadTestFile(require.resolve('./customization')); loadTestFile(require.resolve('./dataset_selection_state')); + loadTestFile(require.resolve('./dataset_selector')); }); } diff --git a/x-pack/test/functional/es_archives/discover_log_explorer/data_streams/data.json.gz b/x-pack/test/functional/es_archives/discover_log_explorer/data_streams/data.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..4e72a78a4f8b9b5eb2f44aabfada690d091e5826 GIT binary patch literal 29526 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 L9tOW<-sS%T0U|_7 literal 0 HcmV?d00001 diff --git a/x-pack/test/functional/es_archives/discover_log_explorer/data_streams/mappings.json b/x-pack/test/functional/es_archives/discover_log_explorer/data_streams/mappings.json new file mode 100644 index 0000000000000..ed9e5982f576f --- /dev/null +++ b/x-pack/test/functional/es_archives/discover_log_explorer/data_streams/mappings.json @@ -0,0 +1,419 @@ +{ + "type": "data_stream", + "value": { + "data_stream": "logs-gaming-activity", + "template": { + "_meta": { + "description": "Template for my time series data", + "my-custom-meta-field": "More arbitrary metadata" + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "logs-gaming-activity" + ], + "name": "logs-gaming-activity", + "priority": 500, + "template": { + "mappings": { + "properties": { + "@timestamp": { + "format": "date_optional_time||epoch_millis", + "type": "date" + }, + "data_stream": { + "properties": { + "namespace": { + "type": "constant_keyword" + } + } + }, + "message": { + "type": "wildcard" + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "logs-gaming-events", + "template": { + "_meta": { + "description": "Template for my time series data", + "my-custom-meta-field": "More arbitrary metadata" + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "logs-gaming-events" + ], + "name": "logs-gaming-events", + "priority": 500, + "template": { + "mappings": { + "properties": { + "@timestamp": { + "format": "date_optional_time||epoch_millis", + "type": "date" + }, + "data_stream": { + "properties": { + "namespace": { + "type": "constant_keyword" + } + } + }, + "message": { + "type": "wildcard" + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "logs-gaming-scores", + "template": { + "_meta": { + "description": "Template for my time series data", + "my-custom-meta-field": "More arbitrary metadata" + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "logs-gaming-scores" + ], + "name": "logs-gaming-scores", + "priority": 500, + "template": { + "mappings": { + "properties": { + "@timestamp": { + "format": "date_optional_time||epoch_millis", + "type": "date" + }, + "data_stream": { + "properties": { + "namespace": { + "type": "constant_keyword" + } + } + }, + "message": { + "type": "wildcard" + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "logs-manufacturing-downtime", + "template": { + "_meta": { + "description": "Template for my time series data", + "my-custom-meta-field": "More arbitrary metadata" + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "logs-manufacturing-downtime" + ], + "name": "logs-manufacturing-downtime", + "priority": 500, + "template": { + "mappings": { + "properties": { + "@timestamp": { + "format": "date_optional_time||epoch_millis", + "type": "date" + }, + "data_stream": { + "properties": { + "namespace": { + "type": "constant_keyword" + } + } + }, + "message": { + "type": "wildcard" + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "logs-manufacturing-output", + "template": { + "_meta": { + "description": "Template for my time series data", + "my-custom-meta-field": "More arbitrary metadata" + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "logs-manufacturing-output" + ], + "name": "logs-manufacturing-output", + "priority": 500, + "template": { + "mappings": { + "properties": { + "@timestamp": { + "format": "date_optional_time||epoch_millis", + "type": "date" + }, + "data_stream": { + "properties": { + "namespace": { + "type": "constant_keyword" + } + } + }, + "message": { + "type": "wildcard" + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "logs-manufacturing-quality", + "template": { + "_meta": { + "description": "Template for my time series data", + "my-custom-meta-field": "More arbitrary metadata" + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "logs-manufacturing-quality" + ], + "name": "logs-manufacturing-quality", + "priority": 500, + "template": { + "mappings": { + "properties": { + "@timestamp": { + "format": "date_optional_time||epoch_millis", + "type": "date" + }, + "data_stream": { + "properties": { + "namespace": { + "type": "constant_keyword" + } + } + }, + "message": { + "type": "wildcard" + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "logs-retail-customers", + "template": { + "_meta": { + "description": "Template for my time series data", + "my-custom-meta-field": "More arbitrary metadata" + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "logs-retail-customers" + ], + "name": "logs-retail-customers", + "priority": 500, + "template": { + "mappings": { + "properties": { + "@timestamp": { + "format": "date_optional_time||epoch_millis", + "type": "date" + }, + "data_stream": { + "properties": { + "namespace": { + "type": "constant_keyword" + } + } + }, + "message": { + "type": "wildcard" + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "logs-retail-inventory", + "template": { + "_meta": { + "description": "Template for my time series data", + "my-custom-meta-field": "More arbitrary metadata" + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "logs-retail-inventory" + ], + "name": "logs-retail-inventory", + "priority": 500, + "template": { + "mappings": { + "properties": { + "@timestamp": { + "format": "date_optional_time||epoch_millis", + "type": "date" + }, + "data_stream": { + "properties": { + "namespace": { + "type": "constant_keyword" + } + } + }, + "message": { + "type": "wildcard" + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "logs-retail-promotions", + "template": { + "_meta": { + "description": "Template for my time series data", + "my-custom-meta-field": "More arbitrary metadata" + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "logs-retail-promotions" + ], + "name": "logs-retail-promotions", + "priority": 500, + "template": { + "mappings": { + "properties": { + "@timestamp": { + "format": "date_optional_time||epoch_millis", + "type": "date" + }, + "data_stream": { + "properties": { + "namespace": { + "type": "constant_keyword" + } + } + }, + "message": { + "type": "wildcard" + } + } + } + } + } + } +} + +{ + "type": "data_stream", + "value": { + "data_stream": "logs-retail-sales", + "template": { + "_meta": { + "description": "Template for my time series data", + "my-custom-meta-field": "More arbitrary metadata" + }, + "data_stream": { + "allow_custom_routing": false, + "hidden": false + }, + "index_patterns": [ + "logs-retail-sales" + ], + "name": "logs-retail-sales", + "priority": 500, + "template": { + "mappings": { + "properties": { + "@timestamp": { + "format": "date_optional_time||epoch_millis", + "type": "date" + }, + "data_stream": { + "properties": { + "namespace": { + "type": "constant_keyword" + } + } + }, + "message": { + "type": "wildcard" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/page_objects/discover_log_explorer.ts b/x-pack/test/functional/page_objects/discover_log_explorer.ts index 15c2dc8fbcc4e..282a703863dc2 100644 --- a/x-pack/test/functional/page_objects/discover_log_explorer.ts +++ b/x-pack/test/functional/page_objects/discover_log_explorer.ts @@ -7,13 +7,186 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../ftr_provider_context'; +export interface IntegrationPackage { + name: string; + version: string; +} + +const packages: IntegrationPackage[] = [ + { + name: 'apache', + version: '1.14.0', + }, + { + name: 'aws', + version: '1.51.0', + }, + { + name: 'system', + version: '1.38.1', + }, + { + name: '1password', + version: '1.18.0', + }, + { + name: 'activemq', + version: '0.13.0', + }, + { + name: 'akamai', + version: '2.14.0', + }, + { + name: 'apache_tomcat', + version: '0.12.1', + }, + { + name: 'apm', + version: '8.4.2', + }, + { + name: 'atlassian_bitbucket', + version: '1.14.0', + }, + { + name: 'atlassian_confluence', + version: '1.15.0', + }, + { + name: 'atlassian_jira', + version: '1.15.0', + }, + { + name: 'auditd', + version: '3.12.0', + }, + { + name: 'auditd_manager', + version: '1.12.0', + }, + { + name: 'auth0', + version: '1.10.0', + }, + { + name: 'aws_logs', + version: '0.5.0', + }, + { + name: 'azure', + version: '1.5.28', + }, + { + name: 'azure_app_service', + version: '0.0.1', + }, + { + name: 'azure_blob_storage', + version: '0.5.0', + }, + { + name: 'azure_frontdoor', + version: '1.1.0', + }, + { + name: 'azure_functions', + version: '0.0.1', + }, +]; + +const initialPackages = packages.slice(0, 3); +const additionalPackages = packages.slice(3); + export function DiscoverLogExplorerPageObject({ getService }: FtrProviderContext) { + const log = getService('log'); + const supertest = getService('supertest'); const testSubjects = getService('testSubjects'); const toasts = getService('toasts'); return { - async getDatasetSelectorButton() { - return testSubjects.find('dataset-selector-popover-button'); + uninstallPackage: ({ name, version }: IntegrationPackage) => { + return supertest.delete(`/api/fleet/epm/packages/${name}/${version}`).set('kbn-xsrf', 'xxxx'); + }, + + installPackage: ({ name, version }: IntegrationPackage) => { + return supertest + .post(`/api/fleet/epm/packages/${name}/${version}`) + .set('kbn-xsrf', 'xxxx') + .send({ force: true }); + }, + + getInstalledPackages: () => { + return supertest + .get(`/api/fleet/epm/packages/installed?dataStreamType=logs&perPage=1000`) + .set('kbn-xsrf', 'xxxx'); + }, + + async removeInstalledPackages(): Promise { + const response = await this.getInstalledPackages(); + + // Uninstall installed integration + await Promise.all( + response.body.items.map((pkg: IntegrationPackage) => this.uninstallPackage(pkg)) + ); + + return response.body.items; + }, + + async setupInitialIntegrations() { + log.info(`===== Setup initial integration packages. =====`); + log.info(`===== Uninstall initial integration packages. =====`); + const uninstalled = await this.removeInstalledPackages(); + log.info(`===== Install ${initialPackages.length} mock integration packages. =====`); + await Promise.all(initialPackages.map((pkg: IntegrationPackage) => this.installPackage(pkg))); + + return async () => { + log.info(`===== Uninstall ${initialPackages.length} mock integration packages. =====`); + await Promise.all( + initialPackages.map((pkg: IntegrationPackage) => this.uninstallPackage(pkg)) + ); + log.info(`===== Restore pre-existing integration packages. =====`); + await Promise.all(uninstalled.map((pkg: IntegrationPackage) => this.installPackage(pkg))); + }; + }, + + async setupAdditionalIntegrations() { + log.info(`===== Setup additional integration packages. =====`); + log.info(`===== Install ${additionalPackages.length} mock integration packages. =====`); + await Promise.all( + additionalPackages.map((pkg: IntegrationPackage) => this.installPackage(pkg)) + ); + + return async () => { + log.info(`===== Uninstall ${additionalPackages.length} mock integration packages. =====`); + await Promise.all( + additionalPackages.map((pkg: IntegrationPackage) => this.uninstallPackage(pkg)) + ); + }; + }, + + getDatasetSelector() { + return testSubjects.find('datasetSelectorPopover'); + }, + + getDatasetSelectorButton() { + return testSubjects.find('datasetSelectorPopoverButton'); + }, + + getDatasetSelectorContent() { + return testSubjects.find('datasetSelectorContent'); + }, + + getDatasetSelectorSearchControls() { + return testSubjects.find('datasetSelectorSearchControls'); + }, + + getDatasetSelectorContextMenu() { + return testSubjects.find('datasetSelectorContextMenu'); + }, + + getDatasetSelectorContextMenuPanelTitle() { + return testSubjects.find('contextMenuPanelTitleButton'); }, async getDatasetSelectorButtonText() { @@ -21,11 +194,115 @@ export function DiscoverLogExplorerPageObject({ getService }: FtrProviderContext return button.getVisibleText(); }, + async getCurrentPanelEntries() { + const contextMenu = await this.getDatasetSelectorContextMenu(); + return contextMenu.findAllByClassName('euiContextMenuItem', 2000); + }, + + getAllLogDatasetsButton() { + return testSubjects.find('allLogDatasets'); + }, + + getUnmanagedDatasetsButton() { + return testSubjects.find('unmanagedDatasets'); + }, + + async getIntegrations() { + const content = await this.getDatasetSelectorContent(); + + const nodes = await content.findAllByCssSelector('[data-test-subj*="integration-"]', 2000); + const integrations = await Promise.all(nodes.map((node) => node.getVisibleText())); + + return { + nodes, + integrations, + }; + }, + + async openDatasetSelector() { + const button = await this.getDatasetSelectorButton(); + return button.click(); + }, + + async closeDatasetSelector() { + const button = await this.getDatasetSelectorButton(); + const isOpen = await testSubjects.exists('datasetSelectorContent'); + + if (isOpen) return button.click(); + }, + + async clickSortButtonBy(direction: 'asc' | 'desc') { + const titleMap = { + asc: 'Ascending', + desc: 'Descending', + }; + const searchControlsContainer = await this.getDatasetSelectorSearchControls(); + const sortingButton = await searchControlsContainer.findByCssSelector( + `[title=${titleMap[direction]}]` + ); + + return sortingButton.click(); + }, + + async getSearchFieldValue() { + const searchControlsContainer = await this.getDatasetSelectorSearchControls(); + const searchField = await searchControlsContainer.findByCssSelector('input[type=search]'); + + return searchField.getAttribute('value'); + }, + + async typeSearchFieldWith(name: string) { + const searchControlsContainer = await this.getDatasetSelectorSearchControls(); + const searchField = await searchControlsContainer.findByCssSelector('input[type=search]'); + + await searchField.clearValueWithKeyboard(); + return searchField.type(name); + }, + + async clearSearchField() { + const searchControlsContainer = await this.getDatasetSelectorSearchControls(); + const searchField = await searchControlsContainer.findByCssSelector('input[type=search]'); + + return searchField.clearValueWithKeyboard(); + }, + async assertRestoreFailureToastExist() { const successToast = await toasts.getToastElement(1); expect(await successToast.getVisibleText()).to.contain( "We couldn't restore your datasets selection" ); }, + + assertLoadingSkeletonExists() { + return testSubjects.existOrFail('datasetSelectorSkeleton'); + }, + + async assertNoIntegrationsPromptExists() { + const integrationStatus = await testSubjects.find('integrationStatusItem'); + const promptTitle = await integrationStatus.findByTagName('h2'); + + expect(await promptTitle.getVisibleText()).to.be('No integrations found'); + }, + + async assertNoIntegrationsErrorExists() { + const integrationStatus = await testSubjects.find('integrationsErrorPrompt'); + const promptTitle = await integrationStatus.findByTagName('h2'); + + expect(await promptTitle.getVisibleText()).to.be('No integrations found'); + }, + + async assertNoDataStreamsPromptExists() { + const integrationStatus = await testSubjects.find('emptyDatasetPrompt'); + const promptTitle = await integrationStatus.findByTagName('h2'); + + expect(await promptTitle.getVisibleText()).to.be('No data streams found'); + }, + + async assertNoDataStreamsErrorExists() { + const integrationStatus = await testSubjects.find('datasetErrorPrompt'); + const promptTitle = await integrationStatus.findByTagName('h2'); + + expect(await promptTitle.getVisibleText()).to.be('No data streams found'); + }, }; } diff --git a/x-pack/test_serverless/functional/test_suites/observability/discover_log_explorer/columns_selection.ts b/x-pack/test_serverless/functional/test_suites/observability/discover_log_explorer/columns_selection.ts new file mode 100644 index 0000000000000..dfba8f72a699d --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/observability/discover_log_explorer/columns_selection.ts @@ -0,0 +1,57 @@ +/* + * 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'; + +const defaultLogColumns = ['@timestamp', 'message']; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const retry = getService('retry'); + const PageObjects = getPageObjects(['common', 'discover']); + + describe('Columns selection initialization and update', () => { + before(async () => { + await esArchiver.load( + 'x-pack/test/functional/es_archives/discover_log_explorer/data_streams' + ); + }); + + after(async () => { + await esArchiver.unload( + 'x-pack/test/functional/es_archives/discover_log_explorer/data_streams' + ); + }); + + describe('when the log explorer profile loads', () => { + it("should initialize the table columns to logs' default selection", async () => { + await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); + + await PageObjects.discover.expandTimeRangeAsSuggestedInNoResultsMessage(); + + await retry.try(async () => { + expect(await PageObjects.discover.getColumnHeaders()).to.eql(defaultLogColumns); + }); + }); + + it('should restore the table columns from the URL state if exists', async () => { + await PageObjects.common.navigateToApp('discover', { + hash: '/p/log-explorer?_a=(columns:!(message,data_stream.namespace))', + }); + + await PageObjects.discover.expandTimeRangeAsSuggestedInNoResultsMessage(); + + await retry.try(async () => { + expect(await PageObjects.discover.getColumnHeaders()).to.eql([ + ...defaultLogColumns, + 'data_stream.namespace', + ]); + }); + }); + }); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/observability/discover_log_explorer/customization.ts b/x-pack/test_serverless/functional/test_suites/observability/discover_log_explorer/customization.ts index 579f4e4b8f5c5..a647293a73145 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/discover_log_explorer/customization.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/discover_log_explorer/customization.ts @@ -24,11 +24,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('DatasetSelector should replace the DataViewPicker', async () => { // Assert does not render on discover app await PageObjects.common.navigateToApp('discover'); - await testSubjects.missingOrFail('dataset-selector-popover'); + await testSubjects.missingOrFail('datasetSelectorPopover'); // Assert it renders on log-explorer profile await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); - await testSubjects.existOrFail('dataset-selector-popover'); + await testSubjects.existOrFail('datasetSelectorPopover'); }); it('the TopNav bar should hide New, Open and Save options', async () => { @@ -50,6 +50,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.existOrFail('openInspectorButton'); await testSubjects.missingOrFail('discoverSaveButton'); }); + + it('should render a filter controls section as part of the unified search bar', async () => { + // Assert does not render on discover app + await PageObjects.common.navigateToApp('discover'); + await testSubjects.missingOrFail('datasetFiltersCustomization'); + + // Assert it renders on log-explorer profile + await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); + await testSubjects.existOrFail('datasetFiltersCustomization', { allowHidden: true }); + }); }); }); } diff --git a/x-pack/test_serverless/functional/test_suites/observability/discover_log_explorer/dataset_selector.ts b/x-pack/test_serverless/functional/test_suites/observability/discover_log_explorer/dataset_selector.ts new file mode 100644 index 0000000000000..cbb3ea9d95de5 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/observability/discover_log_explorer/dataset_selector.ts @@ -0,0 +1,664 @@ +/* + * 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'; + +const initialPackageMap = { + apache: 'Apache HTTP Server', + aws: 'AWS', + system: 'System', +}; +const initialPackagesTexts = Object.values(initialPackageMap); + +const expectedUncategorized = ['logs-gaming-*', 'logs-manufacturing-*', 'logs-retail-*']; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const browser = getService('browser'); + const esArchiver = getService('esArchiver'); + const retry = getService('retry'); + const PageObjects = getPageObjects(['common', 'discoverLogExplorer']); + + describe('Dataset Selector', () => { + before(async () => { + await PageObjects.discoverLogExplorer.removeInstalledPackages(); + }); + + describe('without installed integrations or uncategorized data streams', () => { + before(async () => { + await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); + }); + + beforeEach(async () => { + await browser.refresh(); + await PageObjects.discoverLogExplorer.openDatasetSelector(); + }); + + describe('when open on the first navigation level', () => { + it('should always display the "All log datasets" entry as the first item', async () => { + const allLogDatasetButton = + await PageObjects.discoverLogExplorer.getAllLogDatasetsButton(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + const allLogDatasetTitle = await allLogDatasetButton.getVisibleText(); + const firstEntryTitle = await menuEntries[0].getVisibleText(); + + expect(allLogDatasetTitle).to.be('All log datasets'); + expect(allLogDatasetTitle).to.be(firstEntryTitle); + }); + + it('should always display the unmanaged datasets entry as the second item', async () => { + const unamanagedDatasetButton = + await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + const unmanagedDatasetTitle = await unamanagedDatasetButton.getVisibleText(); + const secondEntryTitle = await menuEntries[1].getVisibleText(); + + expect(unmanagedDatasetTitle).to.be('Uncategorized'); + expect(unmanagedDatasetTitle).to.be(secondEntryTitle); + }); + + it('should display an error prompt if could not retrieve the integrations', async function () { + // Skip the test in case network condition utils are not available + try { + await retry.try(async () => { + await PageObjects.discoverLogExplorer.assertNoIntegrationsPromptExists(); + }); + + await PageObjects.common.sleep(5000); + await browser.setNetworkConditions('OFFLINE'); + await PageObjects.discoverLogExplorer.typeSearchFieldWith('a'); + + await retry.try(async () => { + await PageObjects.discoverLogExplorer.assertNoIntegrationsErrorExists(); + }); + + await browser.restoreNetworkConditions(); + } catch (error) { + this.skip(); + } + }); + + it('should display an empty prompt for no integrations', async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations.length).to.be(0); + + await PageObjects.discoverLogExplorer.assertNoIntegrationsPromptExists(); + }); + }); + + describe('when navigating into Uncategorized data streams', () => { + it('should display a loading skeleton while loading', async function () { + // Skip the test in case network condition utils are not available + try { + await browser.setNetworkConditions('SLOW_3G'); // Almost stuck network conditions + const unamanagedDatasetButton = + await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + await unamanagedDatasetButton.click(); + + await PageObjects.discoverLogExplorer.assertLoadingSkeletonExists(); + + await browser.restoreNetworkConditions(); + } catch (error) { + this.skip(); + } + }); + + it('should display an error prompt if could not retrieve the data streams', async function () { + // Skip the test in case network condition utils are not available + try { + const unamanagedDatasetButton = + await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + await unamanagedDatasetButton.click(); + + await retry.try(async () => { + await PageObjects.discoverLogExplorer.assertNoDataStreamsPromptExists(); + }); + + await browser.setNetworkConditions('OFFLINE'); + await PageObjects.discoverLogExplorer.typeSearchFieldWith('a'); + + await retry.try(async () => { + await PageObjects.discoverLogExplorer.assertNoDataStreamsErrorExists(); + }); + + await browser.restoreNetworkConditions(); + } catch (error) { + this.skip(); + } + }); + + it('should display an empty prompt for no data streams', async () => { + const unamanagedDatasetButton = + await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + await unamanagedDatasetButton.click(); + + const unamanagedDatasetEntries = + await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(unamanagedDatasetEntries.length).to.be(0); + + await PageObjects.discoverLogExplorer.assertNoDataStreamsPromptExists(); + }); + }); + }); + + describe('with installed integrations and uncategorized data streams', () => { + let cleanupIntegrationsSetup: () => Promise; + + before(async () => { + await esArchiver.load( + 'x-pack/test/functional/es_archives/discover_log_explorer/data_streams' + ); + cleanupIntegrationsSetup = await PageObjects.discoverLogExplorer.setupInitialIntegrations(); + }); + + after(async () => { + await esArchiver.unload( + 'x-pack/test/functional/es_archives/discover_log_explorer/data_streams' + ); + await cleanupIntegrationsSetup(); + }); + + describe('when open on the first navigation level', () => { + before(async () => { + await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); + }); + + beforeEach(async () => { + await browser.refresh(); + await PageObjects.discoverLogExplorer.openDatasetSelector(); + }); + + it('should always display the "All log datasets" entry as the first item', async () => { + const allLogDatasetButton = + await PageObjects.discoverLogExplorer.getAllLogDatasetsButton(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + const allLogDatasetTitle = await allLogDatasetButton.getVisibleText(); + const firstEntryTitle = await menuEntries[0].getVisibleText(); + + expect(allLogDatasetTitle).to.be('All log datasets'); + expect(allLogDatasetTitle).to.be(firstEntryTitle); + }); + + it('should always display the unmanaged datasets entry as the second item', async () => { + const unamanagedDatasetButton = + await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + const unmanagedDatasetTitle = await unamanagedDatasetButton.getVisibleText(); + const secondEntryTitle = await menuEntries[1].getVisibleText(); + + expect(unmanagedDatasetTitle).to.be('Uncategorized'); + expect(unmanagedDatasetTitle).to.be(secondEntryTitle); + }); + + it('should display a list of installed integrations', async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + + expect(integrations.length).to.be(3); + expect(integrations).to.eql(initialPackagesTexts); + }); + + it('should sort the integrations list by the clicked sorting option', async () => { + // Test ascending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('asc'); + + await retry.try(async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql(initialPackagesTexts); + }); + + // Test descending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('desc'); + + await retry.try(async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql(initialPackagesTexts.slice().reverse()); + }); + + // Test back ascending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('asc'); + + await retry.try(async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql(initialPackagesTexts); + }); + }); + + it('should filter the integrations list by the typed integration name', async () => { + await PageObjects.discoverLogExplorer.typeSearchFieldWith('system'); + + await retry.try(async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql([initialPackageMap.system]); + }); + + await PageObjects.discoverLogExplorer.typeSearchFieldWith('a'); + + await retry.try(async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql([initialPackageMap.apache, initialPackageMap.aws]); + }); + }); + + it('should display an empty prompt when the search does not match any result', async () => { + await PageObjects.discoverLogExplorer.typeSearchFieldWith('no result search text'); + + await retry.try(async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations.length).to.be(0); + }); + + await PageObjects.discoverLogExplorer.assertNoIntegrationsPromptExists(); + }); + + it('should load more integrations by scrolling to the end of the list', async () => { + // Install more integrations and reload the page + const cleanupAdditionalSetup = + await PageObjects.discoverLogExplorer.setupAdditionalIntegrations(); + await browser.refresh(); + + await PageObjects.discoverLogExplorer.openDatasetSelector(); + + // Initially fetched integrations + await retry.try(async () => { + const { nodes } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(nodes.length).to.be(15); + await nodes.at(-1)?.scrollIntoViewIfNecessary(); + }); + + // Load more integrations + await retry.try(async () => { + const { nodes } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(nodes.length).to.be(20); + await nodes.at(-1)?.scrollIntoViewIfNecessary(); + }); + + // No other integrations to load after scrolling to last integration + await retry.try(async () => { + const { nodes } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(nodes.length).to.be(20); + }); + + cleanupAdditionalSetup(); + }); + }); + + describe('when clicking on integration and moving into the second navigation level', () => { + before(async () => { + await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); + }); + + beforeEach(async () => { + await browser.refresh(); + await PageObjects.discoverLogExplorer.openDatasetSelector(); + }); + + it('should display a list of available datasets', async () => { + await retry.try(async () => { + const { nodes } = await PageObjects.discoverLogExplorer.getIntegrations(); + await nodes[0].click(); + }); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); + }); + + it('should sort the datasets list by the clicked sorting option', async () => { + await retry.try(async () => { + const { nodes } = await PageObjects.discoverLogExplorer.getIntegrations(); + await nodes[0].click(); + }); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + }); + + // Test ascending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('asc'); + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); + + // Test descending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('desc'); + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be('error'); + expect(await menuEntries[1].getVisibleText()).to.be('access'); + }); + + // Test back ascending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('asc'); + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); + }); + + it('should filter the datasets list by the typed dataset name', async () => { + await retry.try(async () => { + const { nodes } = await PageObjects.discoverLogExplorer.getIntegrations(); + await nodes[0].click(); + }); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + }); + + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); + + await PageObjects.discoverLogExplorer.typeSearchFieldWith('err'); + + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(menuEntries.length).to.be(1); + expect(await menuEntries[0].getVisibleText()).to.be('error'); + }); + }); + + it('should update the current selection with the clicked dataset', async () => { + await retry.try(async () => { + const { nodes } = await PageObjects.discoverLogExplorer.getIntegrations(); + await nodes[0].click(); + }); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + }); + + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be('access'); + menuEntries[0].click(); + }); + + await retry.try(async () => { + const selectorButton = await PageObjects.discoverLogExplorer.getDatasetSelectorButton(); + + expect(await selectorButton.getVisibleText()).to.be('[Apache HTTP Server] access'); + }); + }); + }); + + describe('when navigating into Uncategorized data streams', () => { + before(async () => { + await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); + }); + + beforeEach(async () => { + await browser.refresh(); + await PageObjects.discoverLogExplorer.openDatasetSelector(); + }); + + it('should display a list of available datasets', async () => { + const button = await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + await button.click(); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await panelTitleNode.getVisibleText()).to.be('Uncategorized'); + expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); + expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); + expect(await menuEntries[2].getVisibleText()).to.be(expectedUncategorized[2]); + }); + }); + + it('should sort the datasets list by the clicked sorting option', async () => { + const button = await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + await button.click(); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + + expect(await panelTitleNode.getVisibleText()).to.be('Uncategorized'); + }); + + // Test ascending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('asc'); + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); + expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); + expect(await menuEntries[2].getVisibleText()).to.be(expectedUncategorized[2]); + }); + + // Test descending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('desc'); + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[2]); + expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); + expect(await menuEntries[2].getVisibleText()).to.be(expectedUncategorized[0]); + }); + + // Test back ascending order + await PageObjects.discoverLogExplorer.clickSortButtonBy('asc'); + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); + expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); + expect(await menuEntries[2].getVisibleText()).to.be(expectedUncategorized[2]); + }); + }); + + it('should filter the datasets list by the typed dataset name', async () => { + const button = await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + await button.click(); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + + expect(await panelTitleNode.getVisibleText()).to.be('Uncategorized'); + }); + + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); + expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); + expect(await menuEntries[2].getVisibleText()).to.be(expectedUncategorized[2]); + }); + + await PageObjects.discoverLogExplorer.typeSearchFieldWith('retail'); + + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(menuEntries.length).to.be(1); + expect(await menuEntries[0].getVisibleText()).to.be('logs-retail-*'); + }); + }); + + it('should update the current selection with the clicked dataset', async () => { + const button = await PageObjects.discoverLogExplorer.getUnmanagedDatasetsButton(); + await button.click(); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + + expect(await panelTitleNode.getVisibleText()).to.be('Uncategorized'); + }); + + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await menuEntries[0].getVisibleText()).to.be('logs-gaming-*'); + menuEntries[0].click(); + }); + + await retry.try(async () => { + const selectorButton = await PageObjects.discoverLogExplorer.getDatasetSelectorButton(); + + expect(await selectorButton.getVisibleText()).to.be('logs-gaming-*'); + }); + }); + }); + + describe('when open/close the selector', () => { + before(async () => { + await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); + }); + + beforeEach(async () => { + await browser.refresh(); + await PageObjects.discoverLogExplorer.openDatasetSelector(); + }); + + it('should restore the latest navigation panel', async () => { + await retry.try(async () => { + const { nodes } = await PageObjects.discoverLogExplorer.getIntegrations(); + await nodes[0].click(); + }); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); + + await PageObjects.discoverLogExplorer.closeDatasetSelector(); + await PageObjects.discoverLogExplorer.openDatasetSelector(); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); + }); + + it('should restore the latest search results', async () => { + await PageObjects.discoverLogExplorer.typeSearchFieldWith('system'); + + await retry.try(async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql([initialPackageMap.system]); + }); + + await PageObjects.discoverLogExplorer.closeDatasetSelector(); + await PageObjects.discoverLogExplorer.openDatasetSelector(); + + await retry.try(async () => { + const { integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql([initialPackageMap.system]); + }); + }); + }); + + describe('when switching between integration panels', () => { + before(async () => { + await PageObjects.common.navigateToApp('discover', { hash: '/p/log-explorer' }); + }); + + it('should remember the latest search and restore its results for each integration', async () => { + await PageObjects.discoverLogExplorer.openDatasetSelector(); + await PageObjects.discoverLogExplorer.clearSearchField(); + + await PageObjects.discoverLogExplorer.typeSearchFieldWith('apache'); + + await retry.try(async () => { + const { nodes, integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql([initialPackageMap.apache]); + nodes[0].click(); + }); + + await retry.try(async () => { + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); + expect(await menuEntries[0].getVisibleText()).to.be('access'); + expect(await menuEntries[1].getVisibleText()).to.be('error'); + }); + + await PageObjects.discoverLogExplorer.typeSearchFieldWith('err'); + + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + expect(menuEntries.length).to.be(1); + expect(await menuEntries[0].getVisibleText()).to.be('error'); + }); + + // Navigate back to integrations + const panelTitleNode = + await PageObjects.discoverLogExplorer.getDatasetSelectorContextMenuPanelTitle(); + panelTitleNode.click(); + + await retry.try(async () => { + const { nodes, integrations } = await PageObjects.discoverLogExplorer.getIntegrations(); + expect(integrations).to.eql([initialPackageMap.apache]); + + const searchValue = await PageObjects.discoverLogExplorer.getSearchFieldValue(); + expect(searchValue).to.eql('apache'); + + nodes[0].click(); + }); + + await retry.try(async () => { + const menuEntries = await PageObjects.discoverLogExplorer.getCurrentPanelEntries(); + + const searchValue = await PageObjects.discoverLogExplorer.getSearchFieldValue(); + expect(searchValue).to.eql('err'); + + expect(menuEntries.length).to.be(1); + expect(await menuEntries[0].getVisibleText()).to.be('error'); + }); + }); + }); + }); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/observability/discover_log_explorer/index.ts b/x-pack/test_serverless/functional/test_suites/observability/discover_log_explorer/index.ts index e334c028cbaca..8e9843fc02815 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/discover_log_explorer/index.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/discover_log_explorer/index.ts @@ -9,7 +9,9 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function (loadTestFile: FtrProviderContext['loadTestFile']) { describe('Discover Log-Explorer profile', function () { + loadTestFile(require.resolve('./columns_selection')); loadTestFile(require.resolve('./customization')); loadTestFile(require.resolve('./dataset_selection_state')); + loadTestFile(require.resolve('./dataset_selector')); }); } From ecaa8a73707f97c99ef2d57574ac3dcbac3096df Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Fri, 11 Aug 2023 15:46:56 +0200 Subject: [PATCH 11/11] [ML] Transforms: Fix privileges check. (#163687) Fixes a regression introduced in #154007. When security is disabled, the check for transform privileges/capabilities would fail and the transform page would not render. - This fixes the endpoint to return data in the correct format should security be disabled. - The endpoint's TypeScript has been updated to catch malformed responses. - The client side custom `useRequest` is replaced with `useQuery` from `'@tanstack/react-query'` which also fixes TypeScript support on the client side. --- .../common/privilege/has_privilege_factory.ts | 7 +- .../public/__mocks__/shared_imports.ts | 5 - x-pack/plugins/transform/public/app/app.tsx | 33 +++--- .../transform/public/app/hooks/index.ts | 1 - .../transform/public/app/hooks/use_request.ts | 15 --- .../components/authorization_provider.tsx | 40 +++++-- .../transform/public/shared_imports.ts | 2 - .../transform/server/routes/api/privileges.ts | 100 +++++++++--------- 8 files changed, 101 insertions(+), 102 deletions(-) delete mode 100644 x-pack/plugins/transform/public/app/hooks/use_request.ts diff --git a/x-pack/plugins/transform/common/privilege/has_privilege_factory.ts b/x-pack/plugins/transform/common/privilege/has_privilege_factory.ts index 972f8e727f50d..9dee0c1a73cf1 100644 --- a/x-pack/plugins/transform/common/privilege/has_privilege_factory.ts +++ b/x-pack/plugins/transform/common/privilege/has_privilege_factory.ts @@ -12,6 +12,11 @@ import { cloneDeep } from 'lodash'; import { APP_INDEX_PRIVILEGES } from '../constants'; import { Privileges } from '../types/privileges'; +export interface PrivilegesAndCapabilities { + privileges: Privileges; + capabilities: Capabilities; +} + export interface TransformCapabilities { canGetTransform: boolean; canDeleteTransform: boolean; @@ -89,7 +94,7 @@ export const getPrivilegesAndCapabilities = ( clusterPrivileges: Record, hasOneIndexWithAllPrivileges: boolean, hasAllPrivileges: boolean -) => { +): PrivilegesAndCapabilities => { const privilegesResult: Privileges = { hasAllPrivileges: true, missingPrivileges: { diff --git a/x-pack/plugins/transform/public/__mocks__/shared_imports.ts b/x-pack/plugins/transform/public/__mocks__/shared_imports.ts index f10b48de27e38..ac4b7fe49a38c 100644 --- a/x-pack/plugins/transform/public/__mocks__/shared_imports.ts +++ b/x-pack/plugins/transform/public/__mocks__/shared_imports.ts @@ -8,11 +8,6 @@ // actual mocks export const expandLiteralStrings = jest.fn(); export const XJsonMode = jest.fn(); -export const useRequest = jest.fn(() => ({ - isLoading: false, - error: null, - data: undefined, -})); export const getSavedSearch = jest.fn(); // just passing through the reimports diff --git a/x-pack/plugins/transform/public/app/app.tsx b/x-pack/plugins/transform/public/app/app.tsx index af411d0aeda6f..ba4a43bfa0876 100644 --- a/x-pack/plugins/transform/public/app/app.tsx +++ b/x-pack/plugins/transform/public/app/app.tsx @@ -7,14 +7,13 @@ import React, { useContext, FC } from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { Router, Routes, Route } from '@kbn/shared-ux-router'; - -import { ScopedHistory } from '@kbn/core/public'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { EuiErrorBoundary } from '@elastic/eui'; +import { Router, Routes, Route } from '@kbn/shared-ux-router'; +import { ScopedHistory } from '@kbn/core/public'; import { FormattedMessage } from '@kbn/i18n-react'; - import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { addInternalBasePath } from '../../common/constants'; @@ -23,7 +22,6 @@ import { SectionError } from './components'; import { SECTION_SLUG } from './common/constants'; import { AuthorizationContext, AuthorizationProvider } from './lib/authorization'; import { AppDependencies } from './app_dependencies'; - import { CloneTransformSection } from './sections/clone_transform'; import { CreateTransformSection } from './sections/create_transform'; import { TransformManagementSection } from './sections/transform_management'; @@ -63,20 +61,23 @@ export const App: FC<{ history: ScopedHistory }> = ({ history }) => { export const renderApp = (element: HTMLElement, appDependencies: AppDependencies) => { const I18nContext = appDependencies.i18n.Context; + const queryClient = new QueryClient(); render( - - - - - - - - - + + + + + + + + + + + , element ); diff --git a/x-pack/plugins/transform/public/app/hooks/index.ts b/x-pack/plugins/transform/public/app/hooks/index.ts index eb2be5f4b9b23..f6a4c72b39a44 100644 --- a/x-pack/plugins/transform/public/app/hooks/index.ts +++ b/x-pack/plugins/transform/public/app/hooks/index.ts @@ -12,4 +12,3 @@ export { useResetTransforms } from './use_reset_transform'; export { useScheduleNowTransforms } from './use_schedule_now_transform'; export { useStartTransforms } from './use_start_transform'; export { useStopTransforms } from './use_stop_transform'; -export { useRequest } from './use_request'; diff --git a/x-pack/plugins/transform/public/app/hooks/use_request.ts b/x-pack/plugins/transform/public/app/hooks/use_request.ts deleted file mode 100644 index de1df3e561612..0000000000000 --- a/x-pack/plugins/transform/public/app/hooks/use_request.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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { UseRequestConfig, useRequest as _useRequest } from '../../shared_imports'; - -import { useAppDependencies } from '../app_dependencies'; - -export const useRequest = (config: UseRequestConfig) => { - const { http } = useAppDependencies(); - return _useRequest(http, config); -}; diff --git a/x-pack/plugins/transform/public/app/lib/authorization/components/authorization_provider.tsx b/x-pack/plugins/transform/public/app/lib/authorization/components/authorization_provider.tsx index 4271e1447b1e8..02bbe4e40a969 100644 --- a/x-pack/plugins/transform/public/app/lib/authorization/components/authorization_provider.tsx +++ b/x-pack/plugins/transform/public/app/lib/authorization/components/authorization_provider.tsx @@ -6,16 +6,20 @@ */ import React, { createContext } from 'react'; +import { useQuery } from '@tanstack/react-query'; -import { Privileges } from '../../../../../common/types/privileges'; +import type { IHttpFetchError } from '@kbn/core-http-browser'; -import { useRequest } from '../../../hooks'; +import type { Privileges } from '../../../../../common/types/privileges'; import { - TransformCapabilities, + type PrivilegesAndCapabilities, + type TransformCapabilities, INITIAL_CAPABILITIES, } from '../../../../../common/privilege/has_privilege_factory'; +import { useAppDependencies } from '../../../app_dependencies'; + interface Authorization { isLoading: boolean; apiError: Error | null; @@ -41,22 +45,36 @@ interface Props { } export const AuthorizationProvider = ({ privilegesEndpoint, children }: Props) => { + const { http } = useAppDependencies(); + const { path, version } = privilegesEndpoint; + const { isLoading, error, data: privilegesData, - } = useRequest({ - path, - version, - method: 'get', - }); + } = useQuery( + ['transform-privileges-and-capabilities'], + async ({ signal }) => { + return await http.fetch(path, { + version, + method: 'GET', + signal, + }); + } + ); const value = { isLoading, - privileges: isLoading ? { ...initialValue.privileges } : privilegesData.privileges, - capabilities: isLoading ? { ...INITIAL_CAPABILITIES } : privilegesData.capabilities, - apiError: error ? (error as Error) : null, + privileges: + isLoading || privilegesData === undefined + ? { ...initialValue.privileges } + : privilegesData.privileges, + capabilities: + isLoading || privilegesData === undefined + ? { ...INITIAL_CAPABILITIES } + : privilegesData.capabilities, + apiError: error ? error : null, }; return ( diff --git a/x-pack/plugins/transform/public/shared_imports.ts b/x-pack/plugins/transform/public/shared_imports.ts index c24f792eacab0..63276cecc7a86 100644 --- a/x-pack/plugins/transform/public/shared_imports.ts +++ b/x-pack/plugins/transform/public/shared_imports.ts @@ -6,8 +6,6 @@ */ export { XJsonMode } from '@kbn/ace'; -export type { UseRequestConfig } from '@kbn/es-ui-shared-plugin/public'; -export { useRequest } from '@kbn/es-ui-shared-plugin/public'; export type { GetMlSharedImportsReturnType } from '@kbn/ml-plugin/public'; export { getMlSharedImports } from '@kbn/ml-plugin/public'; diff --git a/x-pack/plugins/transform/server/routes/api/privileges.ts b/x-pack/plugins/transform/server/routes/api/privileges.ts index cd6817f8be63c..0b93c8e503fc6 100644 --- a/x-pack/plugins/transform/server/routes/api/privileges.ts +++ b/x-pack/plugins/transform/server/routes/api/privileges.ts @@ -5,15 +5,17 @@ * 2.0. */ -import type { IScopedClusterClient } from '@kbn/core/server'; +import type { IKibanaResponse, IScopedClusterClient } from '@kbn/core/server'; import type { SecurityHasPrivilegesResponse } from '@elastic/elasticsearch/lib/api/types'; -import { getPrivilegesAndCapabilities } from '../../../common/privilege/has_privilege_factory'; +import { + getPrivilegesAndCapabilities, + type PrivilegesAndCapabilities, +} from '../../../common/privilege/has_privilege_factory'; import { addInternalBasePath, APP_CLUSTER_PRIVILEGES, APP_INDEX_PRIVILEGES, } from '../../../common/constants'; -import type { Privileges } from '../../../common/types/privileges'; import type { RouteDependencies } from '../../types'; @@ -23,64 +25,60 @@ export function registerPrivilegesRoute({ router, license }: RouteDependencies) path: addInternalBasePath('privileges'), access: 'internal', }) - .addVersion( + .addVersion( { version: '1', validate: false, }, - license.guardApiRoute(async (ctx, req, res) => { - const privilegesResult: Privileges = { - hasAllPrivileges: true, - missingPrivileges: { - cluster: [], - index: [], - }, - }; - - if (license.getStatus().isSecurityEnabled === false) { - // If security isn't enabled, let the user use app. - return res.ok({ body: privilegesResult }); - } + license.guardApiRoute( + async (ctx, req, res): Promise> => { + if (license.getStatus().isSecurityEnabled === false) { + // If security isn't enabled, let the user use app. + return res.ok({ + body: getPrivilegesAndCapabilities({}, true, true), + }); + } - const esClient: IScopedClusterClient = (await ctx.core).elasticsearch.client; + const esClient: IScopedClusterClient = (await ctx.core).elasticsearch.client; - const esClusterPrivilegesReq: Promise = - esClient.asCurrentUser.security.hasPrivileges({ - body: { - cluster: APP_CLUSTER_PRIVILEGES, - }, - }); - const [esClusterPrivileges, userPrivileges] = await Promise.all([ - // Get cluster privileges - esClusterPrivilegesReq, - // // Get all index privileges the user has - esClient.asCurrentUser.security.getUserPrivileges(), - ]); + const esClusterPrivilegesReq: Promise = + esClient.asCurrentUser.security.hasPrivileges({ + body: { + cluster: APP_CLUSTER_PRIVILEGES, + }, + }); + const [esClusterPrivileges, userPrivileges] = await Promise.all([ + // Get cluster privileges + esClusterPrivilegesReq, + // // Get all index privileges the user has + esClient.asCurrentUser.security.getUserPrivileges(), + ]); - const { has_all_requested: hasAllPrivileges, cluster } = esClusterPrivileges; - const { indices } = userPrivileges; + const { has_all_requested: hasAllPrivileges, cluster } = esClusterPrivileges; + const { indices } = userPrivileges; - // Check if they have all the required index privileges for at least one index - const hasOneIndexWithAllPrivileges = - indices.find(({ privileges }: { privileges: string[] }) => { - if (privileges.includes('all')) { - return true; - } + // Check if they have all the required index privileges for at least one index + const hasOneIndexWithAllPrivileges = + indices.find(({ privileges }: { privileges: string[] }) => { + if (privileges.includes('all')) { + return true; + } - const indexHasAllPrivileges = APP_INDEX_PRIVILEGES.every((privilege) => - privileges.includes(privilege) - ); + const indexHasAllPrivileges = APP_INDEX_PRIVILEGES.every((privilege) => + privileges.includes(privilege) + ); - return indexHasAllPrivileges; - }) !== undefined; + return indexHasAllPrivileges; + }) !== undefined; - return res.ok({ - body: getPrivilegesAndCapabilities( - cluster, - hasOneIndexWithAllPrivileges, - hasAllPrivileges - ), - }); - }) + return res.ok({ + body: getPrivilegesAndCapabilities( + cluster, + hasOneIndexWithAllPrivileges, + hasAllPrivileges + ), + }); + } + ) ); }