From aee939450fc9af86d2718cb7c8da9ed085ebb7a1 Mon Sep 17 00:00:00 2001 From: Tiago Vila Verde Date: Tue, 28 Nov 2023 14:53:17 +0100 Subject: [PATCH 01/13] Introducing a selector --- .../components/asset_criticality_selector.tsx | 81 +++++++++++++++++++ .../components/index.stories.tsx | 26 ++++++ .../side_panel/host_details/index.tsx | 3 + 3 files changed, 110 insertions(+) create mode 100644 x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_selector.tsx create mode 100644 x-pack/plugins/security_solution/public/entity_analytics/components/index.stories.tsx diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_selector.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_selector.tsx new file mode 100644 index 0000000000000..5e5e4f586a739 --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_selector.tsx @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiAccordion, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiModal, + EuiModalBody, + EuiModalHeader, + EuiModalHeaderTitle, + EuiSelect, + useGeneratedHtmlId, +} from '@elastic/eui'; +import type { ChangeEventHandler } from 'react'; +import React, { useState } from 'react'; + +import { RiskSeverity } from '../../../common/search_strategy'; +import { RiskScoreLevel } from '../../explore/components/risk_score/severity/common'; + +export const AssetCriticalitySelector = () => { + const [isModalVisible, setIsModalVisible] = useState(false); + + const closeModal = () => setIsModalVisible(false); + const showModal = () => setIsModalVisible(true); + + const options = [ + { value: RiskSeverity.low, text: 'Low' }, + { value: RiskSeverity.moderate, text: 'Moderate' }, + { value: RiskSeverity.high, text: 'High' }, + { value: RiskSeverity.critical, text: 'Critical' }, + ]; + + const [value, setValue] = useState(options[1].value); + + const basicSelectId = useGeneratedHtmlId({ prefix: 'basicSelect' }); + + const onChange: ChangeEventHandler = (e) => { + setValue(e.target.value as RiskSeverity); + }; + + const modal = !isModalVisible ? null : ( + + + {'Change asset criticality'} + + + + + + ); + + return ( + <> + + + + + + + + {'Change'} + + + + + {modal} + + ); +}; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/index.stories.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/index.stories.tsx new file mode 100644 index 0000000000000..5b2e9c61d5950 --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/index.stories.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import type { Story } from '@storybook/react'; +import { StorybookProviders } from '../../common/mock/storybook_providers'; +import { AssetCriticalitySelector } from './asset_criticality_selector'; + +export default { + component: () => , + title: 'AssetCriticalitySelector', +}; + +export const Default: Story = () => { + return ( + + {' '} + + + ); +}; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/index.tsx index 344255c70d1bd..3185b538b19f7 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/index.tsx @@ -16,6 +16,7 @@ import { import { i18n } from '@kbn/i18n'; import React from 'react'; import styled from 'styled-components'; +import { AssetCriticalitySelector } from '../../../../entity_analytics/components/asset_criticality_selector'; import { ExpandableHostDetails, ExpandableHostDetailsPageLink, @@ -78,6 +79,8 @@ export const HostDetailsPanel: React.FC = React.memo( + + From 94116ad4ef39c2c1790f93f2fb6715a8133c7c31 Mon Sep 17 00:00:00 2001 From: Tiago Vila Verde Date: Sun, 3 Dec 2023 22:57:20 +0100 Subject: [PATCH 02/13] functional tests adding footer btns to the modal improving tests --- .../components/asset_criticality_selector.tsx | 140 +++++++++++++----- .../host_details/expandable_host.tsx | 2 +- .../host_details_right_panel.cy.ts | 86 +++++++++++ .../cypress/screens/alerts.ts | 2 + .../host_details_right_panel.ts | 37 +++++ .../cypress/tasks/expandable_flyout/common.ts | 9 +- 6 files changed, 235 insertions(+), 41 deletions(-) create mode 100644 x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/host_details_right_panel.cy.ts create mode 100644 x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/host_details_right_panel.ts diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_selector.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_selector.tsx index 5e5e4f586a739..4ee5edd7ec2de 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_selector.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_selector.tsx @@ -7,75 +7,137 @@ import { EuiAccordion, + EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiModal, EuiModalBody, + EuiModalFooter, EuiModalHeader, EuiModalHeaderTitle, EuiSelect, useGeneratedHtmlId, } from '@elastic/eui'; -import type { ChangeEventHandler } from 'react'; +import { i18n } from '@kbn/i18n'; +import type { MouseEventHandler } from 'react'; import React, { useState } from 'react'; +import { useToggle } from 'react-use'; import { RiskSeverity } from '../../../common/search_strategy'; import { RiskScoreLevel } from '../../explore/components/risk_score/severity/common'; export const AssetCriticalitySelector = () => { - const [isModalVisible, setIsModalVisible] = useState(false); + const criticality = useAssetCriticality(); + const { modal, state, setState } = criticality; - const closeModal = () => setIsModalVisible(false); - const showModal = () => setIsModalVisible(true); - - const options = [ - { value: RiskSeverity.low, text: 'Low' }, - { value: RiskSeverity.moderate, text: 'Moderate' }, - { value: RiskSeverity.high, text: 'High' }, - { value: RiskSeverity.critical, text: 'Critical' }, - ]; + const cancel: MouseEventHandler = (e) => { + setState({ value: options[1].value, dirty: false }); + }; - const [value, setValue] = useState(options[1].value); + return ( + <> + + + + + + + + {state.dirty ? 'Cancel Changes' : 'Change'} + + + + + {modal.visible ? : null} + + ); +}; +const AssetCriticalityModal: React.FC = ({ modal, state, setState }) => { const basicSelectId = useGeneratedHtmlId({ prefix: 'basicSelect' }); - const onChange: ChangeEventHandler = (e) => { - setValue(e.target.value as RiskSeverity); - }; - - const modal = !isModalVisible ? null : ( - + return ( + - {'Change asset criticality'} + + {i18n.translate('xpack.securitySolution.timeline.sidePanel.assetCriticality', { + defaultMessage: 'Pick asset criticality level', + })} + setState({ value: e.target.value as RiskSeverity, dirty: true })} + aria-label={i18n.translate( + 'xpack.securitySolution.timeline.sidePanel.hostDetails.assetCriticality', + { + defaultMessage: 'Pick asset criticality level', + } + )} + data-test-subj="asset-criticality-modal-select-dropdown" /> + + {'Cancel'} + + + {'Save'} + + ); +}; - return ( - <> - - - - - - - - {'Change'} - - - - - {modal} - - ); +const useAssetCriticality = (): AssetCriticality => { + const [visible, toggleModal] = useToggle(false); + const [state, setState] = useState({ value: options[1].value, dirty: false }); + + return { + // returning a thunk so we can directly pass it to event handlers + modal: { visible, toggle: (next: boolean) => () => toggleModal(next) }, + state, + setState, + }; }; + +interface State { + value: RiskSeverity; + dirty: boolean; +} + +interface ModalState { + visible: boolean; + toggle: (next: boolean) => () => void; +} + +interface AssetCriticality { + modal: ModalState; + state: State; + setState: (action: React.SetStateAction) => void; +} + +const options = [ + { value: RiskSeverity.unknown, text: 'Unknown' }, + { value: RiskSeverity.low, text: 'Low' }, + { value: RiskSeverity.moderate, text: 'Moderate' }, + { value: RiskSeverity.high, text: 'High' }, + { value: RiskSeverity.critical, text: 'Critical' }, +]; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.tsx index d307b9e8273ea..c5f805a53ac87 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.tsx @@ -35,7 +35,7 @@ const StyledTitle = styled.h4` export const ExpandableHostDetailsTitle = ({ hostName }: ExpandableHostProps) => ( - + {i18n.translate('xpack.securitySolution.timeline.sidePanel.hostDetails.title', { defaultMessage: 'Host details', })} diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/host_details_right_panel.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/host_details_right_panel.cy.ts new file mode 100644 index 0000000000000..2c543b0581cd8 --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/host_details_right_panel.cy.ts @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_TITLE, + HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_BUTTON, + HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_SELECTOR, + HOST_DETAILS_FLYOUT_SECTION_HEADER, + toggleAssetCriticalityAccordion, + toggleAssetCriticalityModal, + HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_SAVE_BTN, + HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_LEVEL, + HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_DROPDOWN, +} from '../../../../screens/expandable_flyout/host_details_right_panel'; +import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; +import { expandFirstAlertHostExpandableFlyout } from '../../../../tasks/expandable_flyout/common'; + +import { login } from '../../../../tasks/login'; +import { visit } from '../../../../tasks/navigation'; +import { createRule } from '../../../../tasks/api_calls/rules'; +import { getNewRule } from '../../../../objects/rule'; +import { ALERTS_URL } from '../../../../urls/navigation'; +import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; + +describe( + 'Alert host details expandable flyout right panel', + { tags: ['@ess', '@serverless'] }, + () => { + const rule = { ...getNewRule(), investigation_fields: { field_names: ['host.os.name'] } }; + + beforeEach(() => { + deleteAlertsAndRules(); + login(); + createRule(rule); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertHostExpandableFlyout(); + }); + + describe('Expandable flyout', () => { + it('should display header section', () => { + cy.log('header and content'); + + cy.get(HOST_DETAILS_FLYOUT_SECTION_HEADER).should('contain.text', 'Host details'); + }); + + it('should display asset criticality accordion', () => { + cy.log('asset criticality'); + + cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_SELECTOR).should( + 'contain.text', + 'Asset Criticality' + ); + + toggleAssetCriticalityAccordion(); + cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_BUTTON).should('have.text', 'Change'); + }); + + it('should display asset criticality modal', () => { + cy.log('asset criticality modal'); + + toggleAssetCriticalityModal(); + cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_TITLE).should( + 'have.text', + 'Pick asset criticality level' + ); + }); + + it('should update asset criticality state', () => { + cy.log('asset criticality update'); + + toggleAssetCriticalityModal(); + cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_DROPDOWN) + .should('be.visible') + .select('High'); + + cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_SAVE_BTN).should('be.visible').click(); + cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_LEVEL).contains('High').should('be.visible'); + }); + }); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts b/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts index 83de06466f251..f07c6fe303082 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts @@ -93,6 +93,8 @@ export const ATTACH_TO_NEW_CASE_BUTTON = '[data-test-subj="add-to-new-case-actio export const USER_COLUMN = '[data-gridcell-column-id="user.name"]'; +export const OPEN_HOST_FLYOUT_BUTTON = '[data-test-subj="host-details-button"]'; + export const HOST_RISK_HEADER_COLUMN = '[data-test-subj="dataGridHeaderCell-host.risk.calculated_level"]'; diff --git a/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/host_details_right_panel.ts b/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/host_details_right_panel.ts new file mode 100644 index 0000000000000..92c9eb238836e --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/host_details_right_panel.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getDataTestSubjectSelector } from '../../helpers/common'; + +export const HOST_DETAILS_FLYOUT_SECTION_HEADER = getDataTestSubjectSelector('host-details-header'); +export const HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_SELECTOR = getDataTestSubjectSelector( + 'asset-criticality-selector' +); +export const HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_LEVEL = getDataTestSubjectSelector('risk-score'); +export const HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_BUTTON = getDataTestSubjectSelector( + 'asset-criticality-change-btn' +); +export const HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_TITLE = getDataTestSubjectSelector( + 'asset-criticality-modal-title' +); +export const HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_DROPDOWN = getDataTestSubjectSelector( + 'asset-criticality-modal-select-dropdown' +); +export const HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_SAVE_BTN = getDataTestSubjectSelector( + 'asset-criticality-modal-save-btn' +); + +export const toggleAssetCriticalityAccordion = () => { + cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_SELECTOR).scrollIntoView(); + cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_SELECTOR).should('be.visible').click(); +}; + +export const toggleAssetCriticalityModal = () => { + toggleAssetCriticalityAccordion(); + + cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_BUTTON).should('be.visible').click(); +}; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout/common.ts b/x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout/common.ts index cd244f192bd52..187793d206422 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout/common.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout/common.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { EXPAND_ALERT_BTN } from '../../screens/alerts'; +import { EXPAND_ALERT_BTN, OPEN_HOST_FLYOUT_BUTTON } from '../../screens/alerts'; import { DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE } from '../../screens/expandable_flyout/alert_details_right_panel'; import { DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_CREATE_BUTTON, @@ -23,6 +23,13 @@ export const expandFirstAlertExpandableFlyout = () => { cy.get(EXPAND_ALERT_BTN).first().click(); }; +/** + * Find the first alert row in the alerts table then click on the host name to open the flyout + */ +export const expandFirstAlertHostExpandableFlyout = () => { + cy.get(OPEN_HOST_FLYOUT_BUTTON).first().click(); +}; + /** * create a new case from the expanded expandable flyout */ From 580b3a57721e6ba237f59750ffa3a36cde949b95 Mon Sep 17 00:00:00 2001 From: Tiago Vila Verde Date: Tue, 5 Dec 2023 11:05:07 +0100 Subject: [PATCH 03/13] adding api call using react-query --- .../public/entity_analytics/api/api.ts | 34 ++++ .../components/asset_criticality_selector.tsx | 184 ++++++++++++------ .../side_panel/host_details/index.tsx | 2 +- 3 files changed, 159 insertions(+), 61 deletions(-) diff --git a/x-pack/plugins/security_solution/public/entity_analytics/api/api.ts b/x-pack/plugins/security_solution/public/entity_analytics/api/api.ts index 73cb1cbd57ff0..6d6aabcd29278 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/api/api.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/api/api.ts @@ -5,6 +5,8 @@ * 2.0. */ +import type { SnakeToCamelCase } from '@kbn/cases-plugin/common/types'; +import type { AssetCriticalityRecord } from '../../../common/api/entity_analytics/asset_criticality'; import { RISK_ENGINE_STATUS_URL, RISK_SCORE_PREVIEW_URL, @@ -13,6 +15,7 @@ import { RISK_ENGINE_INIT_URL, RISK_ENGINE_PRIVILEGES_URL, ASSET_CRITICALITY_PRIVILEGES_URL, + ASSET_CRITICALITY_URL, } from '../../../common/constants'; import type { @@ -111,3 +114,34 @@ export const useEntityAnalyticsRoutes = () => { fetchAssetCriticalityPrivileges, }; }; + +type AssetCriticality = SnakeToCamelCase; +/** + * Create asset criticality + */ +export const createAssetCriticality = async ( + params: Pick +): Promise => { + return KibanaServices.get().http.fetch(ASSET_CRITICALITY_URL, { + version: '1', + method: 'POST', + body: JSON.stringify({ + id_value: params.idValue, + id_field: params.idField, + criticality_level: params.criticalityLevel, + }), + }); +}; + +/** + * Get asset criticality + */ +export const fetchAssetCriticality = async ( + params: Pick +): Promise => { + return KibanaServices.get().http.fetch(ASSET_CRITICALITY_URL, { + version: '1', + method: 'GET', + query: { id_value: params.idValue, id_field: params.idField }, + }); +}; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_selector.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_selector.tsx index 4ee5edd7ec2de..cdcbc77ecb5bf 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_selector.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_selector.tsx @@ -11,29 +11,32 @@ import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, + EuiLoadingSpinner, EuiModal, EuiModalBody, EuiModalFooter, EuiModalHeader, EuiModalHeaderTitle, EuiSelect, + EuiText, useGeneratedHtmlId, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import type { MouseEventHandler } from 'react'; + +import type { UseMutationResult, UseQueryResult } from '@tanstack/react-query'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; + import React, { useState } from 'react'; import { useToggle } from 'react-use'; +import type { AssetCriticalityRecord } from '../../../common/api/entity_analytics/asset_criticality'; -import { RiskSeverity } from '../../../common/search_strategy'; -import { RiskScoreLevel } from '../../explore/components/risk_score/severity/common'; +import { createAssetCriticality, fetchAssetCriticality } from '../api/api'; -export const AssetCriticalitySelector = () => { - const criticality = useAssetCriticality(); - const { modal, state, setState } = criticality; - - const cancel: MouseEventHandler = (e) => { - setState({ value: options[1].value, dirty: false }); - }; +interface Props { + entity: Entity; +} +export const AssetCriticalitySelector: React.FC = ({ entity }) => { + const criticality = useAssetCriticality(entity); return ( <> @@ -42,60 +45,80 @@ export const AssetCriticalitySelector = () => { buttonContent="Asset Criticality" data-test-subj="asset-criticality-selector" > - - - - - - - {state.dirty ? 'Cancel Changes' : 'Change'} - - - + {criticality.query.isLoading || criticality.mutation.isLoading ? ( + + ) : ( + + + +

+ {criticality.status === 'update' && criticality.query.data?.criticality_level + ? criticalityDisplayText[criticality.query.data.criticality_level] + : CREATE_ASSET_CRITICALITY} +

+
+
+ + + {criticality.status === 'update' ? 'Change' : 'Create'} + + +
+ )} - {modal.visible ? : null} + {criticality.modal.visible ? ( + + ) : null} ); }; -const AssetCriticalityModal: React.FC = ({ modal, state, setState }) => { +interface ModalProps { + criticality: State; + entity: Entity; +} +const AssetCriticalityModal: React.FC = ({ criticality, entity }) => { const basicSelectId = useGeneratedHtmlId({ prefix: 'basicSelect' }); + const [value, setNewValue] = useState( + criticality.query.data?.criticality_level ?? 'normal' + ); return ( - + - {i18n.translate('xpack.securitySolution.timeline.sidePanel.assetCriticality', { - defaultMessage: 'Pick asset criticality level', - })} + {PICK_ASSET_CRITICALITY} setState({ value: e.target.value as RiskSeverity, dirty: true })} - aria-label={i18n.translate( - 'xpack.securitySolution.timeline.sidePanel.hostDetails.assetCriticality', - { - defaultMessage: 'Pick asset criticality level', - } - )} + value={value} + onChange={(e) => + setNewValue(e.target.value as AssetCriticalityRecord['criticality_level']) + } + aria-label={PICK_ASSET_CRITICALITY} data-test-subj="asset-criticality-modal-select-dropdown" /> - {'Cancel'} + {'Cancel'} + criticality.mutation.mutate({ + criticalityLevel: value, + idField: `${entity.type}.name`, + idValue: entity.name, + }) + } fill data-test-subj="asset-criticality-modal-save-btn" > @@ -106,38 +129,79 @@ const AssetCriticalityModal: React.FC = ({ modal, state, setSt ); }; -const useAssetCriticality = (): AssetCriticality => { +const useAssetCriticality = (entity: Entity): State => { const [visible, toggleModal] = useToggle(false); - const [state, setState] = useState({ value: options[1].value, dirty: false }); + const modal = { visible, toggle: (next: boolean) => () => toggleModal(next) }; + + const QC = useQueryClient(); + + const query = useQuery({ + queryKey: ['ASSET_CRITICALITY', entity.name], + queryFn: () => fetchAssetCriticality({ idField: `${entity.type}.name`, idValue: entity.name }), + retry: (failureCount, error) => error.body.statusCode === 404 && failureCount > 0, + }); + + const mutation = useMutation({ + mutationFn: createAssetCriticality, + onSuccess: (data) => { + QC.setQueryData(['ASSET_CRITICALITY', entity.name], data); + toggleModal(false); + }, + }); return { - // returning a thunk so we can directly pass it to event handlers - modal: { visible, toggle: (next: boolean) => () => toggleModal(next) }, - state, - setState, + status: query.isError && query.error.body.statusCode === 404 ? 'create' : 'update', + query, + mutation, + modal, }; }; interface State { - value: RiskSeverity; - dirty: boolean; + status: 'create' | 'update'; + query: UseQueryResult; + mutation: UseMutationResult; + modal: ModalState; } +type Params = Parameters[0]; interface ModalState { visible: boolean; toggle: (next: boolean) => () => void; } -interface AssetCriticality { - modal: ModalState; - state: State; - setState: (action: React.SetStateAction) => void; +interface Entity { + name: string; + type: 'host' | 'user'; } -const options = [ - { value: RiskSeverity.unknown, text: 'Unknown' }, - { value: RiskSeverity.low, text: 'Low' }, - { value: RiskSeverity.moderate, text: 'Moderate' }, - { value: RiskSeverity.high, text: 'High' }, - { value: RiskSeverity.critical, text: 'Critical' }, +const criticalityDisplayText: Record = { + normal: 'Normal', + not_important: 'Not important', + important: 'Important', + very_important: 'Very important', +}; +interface KeyVal { + value: AssetCriticalityRecord['criticality_level']; + text: string; +} +const options: KeyVal[] = [ + { value: 'normal', text: criticalityDisplayText.normal }, + { value: 'not_important', text: criticalityDisplayText.not_important }, + { value: 'important', text: criticalityDisplayText.important }, + { value: 'very_important', text: criticalityDisplayText.very_important }, ]; + +const PICK_ASSET_CRITICALITY = i18n.translate( + 'xpack.securitySolution.timeline.sidePanel.hostDetails.assetCriticality.pick', + { + defaultMessage: 'Pick asset criticality level', + } +); + +const CREATE_ASSET_CRITICALITY = i18n.translate( + 'xpack.securitySolution.timeline.sidePanel.hostDetails.assetCriticality.create', + { + defaultMessage: 'No criticality assigned yet', + } +); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/index.tsx index 3185b538b19f7..55e3f28629ae0 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/index.tsx @@ -80,7 +80,7 @@ export const HostDetailsPanel: React.FC = React.memo( - + From 4f44826493cb7f2067efb938dc66fa94482a8457 Mon Sep 17 00:00:00 2001 From: Tiago Vila Verde Date: Sun, 10 Dec 2023 15:57:40 +0100 Subject: [PATCH 04/13] checking privileges --- .../components/asset_criticality_selector.tsx | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_selector.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_selector.tsx index cdcbc77ecb5bf..76c39b3d9965d 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_selector.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_selector.tsx @@ -28,9 +28,14 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import React, { useState } from 'react'; import { useToggle } from 'react-use'; +import type { EntityAnalyticsPrivileges } from '../../../common/api/entity_analytics/common'; import type { AssetCriticalityRecord } from '../../../common/api/entity_analytics/asset_criticality'; -import { createAssetCriticality, fetchAssetCriticality } from '../api/api'; +import { + createAssetCriticality, + fetchAssetCriticality, + fetchAssetCriticalityPrivileges, +} from '../api/api'; interface Props { entity: Entity; @@ -38,6 +43,10 @@ interface Props { export const AssetCriticalitySelector: React.FC = ({ entity }) => { const criticality = useAssetCriticality(entity); + if (criticality.privileges.isLoading || !criticality.privileges.data?.has_all_required) { + return null; + } + return ( <> { const QC = useQueryClient(); + const privileges = useQuery({ + queryKey: ['ASSET_CRITICALITY', 'PRIVILEGES', entity.name], + queryFn: fetchAssetCriticalityPrivileges, + }); const query = useQuery({ queryKey: ['ASSET_CRITICALITY', entity.name], queryFn: () => fetchAssetCriticality({ idField: `${entity.type}.name`, idValue: entity.name }), retry: (failureCount, error) => error.body.statusCode === 404 && failureCount > 0, + enabled: privileges.data?.has_all_required, }); const mutation = useMutation({ @@ -154,12 +168,14 @@ const useAssetCriticality = (entity: Entity): State => { query, mutation, modal, + privileges, }; }; interface State { status: 'create' | 'update'; query: UseQueryResult; + privileges: UseQueryResult; mutation: UseMutationResult; modal: ModalState; } From b660fefcaf0286b9d703ff5ba3f32cb0d108ccc0 Mon Sep 17 00:00:00 2001 From: Tiago Vila Verde Date: Sun, 10 Dec 2023 15:58:06 +0100 Subject: [PATCH 05/13] removing story --- .../components/index.stories.tsx | 26 ------------------- 1 file changed, 26 deletions(-) delete mode 100644 x-pack/plugins/security_solution/public/entity_analytics/components/index.stories.tsx diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/index.stories.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/index.stories.tsx deleted file mode 100644 index 5b2e9c61d5950..0000000000000 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/index.stories.tsx +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; - -import type { Story } from '@storybook/react'; -import { StorybookProviders } from '../../common/mock/storybook_providers'; -import { AssetCriticalitySelector } from './asset_criticality_selector'; - -export default { - component: () => , - title: 'AssetCriticalitySelector', -}; - -export const Default: Story = () => { - return ( - - {' '} - - - ); -}; From 86fda7c6d5353ecd61d16c44528e2bdd0802b645 Mon Sep 17 00:00:00 2001 From: Tiago Vila Verde Date: Mon, 11 Dec 2023 11:35:09 +0100 Subject: [PATCH 06/13] replacing with super selector and adding color badges --- .../components/asset_criticality_selector.tsx | 96 +++++++++++++++---- .../side_panel/host_details/index.tsx | 4 +- 2 files changed, 77 insertions(+), 23 deletions(-) diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_selector.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_selector.tsx index 76c39b3d9965d..6e29c063a4b13 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_selector.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_selector.tsx @@ -5,19 +5,21 @@ * 2.0. */ +import type { EuiSuperSelectOption } from '@elastic/eui'; import { EuiAccordion, EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, + EuiHealth, EuiLoadingSpinner, EuiModal, EuiModalBody, EuiModalFooter, EuiModalHeader, EuiModalHeaderTitle, - EuiSelect, + EuiSuperSelect, EuiText, useGeneratedHtmlId, } from '@elastic/eui'; @@ -28,6 +30,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import React, { useState } from 'react'; import { useToggle } from 'react-use'; +import { euiLightVars } from '@kbn/ui-theme'; import type { EntityAnalyticsPrivileges } from '../../../common/api/entity_analytics/common'; import type { AssetCriticalityRecord } from '../../../common/api/entity_analytics/asset_criticality'; @@ -60,11 +63,15 @@ export const AssetCriticalitySelector: React.FC = ({ entity }) => { -

- {criticality.status === 'update' && criticality.query.data?.criticality_level - ? criticalityDisplayText[criticality.query.data.criticality_level] - : CREATE_ASSET_CRITICALITY} -

+ {criticality.status === 'update' && criticality.query.data?.criticality_level ? ( + + {criticalityDisplayText[criticality.query.data.criticality_level]} + + ) : ( + {CREATE_ASSET_CRITICALITY} + )}
@@ -106,13 +113,11 @@ const AssetCriticalityModal: React.FC = ({ criticality, entity }) => - - setNewValue(e.target.value as AssetCriticalityRecord['criticality_level']) - } + valueOfSelected={value} + onChange={setNewValue} aria-label={PICK_ASSET_CRITICALITY} data-test-subj="asset-criticality-modal-select-dropdown" /> @@ -197,16 +202,13 @@ const criticalityDisplayText: Record = + { + very_important: '#E7664C', + important: '#D6BF57', + normal: '#54B399', + not_important: euiLightVars.euiColorMediumShade, + }; const PICK_ASSET_CRITICALITY = i18n.translate( 'xpack.securitySolution.timeline.sidePanel.hostDetails.assetCriticality.pick', @@ -221,3 +223,55 @@ const CREATE_ASSET_CRITICALITY = i18n.translate( defaultMessage: 'No criticality assigned yet', } ); + +const ASSET_CRITICALITY_OPTION_TEXT: Record = { + normal: i18n.translate( + 'xpack.securitySolution.timeline.sidePanel.hostDetails.assetCriticality.pickerOption.normal', + { + defaultMessage: 'Entity risk score rises at normal speed', + } + ), + not_important: i18n.translate( + 'xpack.securitySolution.timeline.sidePanel.hostDetails.assetCriticality.pickerOption.notImportant', + { + defaultMessage: 'Entity risk score rises slower', + } + ), + important: i18n.translate( + 'xpack.securitySolution.timeline.sidePanel.hostDetails.assetCriticality.pickerOption.important', + { + defaultMessage: 'Entity risk score rises faster', + } + ), + very_important: i18n.translate( + 'xpack.securitySolution.timeline.sidePanel.hostDetails.assetCriticality.pickerOption.veryImportant', + { + defaultMessage: 'Entity risk score rises much faster', + } + ), +}; + +const option = ( + level: AssetCriticalityRecord['criticality_level'] +): EuiSuperSelectOption => ({ + value: level, + dropdownDisplay: ( + + {criticalityDisplayText[level]} + +

{ASSET_CRITICALITY_OPTION_TEXT[level]}

+
+
+ ), + inputDisplay: ( + + {criticalityDisplayText[level]} + + ), +}); +const options: Array> = [ + option('normal'), + option('not_important'), + option('important'), + option('very_important'), +]; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/index.tsx index 55e3f28629ae0..2e440cf42acf3 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/index.tsx @@ -79,12 +79,12 @@ export const HostDetailsPanel: React.FC = React.memo( - - + + ) : ( From 8d8c28bc32b39e0e42aa9762d1d98151e52faaea Mon Sep 17 00:00:00 2001 From: Tiago Vila Verde Date: Mon, 11 Dec 2023 11:44:56 +0100 Subject: [PATCH 07/13] moving stuff around --- .../asset_criticality/constants.ts | 56 +++++++++++++++++++ .../components/asset_criticality_selector.tsx | 56 ++----------------- 2 files changed, 62 insertions(+), 50 deletions(-) diff --git a/x-pack/plugins/security_solution/common/entity_analytics/asset_criticality/constants.ts b/x-pack/plugins/security_solution/common/entity_analytics/asset_criticality/constants.ts index 64746a9ba96b5..89a5f8a6c2fe4 100644 --- a/x-pack/plugins/security_solution/common/entity_analytics/asset_criticality/constants.ts +++ b/x-pack/plugins/security_solution/common/entity_analytics/asset_criticality/constants.ts @@ -5,9 +5,65 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; +import { euiLightVars } from '@kbn/ui-theme'; +import type { AssetCriticalityRecord } from '../api/entity_analytics/asset_criticality'; + export const ASSET_CRITICALITY_INDEX_PATTERN = '.asset-criticality.asset-criticality-*'; type AssetCriticalityIndexPrivilege = 'read' | 'write'; export const ASSET_CRITICALITY_REQUIRED_ES_INDEX_PRIVILEGES = { [ASSET_CRITICALITY_INDEX_PATTERN]: ['read', 'write'] as AssetCriticalityIndexPrivilege[], }; + +export const PICK_ASSET_CRITICALITY = i18n.translate( + 'xpack.securitySolution.timeline.sidePanel.hostDetails.assetCriticality.pick', + { + defaultMessage: 'Pick asset criticality level', + } +); + +export const CREATE_ASSET_CRITICALITY = i18n.translate( + 'xpack.securitySolution.timeline.sidePanel.hostDetails.assetCriticality.create', + { + defaultMessage: 'No criticality assigned yet', + } +); + +export const ASSET_CRITICALITY_OPTION_TEXT: Record< + AssetCriticalityRecord['criticality_level'], + string +> = { + normal: i18n.translate( + 'xpack.securitySolution.timeline.sidePanel.hostDetails.assetCriticality.pickerOption.normal', + { + defaultMessage: 'Entity risk score rises at normal speed', + } + ), + not_important: i18n.translate( + 'xpack.securitySolution.timeline.sidePanel.hostDetails.assetCriticality.pickerOption.notImportant', + { + defaultMessage: 'Entity risk score rises slower', + } + ), + important: i18n.translate( + 'xpack.securitySolution.timeline.sidePanel.hostDetails.assetCriticality.pickerOption.important', + { + defaultMessage: 'Entity risk score rises faster', + } + ), + very_important: i18n.translate( + 'xpack.securitySolution.timeline.sidePanel.hostDetails.assetCriticality.pickerOption.veryImportant', + { + defaultMessage: 'Entity risk score rises much faster', + } + ), +}; + +export const CRITICALITY_LEVEL_COLOR: Record = + { + very_important: '#E7664C', + important: '#D6BF57', + normal: '#54B399', + not_important: euiLightVars.euiColorMediumShade, + }; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_selector.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_selector.tsx index 6e29c063a4b13..ac3ea9b0252e8 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_selector.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_selector.tsx @@ -23,14 +23,18 @@ import { EuiText, useGeneratedHtmlId, } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; import type { UseMutationResult, UseQueryResult } from '@tanstack/react-query'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import React, { useState } from 'react'; import { useToggle } from 'react-use'; -import { euiLightVars } from '@kbn/ui-theme'; +import { + ASSET_CRITICALITY_OPTION_TEXT, + CREATE_ASSET_CRITICALITY, + CRITICALITY_LEVEL_COLOR, + PICK_ASSET_CRITICALITY, +} from '../../../common/asset_criticality'; import type { EntityAnalyticsPrivileges } from '../../../common/api/entity_analytics/common'; import type { AssetCriticalityRecord } from '../../../common/api/entity_analytics/asset_criticality'; @@ -202,54 +206,6 @@ const criticalityDisplayText: Record = - { - very_important: '#E7664C', - important: '#D6BF57', - normal: '#54B399', - not_important: euiLightVars.euiColorMediumShade, - }; - -const PICK_ASSET_CRITICALITY = i18n.translate( - 'xpack.securitySolution.timeline.sidePanel.hostDetails.assetCriticality.pick', - { - defaultMessage: 'Pick asset criticality level', - } -); - -const CREATE_ASSET_CRITICALITY = i18n.translate( - 'xpack.securitySolution.timeline.sidePanel.hostDetails.assetCriticality.create', - { - defaultMessage: 'No criticality assigned yet', - } -); - -const ASSET_CRITICALITY_OPTION_TEXT: Record = { - normal: i18n.translate( - 'xpack.securitySolution.timeline.sidePanel.hostDetails.assetCriticality.pickerOption.normal', - { - defaultMessage: 'Entity risk score rises at normal speed', - } - ), - not_important: i18n.translate( - 'xpack.securitySolution.timeline.sidePanel.hostDetails.assetCriticality.pickerOption.notImportant', - { - defaultMessage: 'Entity risk score rises slower', - } - ), - important: i18n.translate( - 'xpack.securitySolution.timeline.sidePanel.hostDetails.assetCriticality.pickerOption.important', - { - defaultMessage: 'Entity risk score rises faster', - } - ), - very_important: i18n.translate( - 'xpack.securitySolution.timeline.sidePanel.hostDetails.assetCriticality.pickerOption.veryImportant', - { - defaultMessage: 'Entity risk score rises much faster', - } - ), -}; const option = ( level: AssetCriticalityRecord['criticality_level'] From dc414412d2c3b5687b0ba8306bcdd8b06aff7be5 Mon Sep 17 00:00:00 2001 From: Tiago Vila Verde Date: Mon, 11 Dec 2023 16:05:10 +0100 Subject: [PATCH 08/13] review --- .../asset_criticality/constants.ts | 56 ----- .../public/entity_analytics/api/api.ts | 65 ++--- .../public/entity_analytics/common/utils.ts | 17 ++ .../asset_criticality_selector.tsx | 203 +++++++++++++++ .../components/asset_criticality/common.ts | 28 +++ .../asset_criticality/translations.ts | 69 ++++++ .../use_asset_criticality.ts | 76 ++++++ .../components/asset_criticality_selector.tsx | 233 ------------------ .../components/host_overview/index.tsx | 2 + .../components/user_overview/index.tsx | 3 + .../side_panel/host_details/index.tsx | 2 - .../test/security_solution_cypress/config.ts | 1 + .../asset_criticality/host_flyout.cy.ts} | 55 +++-- .../host_details_right_panel.ts | 10 +- .../serverless_config.ts | 1 + 15 files changed, 474 insertions(+), 347 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/asset_criticality_selector.tsx create mode 100644 x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/common.ts create mode 100644 x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/translations.ts create mode 100644 x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/use_asset_criticality.ts delete mode 100644 x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_selector.tsx rename x-pack/test/security_solution_cypress/cypress/e2e/{investigations/alerts/expandable_flyout/host_details_right_panel.cy.ts => entity_analytics/asset_criticality/host_flyout.cy.ts} (61%) diff --git a/x-pack/plugins/security_solution/common/entity_analytics/asset_criticality/constants.ts b/x-pack/plugins/security_solution/common/entity_analytics/asset_criticality/constants.ts index 89a5f8a6c2fe4..64746a9ba96b5 100644 --- a/x-pack/plugins/security_solution/common/entity_analytics/asset_criticality/constants.ts +++ b/x-pack/plugins/security_solution/common/entity_analytics/asset_criticality/constants.ts @@ -5,65 +5,9 @@ * 2.0. */ -import { i18n } from '@kbn/i18n'; -import { euiLightVars } from '@kbn/ui-theme'; -import type { AssetCriticalityRecord } from '../api/entity_analytics/asset_criticality'; - export const ASSET_CRITICALITY_INDEX_PATTERN = '.asset-criticality.asset-criticality-*'; type AssetCriticalityIndexPrivilege = 'read' | 'write'; export const ASSET_CRITICALITY_REQUIRED_ES_INDEX_PRIVILEGES = { [ASSET_CRITICALITY_INDEX_PATTERN]: ['read', 'write'] as AssetCriticalityIndexPrivilege[], }; - -export const PICK_ASSET_CRITICALITY = i18n.translate( - 'xpack.securitySolution.timeline.sidePanel.hostDetails.assetCriticality.pick', - { - defaultMessage: 'Pick asset criticality level', - } -); - -export const CREATE_ASSET_CRITICALITY = i18n.translate( - 'xpack.securitySolution.timeline.sidePanel.hostDetails.assetCriticality.create', - { - defaultMessage: 'No criticality assigned yet', - } -); - -export const ASSET_CRITICALITY_OPTION_TEXT: Record< - AssetCriticalityRecord['criticality_level'], - string -> = { - normal: i18n.translate( - 'xpack.securitySolution.timeline.sidePanel.hostDetails.assetCriticality.pickerOption.normal', - { - defaultMessage: 'Entity risk score rises at normal speed', - } - ), - not_important: i18n.translate( - 'xpack.securitySolution.timeline.sidePanel.hostDetails.assetCriticality.pickerOption.notImportant', - { - defaultMessage: 'Entity risk score rises slower', - } - ), - important: i18n.translate( - 'xpack.securitySolution.timeline.sidePanel.hostDetails.assetCriticality.pickerOption.important', - { - defaultMessage: 'Entity risk score rises faster', - } - ), - very_important: i18n.translate( - 'xpack.securitySolution.timeline.sidePanel.hostDetails.assetCriticality.pickerOption.veryImportant', - { - defaultMessage: 'Entity risk score rises much faster', - } - ), -}; - -export const CRITICALITY_LEVEL_COLOR: Record = - { - very_important: '#E7664C', - important: '#D6BF57', - normal: '#54B399', - not_important: euiLightVars.euiColorMediumShade, - }; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/api/api.ts b/x-pack/plugins/security_solution/public/entity_analytics/api/api.ts index 6d6aabcd29278..76255ae1ef0ae 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/api/api.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/api/api.ts @@ -5,7 +5,6 @@ * 2.0. */ -import type { SnakeToCamelCase } from '@kbn/cases-plugin/common/types'; import type { AssetCriticalityRecord } from '../../../common/api/entity_analytics/asset_criticality'; import { RISK_ENGINE_STATUS_URL, @@ -27,6 +26,8 @@ import type { } from '../../../server/lib/entity_analytics/types'; import type { RiskScorePreviewRequestSchema } from '../../../common/entity_analytics/risk_engine/risk_score_preview/request_schema'; import type { EntityAnalyticsPrivileges } from '../../../common/api/entity_analytics/common'; +import type { SnakeToCamelCase } from '../common/utils'; + import { useKibana } from '../../common/lib/kibana/kibana_react'; export const useEntityAnalyticsRoutes = () => { @@ -104,6 +105,35 @@ export const useEntityAnalyticsRoutes = () => { method: 'GET', }); + /** + * Create asset criticality + */ + const createAssetCriticality = async ( + params: Pick + ): Promise => + http.fetch(ASSET_CRITICALITY_URL, { + version: '1', + method: 'POST', + body: JSON.stringify({ + id_value: params.idValue, + id_field: params.idField, + criticality_level: params.criticalityLevel, + }), + }); + + /** + * Get asset criticality + */ + const fetchAssetCriticality = async ( + params: Pick + ): Promise => { + return http.fetch(ASSET_CRITICALITY_URL, { + version: '1', + method: 'GET', + query: { id_value: params.idValue, id_field: params.idField }, + }); + }; + return { fetchRiskScorePreview, fetchRiskEngineStatus, @@ -112,36 +142,9 @@ export const useEntityAnalyticsRoutes = () => { disableRiskEngine, fetchRiskEnginePrivileges, fetchAssetCriticalityPrivileges, + createAssetCriticality, + fetchAssetCriticality, }; }; -type AssetCriticality = SnakeToCamelCase; -/** - * Create asset criticality - */ -export const createAssetCriticality = async ( - params: Pick -): Promise => { - return KibanaServices.get().http.fetch(ASSET_CRITICALITY_URL, { - version: '1', - method: 'POST', - body: JSON.stringify({ - id_value: params.idValue, - id_field: params.idField, - criticality_level: params.criticalityLevel, - }), - }); -}; - -/** - * Get asset criticality - */ -export const fetchAssetCriticality = async ( - params: Pick -): Promise => { - return KibanaServices.get().http.fetch(ASSET_CRITICALITY_URL, { - version: '1', - method: 'GET', - query: { id_value: params.idValue, id_field: params.idField }, - }); -}; +export type AssetCriticality = SnakeToCamelCase; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/common/utils.ts b/x-pack/plugins/security_solution/public/entity_analytics/common/utils.ts index 361d6d133a93d..3f67bea85dbcd 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/common/utils.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/common/utils.ts @@ -32,3 +32,20 @@ export const RISK_SCORE_RANGES = { [RiskSeverity.high]: { start: 70, stop: 90 }, [RiskSeverity.critical]: { start: 90, stop: 100 }, }; + +type SnakeToCamelCaseString = S extends `${infer T}_${infer U}` + ? `${T}${Capitalize>}` + : S; + +type SnakeToCamelCaseArray = T extends Array + ? Array> + : T; + +// TODO #173073 @tiansivive Add to utilities in `packages/kbn-utility-types` +export type SnakeToCamelCase = T extends Record + ? { + [K in keyof T as SnakeToCamelCaseString]: SnakeToCamelCase; + } + : T extends unknown[] + ? SnakeToCamelCaseArray + : T; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/asset_criticality_selector.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/asset_criticality_selector.tsx new file mode 100644 index 0000000000000..3f9b1fb7ee2b9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/asset_criticality_selector.tsx @@ -0,0 +1,203 @@ +/* + * 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 { EuiSuperSelectOption } from '@elastic/eui'; +import { + EuiAccordion, + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiHealth, + EuiLoadingSpinner, + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, + EuiSuperSelect, + EuiText, +} from '@elastic/eui'; + +import React, { useState } from 'react'; + +import { FormattedMessage } from '@kbn/i18n-react'; + +import { + CRITICALITY_LEVEL_DESCRIPTION, + CRITICALITY_LEVEL_TITLE, + PICK_ASSET_CRITICALITY, +} from './translations'; +import type { Entity, ModalState, State } from './use_asset_criticality'; +import { useAssetCriticalityData, useCriticalityModal } from './use_asset_criticality'; +import type { CriticalityLevel } from './common'; +import { CRITICALITY_LEVEL_COLOR } from './common'; + +interface Props { + entity: Entity; +} +export const AssetCriticalitySelector: React.FC = ({ entity }) => { + const modal = useCriticalityModal(); + const criticality = useAssetCriticalityData(entity, modal); + + if (criticality.privileges.isLoading || !criticality.privileges.data?.has_all_required) { + return null; + } + + return ( + <> + + } + data-test-subj="asset-criticality-selector" + > + {criticality.query.isLoading || criticality.mutation.isLoading ? ( + + ) : ( + + + + {criticality.status === 'update' && criticality.query.data?.criticality_level ? ( + + {CRITICALITY_LEVEL_TITLE[criticality.query.data.criticality_level]} + + ) : ( + + + + )} + + + + modal.toggle(true)} + > + {criticality.status === 'update' ? ( + + ) : ( + + )} + + + + )} + + {modal.visible ? ( + + ) : null} + + ); +}; + +interface ModalProps { + criticality: State; + modal: ModalState; + entity: Entity; +} +const AssetCriticalityModal: React.FC = ({ criticality, modal, entity }) => { + const [value, setNewValue] = useState( + criticality.query.data?.criticality_level ?? 'normal' + ); + + return ( + modal.toggle(false)}> + + + {PICK_ASSET_CRITICALITY} + + + + + + + modal.toggle(false)}> + + + + + criticality.mutation.mutate({ + criticalityLevel: value, + idField: `${entity.type}.name`, + idValue: entity.name, + }) + } + fill + data-test-subj="asset-criticality-modal-save-btn" + > + + + + + ); +}; + +const option = (level: CriticalityLevel): EuiSuperSelectOption => ({ + value: level, + dropdownDisplay: ( + + {CRITICALITY_LEVEL_TITLE[level]} + +

{CRITICALITY_LEVEL_DESCRIPTION[level]}

+
+
+ ), + inputDisplay: ( + + {CRITICALITY_LEVEL_TITLE[level]} + + ), +}); +const options: Array> = [ + option('normal'), + option('not_important'), + option('important'), + option('very_important'), +]; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/common.ts b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/common.ts new file mode 100644 index 0000000000000..0c3e16c0e77a2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/common.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { euiLightVars } from '@kbn/ui-theme'; +import type { AssetCriticalityRecord } from '../../../../common/api/entity_analytics/asset_criticality'; + +export type CriticalityLevel = AssetCriticalityRecord['criticality_level']; + +export const CRITICALITY_LEVEL_COLOR: Record = { + very_important: '#E7664C', + important: '#D6BF57', + normal: '#54B399', + not_important: euiLightVars.euiColorMediumShade, +}; + +// SUGGESTION: @tiansivive Move this to some more general place within Entity Analytics +export const buildCriticalityQueryKeys = (id: string) => { + const ASSET_CRITICALITY = 'ASSET_CRITICALITY'; + const PRIVILEGES = 'PRIVILEGES'; + return { + doc: [ASSET_CRITICALITY, id], + privileges: [ASSET_CRITICALITY, PRIVILEGES, id], + }; +}; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/translations.ts b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/translations.ts new file mode 100644 index 0000000000000..bb4d4f51ace09 --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/translations.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import type { CriticalityLevel } from './common'; + +export const PICK_ASSET_CRITICALITY = i18n.translate( + 'xpack.securitySolution.entityAnalytics.assetCriticality.pickerText', + { + defaultMessage: 'Pick asset criticality level', + } +); + +export const CRITICALITY_LEVEL_TITLE: Record = { + normal: i18n.translate( + 'xpack.securitySolution.entityAnalytics.assetCriticality.levelTitle.normal', + { + defaultMessage: 'Normal', + } + ), + not_important: i18n.translate( + 'xpack.securitySolution.entityAnalytics.assetCriticality.levelTitle.notImportant', + { + defaultMessage: 'Not important', + } + ), + important: i18n.translate( + 'xpack.securitySolution.entityAnalytics.assetCriticality.levelTitle.important', + { + defaultMessage: 'Important', + } + ), + very_important: i18n.translate( + 'xpack.securitySolution.entityAnalytics.assetCriticality.levelTitle.veryImportant', + { + defaultMessage: 'Very important', + } + ), +}; +export const CRITICALITY_LEVEL_DESCRIPTION: Record = { + normal: i18n.translate( + 'xpack.securitySolution.entityAnalytics.assetCriticality.levelDescription.normal', + { + defaultMessage: 'Entity risk score rises at normal speed', + } + ), + not_important: i18n.translate( + 'xpack.securitySolution.entityAnalytics.assetCriticality.levelDescription.notImportant', + { + defaultMessage: 'Entity risk score rises slower', + } + ), + important: i18n.translate( + 'xpack.securitySolution.entityAnalytics.assetCriticality.levelDescription.important', + { + defaultMessage: 'Entity risk score rises faster', + } + ), + very_important: i18n.translate( + 'xpack.securitySolution.entityAnalytics.assetCriticality.levelDescription.veryImportant', + { + defaultMessage: 'Entity risk score rises much faster', + } + ), +}; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/use_asset_criticality.ts b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/use_asset_criticality.ts new file mode 100644 index 0000000000000..669bc878b18a5 --- /dev/null +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/use_asset_criticality.ts @@ -0,0 +1,76 @@ +/* + * 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 { useGeneratedHtmlId } from '@elastic/eui'; +import type { UseMutationResult, UseQueryResult } from '@tanstack/react-query'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; + +import { useToggle } from 'react-use'; +import type { AssetCriticalityRecord } from '../../../../common/api/entity_analytics/asset_criticality'; +import type { EntityAnalyticsPrivileges } from '../../../../common/api/entity_analytics/common'; +import type { AssetCriticality } from '../../api/api'; +import { useEntityAnalyticsRoutes } from '../../api/api'; +import { buildCriticalityQueryKeys } from './common'; + +export const useAssetCriticalityData = (entity: Entity, modal: ModalState): State => { + const QC = useQueryClient(); + const QUERY_KEYS = buildCriticalityQueryKeys(entity.name); + + const { fetchAssetCriticality, createAssetCriticality, fetchAssetCriticalityPrivileges } = + useEntityAnalyticsRoutes(); + + const privileges = useQuery({ + queryKey: QUERY_KEYS.privileges, + queryFn: fetchAssetCriticalityPrivileges, + }); + const query = useQuery({ + queryKey: QUERY_KEYS.doc, + queryFn: () => fetchAssetCriticality({ idField: `${entity.type}.name`, idValue: entity.name }), + retry: (failureCount, error) => error.body.statusCode === 404 && failureCount > 0, + enabled: privileges.data?.has_all_required, + }); + + const mutation = useMutation({ + mutationFn: createAssetCriticality, + onSuccess: (data) => { + QC.setQueryData(QUERY_KEYS.doc, data); + modal.toggle(false); + }, + }); + + return { + status: query.isError && query.error.body.statusCode === 404 ? 'create' : 'update', + query, + mutation, + privileges, + }; +}; + +export const useCriticalityModal = () => { + const [visible, toggle] = useToggle(false); + const basicSelectId = useGeneratedHtmlId({ prefix: 'basicSelect' }); + return { visible, toggle, basicSelectId }; +}; + +export interface State { + status: 'create' | 'update'; + query: UseQueryResult; + privileges: UseQueryResult; + mutation: UseMutationResult; +} +type Params = Pick; + +export interface ModalState { + basicSelectId: string; + visible: boolean; + toggle: (next: boolean) => void; +} + +export interface Entity { + name: string; + type: 'host' | 'user'; +} diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_selector.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_selector.tsx deleted file mode 100644 index ac3ea9b0252e8..0000000000000 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_selector.tsx +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { EuiSuperSelectOption } from '@elastic/eui'; -import { - EuiAccordion, - EuiButton, - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiHealth, - EuiLoadingSpinner, - EuiModal, - EuiModalBody, - EuiModalFooter, - EuiModalHeader, - EuiModalHeaderTitle, - EuiSuperSelect, - EuiText, - useGeneratedHtmlId, -} from '@elastic/eui'; - -import type { UseMutationResult, UseQueryResult } from '@tanstack/react-query'; -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; - -import React, { useState } from 'react'; -import { useToggle } from 'react-use'; -import { - ASSET_CRITICALITY_OPTION_TEXT, - CREATE_ASSET_CRITICALITY, - CRITICALITY_LEVEL_COLOR, - PICK_ASSET_CRITICALITY, -} from '../../../common/asset_criticality'; -import type { EntityAnalyticsPrivileges } from '../../../common/api/entity_analytics/common'; -import type { AssetCriticalityRecord } from '../../../common/api/entity_analytics/asset_criticality'; - -import { - createAssetCriticality, - fetchAssetCriticality, - fetchAssetCriticalityPrivileges, -} from '../api/api'; - -interface Props { - entity: Entity; -} -export const AssetCriticalitySelector: React.FC = ({ entity }) => { - const criticality = useAssetCriticality(entity); - - if (criticality.privileges.isLoading || !criticality.privileges.data?.has_all_required) { - return null; - } - - return ( - <> - - {criticality.query.isLoading || criticality.mutation.isLoading ? ( - - ) : ( - - - - {criticality.status === 'update' && criticality.query.data?.criticality_level ? ( - - {criticalityDisplayText[criticality.query.data.criticality_level]} - - ) : ( - {CREATE_ASSET_CRITICALITY} - )} - - - - - {criticality.status === 'update' ? 'Change' : 'Create'} - - - - )} - - {criticality.modal.visible ? ( - - ) : null} - - ); -}; - -interface ModalProps { - criticality: State; - entity: Entity; -} -const AssetCriticalityModal: React.FC = ({ criticality, entity }) => { - const basicSelectId = useGeneratedHtmlId({ prefix: 'basicSelect' }); - const [value, setNewValue] = useState( - criticality.query.data?.criticality_level ?? 'normal' - ); - - return ( - - - - {PICK_ASSET_CRITICALITY} - - - - - - - {'Cancel'} - - - criticality.mutation.mutate({ - criticalityLevel: value, - idField: `${entity.type}.name`, - idValue: entity.name, - }) - } - fill - data-test-subj="asset-criticality-modal-save-btn" - > - {'Save'} - - - - ); -}; - -const useAssetCriticality = (entity: Entity): State => { - const [visible, toggleModal] = useToggle(false); - const modal = { visible, toggle: (next: boolean) => () => toggleModal(next) }; - - const QC = useQueryClient(); - - const privileges = useQuery({ - queryKey: ['ASSET_CRITICALITY', 'PRIVILEGES', entity.name], - queryFn: fetchAssetCriticalityPrivileges, - }); - const query = useQuery({ - queryKey: ['ASSET_CRITICALITY', entity.name], - queryFn: () => fetchAssetCriticality({ idField: `${entity.type}.name`, idValue: entity.name }), - retry: (failureCount, error) => error.body.statusCode === 404 && failureCount > 0, - enabled: privileges.data?.has_all_required, - }); - - const mutation = useMutation({ - mutationFn: createAssetCriticality, - onSuccess: (data) => { - QC.setQueryData(['ASSET_CRITICALITY', entity.name], data); - toggleModal(false); - }, - }); - - return { - status: query.isError && query.error.body.statusCode === 404 ? 'create' : 'update', - query, - mutation, - modal, - privileges, - }; -}; - -interface State { - status: 'create' | 'update'; - query: UseQueryResult; - privileges: UseQueryResult; - mutation: UseMutationResult; - modal: ModalState; -} -type Params = Parameters[0]; - -interface ModalState { - visible: boolean; - toggle: (next: boolean) => () => void; -} - -interface Entity { - name: string; - type: 'host' | 'user'; -} - -const criticalityDisplayText: Record = { - normal: 'Normal', - not_important: 'Not important', - important: 'Important', - very_important: 'Very important', -}; - -const option = ( - level: AssetCriticalityRecord['criticality_level'] -): EuiSuperSelectOption => ({ - value: level, - dropdownDisplay: ( - - {criticalityDisplayText[level]} - -

{ASSET_CRITICALITY_OPTION_TEXT[level]}

-
-
- ), - inputDisplay: ( - - {criticalityDisplayText[level]} - - ), -}); -const options: Array> = [ - option('normal'), - option('not_important'), - option('important'), - option('very_important'), -]; diff --git a/x-pack/plugins/security_solution/public/overview/components/host_overview/index.tsx b/x-pack/plugins/security_solution/public/overview/components/host_overview/index.tsx index 686828412977a..d4a4a710741b6 100644 --- a/x-pack/plugins/security_solution/public/overview/components/host_overview/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/host_overview/index.tsx @@ -10,6 +10,7 @@ import { euiDarkVars as darkTheme, euiLightVars as lightTheme } from '@kbn/ui-th import { getOr } from 'lodash/fp'; import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; +import { AssetCriticalitySelector } from '../../../entity_analytics/components/asset_criticality/asset_criticality_selector'; import type { HostItem } from '../../../../common/search_strategy'; import { buildHostNamesFilter, RiskScoreEntity } from '../../../../common/search_strategy'; import { DEFAULT_DARK_MODE } from '../../../../common/constants'; @@ -283,6 +284,7 @@ export const HostOverview = React.memo( {!isInDetailsSidePanel && ( )} + {descriptionLists.map((descriptionList, index) => ( ))} diff --git a/x-pack/plugins/security_solution/public/overview/components/user_overview/index.tsx b/x-pack/plugins/security_solution/public/overview/components/user_overview/index.tsx index 446fe215a695a..0cad73ec6e5e8 100644 --- a/x-pack/plugins/security_solution/public/overview/components/user_overview/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/user_overview/index.tsx @@ -10,6 +10,7 @@ import { euiDarkVars as darkTheme, euiLightVars as lightTheme } from '@kbn/ui-th import { getOr } from 'lodash/fp'; import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; +import { AssetCriticalitySelector } from '../../../entity_analytics/components/asset_criticality/asset_criticality_selector'; import { buildUserNamesFilter, RiskScoreEntity } from '../../../../common/search_strategy'; import { DEFAULT_DARK_MODE } from '../../../../common/constants'; import type { DescriptionList } from '../../../../common/utility_types'; @@ -275,6 +276,7 @@ export const UserOverview = React.memo( {!isInDetailsSidePanel && ( )} + {descriptionLists.map((descriptionList, index) => ( ))} @@ -290,6 +292,7 @@ export const UserOverview = React.memo( )} + {isAuthorized && ( = React.memo( -
) : ( diff --git a/x-pack/test/security_solution_cypress/config.ts b/x-pack/test/security_solution_cypress/config.ts index 4fe61b660f1a4..dc17bf6644e34 100644 --- a/x-pack/test/security_solution_cypress/config.ts +++ b/x-pack/test/security_solution_cypress/config.ts @@ -47,6 +47,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { `--xpack.securitySolution.enableExperimental=${JSON.stringify([ 'chartEmbeddablesEnabled', 'alertSuppressionForThresholdRuleEnabled', + 'entityAnalyticsAssetCriticalityEnabled', ])}`, // mock cloud to enable the guided onboarding tour in e2e tests '--xpack.cloud.id=test', diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/host_details_right_panel.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/asset_criticality/host_flyout.cy.ts similarity index 61% rename from x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/host_details_right_panel.cy.ts rename to x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/asset_criticality/host_flyout.cy.ts index 2c543b0581cd8..75f2df0ee25d2 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/host_details_right_panel.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/asset_criticality/host_flyout.cy.ts @@ -5,30 +5,37 @@ * 2.0. */ +import { getNewRule } from '../../../objects/rule'; import { - HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_TITLE, - HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_BUTTON, - HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_SELECTOR, HOST_DETAILS_FLYOUT_SECTION_HEADER, + HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_SELECTOR, toggleAssetCriticalityAccordion, + HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_BUTTON, toggleAssetCriticalityModal, + HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_TITLE, + HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_SELECT, + HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_SELECT_OPTION, HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_SAVE_BTN, HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_LEVEL, - HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_DROPDOWN, -} from '../../../../screens/expandable_flyout/host_details_right_panel'; -import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; -import { expandFirstAlertHostExpandableFlyout } from '../../../../tasks/expandable_flyout/common'; - -import { login } from '../../../../tasks/login'; -import { visit } from '../../../../tasks/navigation'; -import { createRule } from '../../../../tasks/api_calls/rules'; -import { getNewRule } from '../../../../objects/rule'; -import { ALERTS_URL } from '../../../../urls/navigation'; -import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; +} from '../../../screens/expandable_flyout/host_details_right_panel'; +import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; +import { createRule } from '../../../tasks/api_calls/rules'; +import { waitForAlertsToPopulate } from '../../../tasks/create_new_rule'; +import { expandFirstAlertHostExpandableFlyout } from '../../../tasks/expandable_flyout/common'; +import { login } from '../../../tasks/login'; +import { visit } from '../../../tasks/navigation'; +import { ALERTS_URL } from '../../../urls/navigation'; describe( - 'Alert host details expandable flyout right panel', - { tags: ['@ess', '@serverless'] }, + 'Host details flyout', + { + tags: ['@ess', '@serverless'], + env: { + ftrConfig: { + enableExperimental: ['entityAnalyticsAssetCriticalityEnabled'], + }, + }, + }, () => { const rule = { ...getNewRule(), investigation_fields: { field_names: ['host.os.name'] } }; @@ -41,7 +48,7 @@ describe( expandFirstAlertHostExpandableFlyout(); }); - describe('Expandable flyout', () => { + describe('Host flyout', () => { it('should display header section', () => { cy.log('header and content'); @@ -57,7 +64,7 @@ describe( ); toggleAssetCriticalityAccordion(); - cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_BUTTON).should('have.text', 'Change'); + cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_BUTTON).should('have.text', 'Create'); }); it('should display asset criticality modal', () => { @@ -74,12 +81,16 @@ describe( cy.log('asset criticality update'); toggleAssetCriticalityModal(); - cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_DROPDOWN) - .should('be.visible') - .select('High'); + cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_SELECT).should('be.visible').click(); + + cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_SELECT_OPTION) + .contains('Important') + .click(); cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_SAVE_BTN).should('be.visible').click(); - cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_LEVEL).contains('High').should('be.visible'); + cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_LEVEL) + .contains('Important') + .should('be.visible'); }); }); } diff --git a/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/host_details_right_panel.ts b/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/host_details_right_panel.ts index 92c9eb238836e..f2530202cb2eb 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/host_details_right_panel.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/host_details_right_panel.ts @@ -11,15 +11,19 @@ export const HOST_DETAILS_FLYOUT_SECTION_HEADER = getDataTestSubjectSelector('ho export const HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_SELECTOR = getDataTestSubjectSelector( 'asset-criticality-selector' ); -export const HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_LEVEL = getDataTestSubjectSelector('risk-score'); +export const HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_LEVEL = + getDataTestSubjectSelector('asset-criticality-level'); export const HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_BUTTON = getDataTestSubjectSelector( 'asset-criticality-change-btn' ); export const HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_TITLE = getDataTestSubjectSelector( 'asset-criticality-modal-title' ); -export const HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_DROPDOWN = getDataTestSubjectSelector( - 'asset-criticality-modal-select-dropdown' +export const HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_SELECT = getDataTestSubjectSelector( + 'asset-criticality-modal-select' +); +export const HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_SELECT_OPTION = getDataTestSubjectSelector( + 'asset-criticality-modal-select-option' ); export const HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_SAVE_BTN = getDataTestSubjectSelector( 'asset-criticality-modal-save-btn' diff --git a/x-pack/test/security_solution_cypress/serverless_config.ts b/x-pack/test/security_solution_cypress/serverless_config.ts index 8eb8d2efdefdc..c2d680cb228ec 100644 --- a/x-pack/test/security_solution_cypress/serverless_config.ts +++ b/x-pack/test/security_solution_cypress/serverless_config.ts @@ -36,6 +36,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ])}`, `--xpack.securitySolution.enableExperimental=${JSON.stringify([ 'alertSuppressionForThresholdRuleEnabled', + 'entityAnalyticsAssetCriticalityEnabled', ])}`, ], }, From 1532e07bde2663653802e8e91cacb81307b55cb3 Mon Sep 17 00:00:00 2001 From: Tiago Vila Verde Date: Mon, 18 Dec 2023 12:08:24 +0100 Subject: [PATCH 09/13] Polishing UI --- .../asset_criticality_selector.tsx | 5 ++- .../components/host_overview/index.tsx | 2 - .../components/user_overview/index.tsx | 3 -- .../side_panel/host_details/index.tsx | 7 +++- .../user_details/user_details_flyout.tsx | 7 +++- .../asset_criticality/host_flyout.cy.ts | 14 ++++--- .../cypress/screens/alerts.ts | 2 - .../flyouts.ts} | 12 +----- .../cypress/tasks/asset_criticality/common.ts | 42 +++++++++++++++++++ .../cypress/tasks/expandable_flyout/common.ts | 9 +--- 10 files changed, 67 insertions(+), 36 deletions(-) rename x-pack/test/security_solution_cypress/cypress/screens/{expandable_flyout/host_details_right_panel.ts => asset_criticality/flyouts.ts} (76%) create mode 100644 x-pack/test/security_solution_cypress/cypress/tasks/asset_criticality/common.ts diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/asset_criticality_selector.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/asset_criticality_selector.tsx index 3f9b1fb7ee2b9..c6d80033aba6a 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/asset_criticality_selector.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/asset_criticality_selector.tsx @@ -6,6 +6,7 @@ */ import type { EuiSuperSelectOption } from '@elastic/eui'; + import { EuiAccordion, EuiButton, @@ -66,7 +67,7 @@ export const AssetCriticalitySelector: React.FC = ({ entity }) => { @@ -88,7 +89,7 @@ export const AssetCriticalitySelector: React.FC = ({ entity }) => { )} - + ( {!isInDetailsSidePanel && ( )} - {descriptionLists.map((descriptionList, index) => ( ))} diff --git a/x-pack/plugins/security_solution/public/overview/components/user_overview/index.tsx b/x-pack/plugins/security_solution/public/overview/components/user_overview/index.tsx index 0cad73ec6e5e8..446fe215a695a 100644 --- a/x-pack/plugins/security_solution/public/overview/components/user_overview/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/user_overview/index.tsx @@ -10,7 +10,6 @@ import { euiDarkVars as darkTheme, euiLightVars as lightTheme } from '@kbn/ui-th import { getOr } from 'lodash/fp'; import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; -import { AssetCriticalitySelector } from '../../../entity_analytics/components/asset_criticality/asset_criticality_selector'; import { buildUserNamesFilter, RiskScoreEntity } from '../../../../common/search_strategy'; import { DEFAULT_DARK_MODE } from '../../../../common/constants'; import type { DescriptionList } from '../../../../common/utility_types'; @@ -276,7 +275,6 @@ export const UserOverview = React.memo( {!isInDetailsSidePanel && ( )} - {descriptionLists.map((descriptionList, index) => ( ))} @@ -292,7 +290,6 @@ export const UserOverview = React.memo( )} - {isAuthorized && ( = React.memo( - + + + - ) : ( diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/user_details_flyout.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/user_details_flyout.tsx index 9bc87bdc49751..750a7b05b26aa 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/user_details_flyout.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/user_details_flyout.tsx @@ -5,9 +5,10 @@ * 2.0. */ -import { EuiFlyoutHeader, EuiFlyoutBody, EuiSpacer } from '@elastic/eui'; +import { EuiFlyoutHeader, EuiFlyoutBody, EuiSpacer, EuiHorizontalRule } from '@elastic/eui'; import React from 'react'; import styled from 'styled-components'; +import { AssetCriticalitySelector } from '../../../../entity_analytics/components/asset_criticality/asset_criticality_selector'; import { ExpandableUserDetailsTitle, ExpandableUserDetailsPageLink, @@ -43,7 +44,9 @@ export const UserDetailsFlyout = ({ - + + + diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/asset_criticality/host_flyout.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/asset_criticality/host_flyout.cy.ts index 75f2df0ee25d2..8052430541c0e 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/asset_criticality/host_flyout.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/asset_criticality/host_flyout.cy.ts @@ -9,19 +9,21 @@ import { getNewRule } from '../../../objects/rule'; import { HOST_DETAILS_FLYOUT_SECTION_HEADER, HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_SELECTOR, - toggleAssetCriticalityAccordion, HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_BUTTON, - toggleAssetCriticalityModal, HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_TITLE, HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_SELECT, HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_SELECT_OPTION, HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_SAVE_BTN, HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_LEVEL, -} from '../../../screens/expandable_flyout/host_details_right_panel'; +} from '../../../screens/asset_criticality/flyouts'; import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; +import { + expandFirstAlertHostFlyout, + toggleAssetCriticalityAccordion, + toggleAssetCriticalityModal, +} from '../../../tasks/asset_criticality/common'; import { createRule } from '../../../tasks/api_calls/rules'; import { waitForAlertsToPopulate } from '../../../tasks/create_new_rule'; -import { expandFirstAlertHostExpandableFlyout } from '../../../tasks/expandable_flyout/common'; import { login } from '../../../tasks/login'; import { visit } from '../../../tasks/navigation'; import { ALERTS_URL } from '../../../urls/navigation'; @@ -45,10 +47,12 @@ describe( createRule(rule); visit(ALERTS_URL); waitForAlertsToPopulate(); - expandFirstAlertHostExpandableFlyout(); }); describe('Host flyout', () => { + beforeEach(() => { + expandFirstAlertHostFlyout(); + }); it('should display header section', () => { cy.log('header and content'); diff --git a/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts b/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts index f07c6fe303082..83de06466f251 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/alerts.ts @@ -93,8 +93,6 @@ export const ATTACH_TO_NEW_CASE_BUTTON = '[data-test-subj="add-to-new-case-actio export const USER_COLUMN = '[data-gridcell-column-id="user.name"]'; -export const OPEN_HOST_FLYOUT_BUTTON = '[data-test-subj="host-details-button"]'; - export const HOST_RISK_HEADER_COLUMN = '[data-test-subj="dataGridHeaderCell-host.risk.calculated_level"]'; diff --git a/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/host_details_right_panel.ts b/x-pack/test/security_solution_cypress/cypress/screens/asset_criticality/flyouts.ts similarity index 76% rename from x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/host_details_right_panel.ts rename to x-pack/test/security_solution_cypress/cypress/screens/asset_criticality/flyouts.ts index f2530202cb2eb..7fbfb5f3a03e0 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/host_details_right_panel.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/asset_criticality/flyouts.ts @@ -29,13 +29,5 @@ export const HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_SAVE_BTN = getDataTestS 'asset-criticality-modal-save-btn' ); -export const toggleAssetCriticalityAccordion = () => { - cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_SELECTOR).scrollIntoView(); - cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_SELECTOR).should('be.visible').click(); -}; - -export const toggleAssetCriticalityModal = () => { - toggleAssetCriticalityAccordion(); - - cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_BUTTON).should('be.visible').click(); -}; +export const OPEN_HOST_FLYOUT_BUTTON = getDataTestSubjectSelector('host-details-button'); +export const OPEN_USER_FLYOUT_BUTTON = getDataTestSubjectSelector('user-details-button'); diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/asset_criticality/common.ts b/x-pack/test/security_solution_cypress/cypress/tasks/asset_criticality/common.ts new file mode 100644 index 0000000000000..159908ba589db --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/tasks/asset_criticality/common.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + OPEN_HOST_FLYOUT_BUTTON, + OPEN_USER_FLYOUT_BUTTON, +} from '../../screens/asset_criticality/flyouts'; + +/** + * Find the first alert row in the alerts table then click on the host name to open the flyout + */ +export const expandFirstAlertHostFlyout = () => { + cy.get(OPEN_HOST_FLYOUT_BUTTON).first().click(); +}; + +/** + * Find the first alert row in the alerts table then click on the host name to open the flyout + */ +export const expandFirstAlertUserFlyout = () => { + cy.get(OPEN_USER_FLYOUT_BUTTON).first().click(); +}; + +/** + * Open the asset criticality accordion + */ +export const toggleAssetCriticalityAccordion = () => { + cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_SELECTOR).scrollIntoView(); + cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_SELECTOR).should('be.visible').click(); +}; + +/** + * Open the asset criticality modal + */ +export const toggleAssetCriticalityModal = () => { + toggleAssetCriticalityAccordion(); + + cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_BUTTON).should('be.visible').click(); +}; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout/common.ts b/x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout/common.ts index 187793d206422..cd244f192bd52 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout/common.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout/common.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { EXPAND_ALERT_BTN, OPEN_HOST_FLYOUT_BUTTON } from '../../screens/alerts'; +import { EXPAND_ALERT_BTN } from '../../screens/alerts'; import { DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE } from '../../screens/expandable_flyout/alert_details_right_panel'; import { DOCUMENT_DETAILS_FLYOUT_FOOTER_ADD_TO_NEW_CASE_CREATE_BUTTON, @@ -23,13 +23,6 @@ export const expandFirstAlertExpandableFlyout = () => { cy.get(EXPAND_ALERT_BTN).first().click(); }; -/** - * Find the first alert row in the alerts table then click on the host name to open the flyout - */ -export const expandFirstAlertHostExpandableFlyout = () => { - cy.get(OPEN_HOST_FLYOUT_BUTTON).first().click(); -}; - /** * create a new case from the expanded expandable flyout */ From 2374e50576c41b89ebf3a6148066611f443d52bf Mon Sep 17 00:00:00 2001 From: Tiago Vila Verde Date: Mon, 18 Dec 2023 15:36:07 +0100 Subject: [PATCH 10/13] review --- .../asset_criticality/asset_criticality_selector.tsx | 3 +++ .../timelines/components/side_panel/host_details/index.tsx | 3 --- .../side_panel/user_details/user_details_flyout.tsx | 4 +--- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/asset_criticality_selector.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/asset_criticality_selector.tsx index c6d80033aba6a..f2a4c4c3a3288 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/asset_criticality_selector.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/asset_criticality_selector.tsx @@ -22,6 +22,7 @@ import { EuiModalHeaderTitle, EuiSuperSelect, EuiText, + EuiHorizontalRule, } from '@elastic/eui'; import React, { useState } from 'react'; @@ -51,6 +52,7 @@ export const AssetCriticalitySelector: React.FC = ({ entity }) => { return ( <> + = ({ entity }) => { )} + {modal.visible ? ( ) : null} diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/index.tsx index 929c2b8a56cb5..981e81e71491d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/index.tsx @@ -12,7 +12,6 @@ import { EuiFlexItem, EuiButtonIcon, EuiSpacer, - EuiHorizontalRule, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; @@ -82,9 +81,7 @@ export const HostDetailsPanel: React.FC = React.memo( - - diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/user_details_flyout.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/user_details_flyout.tsx index 750a7b05b26aa..e2963c2e3ca3c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/user_details_flyout.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/user_details_flyout.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiFlyoutHeader, EuiFlyoutBody, EuiSpacer, EuiHorizontalRule } from '@elastic/eui'; +import { EuiFlyoutHeader, EuiFlyoutBody, EuiSpacer } from '@elastic/eui'; import React from 'react'; import styled from 'styled-components'; import { AssetCriticalitySelector } from '../../../../entity_analytics/components/asset_criticality/asset_criticality_selector'; @@ -44,9 +44,7 @@ export const UserDetailsFlyout = ({ - - From ec2c1da45b3f1dbcc5148db7b30734370bb3daae Mon Sep 17 00:00:00 2001 From: Tiago Vila Verde Date: Mon, 18 Dec 2023 15:48:19 +0100 Subject: [PATCH 11/13] test fix --- .../cypress/tasks/asset_criticality/common.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/asset_criticality/common.ts b/x-pack/test/security_solution_cypress/cypress/tasks/asset_criticality/common.ts index 159908ba589db..0824885eecc12 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/asset_criticality/common.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/asset_criticality/common.ts @@ -8,6 +8,8 @@ import { OPEN_HOST_FLYOUT_BUTTON, OPEN_USER_FLYOUT_BUTTON, + HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_SELECTOR, + HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_BUTTON, } from '../../screens/asset_criticality/flyouts'; /** From 5740116f30c07fa16870c09b94ec4037fd0fedca Mon Sep 17 00:00:00 2001 From: Tiago Vila Verde Date: Wed, 20 Dec 2023 17:30:24 +0100 Subject: [PATCH 12/13] allowing feature flag in cypress tests --- .../entity_analytics/asset_criticality/host_flyout.cy.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/asset_criticality/host_flyout.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/asset_criticality/host_flyout.cy.ts index 8052430541c0e..15ca0cfbabe02 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/asset_criticality/host_flyout.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/asset_criticality/host_flyout.cy.ts @@ -34,7 +34,11 @@ describe( tags: ['@ess', '@serverless'], env: { ftrConfig: { - enableExperimental: ['entityAnalyticsAssetCriticalityEnabled'], + kbnServerArgs: [ + `--xpack.securitySolution.enableExperimental=${JSON.stringify([ + 'entityAnalyticsAssetCriticalityEnabled', + ])}`, + ], }, }, }, From 55067cd970ede3ad0267faa5950f68f165d3f288 Mon Sep 17 00:00:00 2001 From: Tiago Vila Verde Date: Wed, 20 Dec 2023 17:30:24 +0100 Subject: [PATCH 13/13] allowing feature flag in cypress tests --- .../asset_criticality/host_flyout.cy.ts | 17 +++-------------- .../cypress/tasks/asset_criticality/common.ts | 18 +++++++++++++++--- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/asset_criticality/host_flyout.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/asset_criticality/host_flyout.cy.ts index 15ca0cfbabe02..2ec2e40cb72ac 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/asset_criticality/host_flyout.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/asset_criticality/host_flyout.cy.ts @@ -11,14 +11,12 @@ import { HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_SELECTOR, HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_BUTTON, HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_TITLE, - HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_SELECT, - HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_SELECT_OPTION, - HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_SAVE_BTN, HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_LEVEL, } from '../../../screens/asset_criticality/flyouts'; import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; import { expandFirstAlertHostFlyout, + selectAssetCriticalityLevel, toggleAssetCriticalityAccordion, toggleAssetCriticalityModal, } from '../../../tasks/asset_criticality/common'; @@ -51,12 +49,10 @@ describe( createRule(rule); visit(ALERTS_URL); waitForAlertsToPopulate(); + expandFirstAlertHostFlyout(); }); describe('Host flyout', () => { - beforeEach(() => { - expandFirstAlertHostFlyout(); - }); it('should display header section', () => { cy.log('header and content'); @@ -88,14 +84,7 @@ describe( it('should update asset criticality state', () => { cy.log('asset criticality update'); - toggleAssetCriticalityModal(); - cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_SELECT).should('be.visible').click(); - - cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_SELECT_OPTION) - .contains('Important') - .click(); - - cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_SAVE_BTN).should('be.visible').click(); + selectAssetCriticalityLevel('Important'); cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_LEVEL) .contains('Important') .should('be.visible'); diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/asset_criticality/common.ts b/x-pack/test/security_solution_cypress/cypress/tasks/asset_criticality/common.ts index 0824885eecc12..79979c8a33016 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/asset_criticality/common.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/asset_criticality/common.ts @@ -10,6 +10,9 @@ import { OPEN_USER_FLYOUT_BUTTON, HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_SELECTOR, HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_BUTTON, + HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_SELECT, + HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_SELECT_OPTION, + HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_SAVE_BTN, } from '../../screens/asset_criticality/flyouts'; /** @@ -30,8 +33,7 @@ export const expandFirstAlertUserFlyout = () => { * Open the asset criticality accordion */ export const toggleAssetCriticalityAccordion = () => { - cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_SELECTOR).scrollIntoView(); - cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_SELECTOR).should('be.visible').click(); + cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_SELECTOR).click(); }; /** @@ -39,6 +41,16 @@ export const toggleAssetCriticalityAccordion = () => { */ export const toggleAssetCriticalityModal = () => { toggleAssetCriticalityAccordion(); + cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_BUTTON).click(); +}; - cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_BUTTON).should('be.visible').click(); +/** + * Open the asset criticality modal + */ +export const selectAssetCriticalityLevel = (option: string) => { + toggleAssetCriticalityAccordion(); + toggleAssetCriticalityModal(); + cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_SELECT).click(); + cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_SELECT_OPTION).contains(option).click(); + cy.get(HOST_DETAILS_FLYOUT_ASSET_CRITICALITY_MODAL_SAVE_BTN).click(); };