diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 89e4a2c008727..684f2ffd3b548 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1283,7 +1283,11 @@ x-pack/plugins/security_solution/server/lib/telemetry/ @elastic/security-data-an ## Security Solution sub teams - security-engineering-productivity x-pack/test/security_solution_cypress/cypress/README.md @elastic/security-engineering-productivity -x-pack/test/security_solution_cypress @elastic/security-engineering-productivity +x-pack/test/security_solution_cypress/es_archives @elastic/security-engineering-productivity +x-pack/test/security_solution_cypress/cli_config.ts @elastic/security-engineering-productivity +x-pack/test/security_solution_cypress/config.ts @elastic/security-engineering-productivity +x-pack/test/security_solution_cypress/runner.ts @elastic/security-engineering-productivity +x-pack/test/security_solution_cypress/serverless_config.ts @elastic/security-engineering-productivity ## Security Solution sub teams - adaptive-workload-protection x-pack/plugins/security_solution/public/common/components/sessions_viewer @elastic/sec-cloudnative-integrations diff --git a/x-pack/plugins/cases/public/components/description/index.test.tsx b/x-pack/plugins/cases/public/components/description/index.test.tsx index f519c7af3eb19..e077c0896d67d 100644 --- a/x-pack/plugins/cases/public/components/description/index.test.tsx +++ b/x-pack/plugins/cases/public/components/description/index.test.tsx @@ -27,7 +27,13 @@ const defaultProps = { isLoadingDescription: false, }; -describe('Description', () => { +// FLAKY: https://github.com/elastic/kibana/issues/164049 +// FLAKY: https://github.com/elastic/kibana/issues/164048 +// FLAKY: https://github.com/elastic/kibana/issues/164047 +// FLAKY: https://github.com/elastic/kibana/issues/164046 +// FLAKY: https://github.com/elastic/kibana/issues/164045 +// FLAKY: https://github.com/elastic/kibana/issues/164044 +describe.skip('Description', () => { const onUpdateField = jest.fn(); let appMockRender: AppMockRenderer; @@ -198,7 +204,8 @@ describe('Description', () => { expect(screen.queryByTestId('description-edit-icon')).not.toBeInTheDocument(); }); - describe('draft message', () => { + // FLAKY: https://github.com/elastic/kibana/issues/164050 + describe.skip('draft message', () => { const draftStorageKey = `cases.testAppId.basic-case-id.description.markdownEditor`; beforeEach(() => { diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details_alerts_table.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details_alerts_table.tsx index 79ce5e8f4bf0d..28d05d3a08d70 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details_alerts_table.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details_alerts_table.tsx @@ -10,6 +10,7 @@ import { type Criteria, EuiBasicTable, formatDate, EuiEmptyPrompt } from '@elast import { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; import { isRight } from 'fp-ts/lib/Either'; +import { ALERT_REASON, ALERT_RULE_NAME } from '@kbn/rule-data-utils'; import { SeverityBadge } from '../../../detections/components/rules/severity_badge'; import { usePaginatedAlerts } from '../hooks/use_paginated_alerts'; import { ERROR_MESSAGE, ERROR_TITLE } from '../../shared/translations'; @@ -26,12 +27,12 @@ export const columns = [ render: (value: string) => formatDate(value, TIMESTAMP_DATE_FORMAT), }, { - field: 'kibana.alert.rule.name', + field: ALERT_RULE_NAME, name: i18n.CORRELATIONS_RULE_COLUMN_TITLE, truncateText: true, }, { - field: 'kibana.alert.reason', + field: ALERT_REASON, name: i18n.CORRELATIONS_REASON_COLUMN_TITLE, truncateText: true, }, diff --git a/x-pack/plugins/security_solution/public/flyout/preview/components/alert_reason_preview.test.tsx b/x-pack/plugins/security_solution/public/flyout/preview/components/alert_reason_preview.test.tsx new file mode 100644 index 0000000000000..30076fd3ca1d2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/preview/components/alert_reason_preview.test.tsx @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { PreviewPanelContext } from '../context'; +import { mockContextValue } from '../mocks/mock_preview_panel_context'; +import { ALERT_REASON_PREVIEW_BODY_TEST_ID } from './test_ids'; +import { AlertReasonPreview } from './alert_reason_preview'; +import { ThemeProvider } from 'styled-components'; +import { getMockTheme } from '../../../common/lib/kibana/kibana_react.mock'; + +const mockTheme = getMockTheme({ eui: { euiFontSizeXS: '' } }); + +const panelContextValue = { + ...mockContextValue, +}; + +describe('', () => { + it('should render alert reason preview', () => { + const { getByTestId } = render( + + + + + + ); + expect(getByTestId(ALERT_REASON_PREVIEW_BODY_TEST_ID)).toBeInTheDocument(); + }); + + it('should render null is dataAsNestedObject is null', () => { + const contextValue = { + ...mockContextValue, + dataAsNestedObject: null, + }; + const { queryByTestId } = render( + + + + ); + expect(queryByTestId(ALERT_REASON_PREVIEW_BODY_TEST_ID)).not.toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/preview/components/alert_reason_preview.tsx b/x-pack/plugins/security_solution/public/flyout/preview/components/alert_reason_preview.tsx new file mode 100644 index 0000000000000..4fd912cbfbeec --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/preview/components/alert_reason_preview.tsx @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo } from 'react'; +import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; +import { ALERT_REASON_TITLE } from './translations'; +import { ALERT_REASON_PREVIEW_BODY_TEST_ID } from './test_ids'; +import { usePreviewPanelContext } from '../context'; +import { getRowRenderer } from '../../../timelines/components/timeline/body/renderers/get_row_renderer'; +import { defaultRowRenderers } from '../../../timelines/components/timeline/body/renderers'; + +/** + * Alert reason renderer on a preview panel on top of the right section of expandable flyout + */ +export const AlertReasonPreview: React.FC = () => { + const { dataAsNestedObject } = usePreviewPanelContext(); + + const renderer = useMemo( + () => + dataAsNestedObject != null + ? getRowRenderer({ data: dataAsNestedObject, rowRenderers: defaultRowRenderers }) + : null, + [dataAsNestedObject] + ); + + if (!dataAsNestedObject || !renderer) { + return null; + } + + return ( + + +
{ALERT_REASON_TITLE}
+
+ + {renderer.renderRow({ + contextId: 'event-details', + data: dataAsNestedObject, + isDraggable: false, + scopeId: 'global', + })} +
+ ); +}; + +AlertReasonPreview.displayName = 'AlertReasonPreview'; diff --git a/x-pack/plugins/security_solution/public/flyout/preview/components/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/preview/components/test_ids.ts index 1c27fd7472fca..764ec90fd9bdf 100644 --- a/x-pack/plugins/security_solution/public/flyout/preview/components/test_ids.ts +++ b/x-pack/plugins/security_solution/public/flyout/preview/components/test_ids.ts @@ -38,3 +38,5 @@ export const RULE_PREVIEW_LOADING_TEST_ID = 'securitySolutionDocumentDetailsFlyoutRulePreviewLoadingSpinner'; export const RULE_PREVIEW_FOOTER_TEST_ID = 'securitySolutionDocumentDetailsFlyoutRulePreviewFooter'; export const RULE_PREVIEW_NAVIGATE_TO_RULE_TEST_ID = 'goToRuleDetails'; +export const ALERT_REASON_PREVIEW_BODY_TEST_ID = + 'securitySolutionDocumentDetailsFlyoutAlertReasonPreviewBody'; diff --git a/x-pack/plugins/security_solution/public/flyout/preview/components/translations.ts b/x-pack/plugins/security_solution/public/flyout/preview/components/translations.ts index e3f3b1fd095fb..36bfdd33ea2ca 100644 --- a/x-pack/plugins/security_solution/public/flyout/preview/components/translations.ts +++ b/x-pack/plugins/security_solution/public/flyout/preview/components/translations.ts @@ -31,3 +31,8 @@ export const RULE_PREVIEW_ACTIONS_TEXT = i18n.translate( 'xpack.securitySolution.flyout.documentDetails.rulePreviewActionsSectionText', { defaultMessage: 'Actions' } ); + +export const ALERT_REASON_TITLE = i18n.translate( + 'xpack.securitySolution.flyout.documentDetails.alertReasonTitle', + { defaultMessage: 'Alert reason' } +); diff --git a/x-pack/plugins/security_solution/public/flyout/preview/context.tsx b/x-pack/plugins/security_solution/public/flyout/preview/context.tsx index 521303635c25d..005ef1dcdb258 100644 --- a/x-pack/plugins/security_solution/public/flyout/preview/context.tsx +++ b/x-pack/plugins/security_solution/public/flyout/preview/context.tsx @@ -7,11 +7,15 @@ import React, { createContext, useContext, useMemo } from 'react'; import type { DataViewBase } from '@kbn/es-query'; +import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; +import { SecurityPageName } from '@kbn/security-solution-navigation'; import type { PreviewPanelProps } from '.'; -import { useRouteSpy } from '../../common/utils/route/use_route_spy'; -import { SecurityPageName } from '../../../common/constants'; import { SourcererScopeName } from '../../common/store/sourcerer/model'; import { useSourcererDataView } from '../../common/containers/sourcerer'; +import { useTimelineEventsDetails } from '../../timelines/containers/details'; +import { getAlertIndexAlias } from '../../timelines/components/side_panel/event_details/helpers'; +import { useSpaceId } from '../../common/hooks/use_space_id'; +import { useRouteSpy } from '../../common/utils/route/use_route_spy'; export interface PreviewPanelContext { /** @@ -34,6 +38,10 @@ export interface PreviewPanelContext { * Index pattern for rule details */ indexPattern: DataViewBase; + /** + * An object with top level fields from the ECS object + */ + dataAsNestedObject: Ecs | null; } export const PreviewPanelContext = createContext(undefined); @@ -52,12 +60,21 @@ export const PreviewPanelProvider = ({ ruleId, children, }: PreviewPanelProviderProps) => { + const currentSpaceId = useSpaceId(); + const eventIndex = indexName ? getAlertIndexAlias(indexName, currentSpaceId) ?? indexName : ''; const [{ pageName }] = useRouteSpy(); const sourcererScope = pageName === SecurityPageName.detections ? SourcererScopeName.detections : SourcererScopeName.default; const sourcererDataView = useSourcererDataView(sourcererScope); + const [_, __, ___, dataAsNestedObject] = useTimelineEventsDetails({ + indexName: eventIndex, + eventId: id ?? '', + runtimeMappings: sourcererDataView.runtimeMappings, + skip: !id, + }); + const contextValue = useMemo( () => id && indexName && scopeId @@ -67,9 +84,10 @@ export const PreviewPanelProvider = ({ scopeId, ruleId: ruleId ?? '', indexPattern: sourcererDataView.indexPattern, + dataAsNestedObject, } : undefined, - [id, indexName, scopeId, ruleId, sourcererDataView.indexPattern] + [id, indexName, scopeId, ruleId, sourcererDataView.indexPattern, dataAsNestedObject] ); return ( diff --git a/x-pack/plugins/security_solution/public/flyout/preview/index.tsx b/x-pack/plugins/security_solution/public/flyout/preview/index.tsx index 9bfefb8f257fd..db9f7bb5ba58a 100644 --- a/x-pack/plugins/security_solution/public/flyout/preview/index.tsx +++ b/x-pack/plugins/security_solution/public/flyout/preview/index.tsx @@ -10,8 +10,9 @@ import type { FlyoutPanelProps, PanelPath } from '@kbn/expandable-flyout'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { panels } from './panels'; -export type PreviewPanelPaths = 'rule-preview'; +export type PreviewPanelPaths = 'rule-preview' | 'alert-reason-preview'; export const RulePreviewPanel: PreviewPanelPaths = 'rule-preview'; +export const AlertReasonPreviewPanel: PreviewPanelPaths = 'alert-reason-preview'; export const PreviewPanelKey: PreviewPanelProps['key'] = 'document-details-preview'; export interface PreviewPanelProps extends FlyoutPanelProps { diff --git a/x-pack/plugins/security_solution/public/flyout/preview/mocks/mock_preview_panel_context.ts b/x-pack/plugins/security_solution/public/flyout/preview/mocks/mock_preview_panel_context.ts index 4e9f9cc43d8ba..cdfe8ab5307ba 100644 --- a/x-pack/plugins/security_solution/public/flyout/preview/mocks/mock_preview_panel_context.ts +++ b/x-pack/plugins/security_solution/public/flyout/preview/mocks/mock_preview_panel_context.ts @@ -5,6 +5,8 @@ * 2.0. */ +import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; +import { mockDataAsNestedObject } from '../../shared/mocks/mock_context'; import type { PreviewPanelContext } from '../context'; /** @@ -16,4 +18,5 @@ export const mockContextValue: PreviewPanelContext = { scopeId: 'scopeId', ruleId: '', indexPattern: { fields: [], title: 'test index' }, + dataAsNestedObject: mockDataAsNestedObject as unknown as Ecs, }; diff --git a/x-pack/plugins/security_solution/public/flyout/preview/panels.tsx b/x-pack/plugins/security_solution/public/flyout/preview/panels.tsx index b9aee26bdd577..e585c58945d9c 100644 --- a/x-pack/plugins/security_solution/public/flyout/preview/panels.tsx +++ b/x-pack/plugins/security_solution/public/flyout/preview/panels.tsx @@ -6,8 +6,9 @@ */ import React from 'react'; +import { AlertReasonPreview } from './components/alert_reason_preview'; import type { PreviewPanelPaths } from '.'; -import { RULE_PREVIEW } from './translations'; +import { ALERT_REASON_PREVIEW, RULE_PREVIEW } from './translations'; import { RulePreview } from './components/rule_preview'; import { RulePreviewFooter } from './components/rule_preview_footer'; @@ -40,4 +41,9 @@ export const panels: PreviewPanelType = [ content: , footer: , }, + { + id: 'alert-reason-preview', + name: ALERT_REASON_PREVIEW, + content: , + }, ]; diff --git a/x-pack/plugins/security_solution/public/flyout/preview/translations.ts b/x-pack/plugins/security_solution/public/flyout/preview/translations.ts index 1db37fbb49bb8..cf359e7900cea 100644 --- a/x-pack/plugins/security_solution/public/flyout/preview/translations.ts +++ b/x-pack/plugins/security_solution/public/flyout/preview/translations.ts @@ -11,3 +11,8 @@ export const RULE_PREVIEW = i18n.translate( 'xpack.securitySolution.flyout.documentDetails.rulePreviewPanel', { defaultMessage: 'Rule preview' } ); + +export const ALERT_REASON_PREVIEW = i18n.translate( + 'xpack.securitySolution.flyout.documentDetails.alertReasonPreviewPanel', + { defaultMessage: 'Alert reason preview' } +); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/description.stories.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/description.stories.tsx deleted file mode 100644 index 13fa592c4f7d6..0000000000000 --- a/x-pack/plugins/security_solution/public/flyout/right/components/description.stories.tsx +++ /dev/null @@ -1,92 +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 { css } from '@emotion/react'; -import type { Story } from '@storybook/react'; -import { Description } from './description'; -import { RightPanelContext } from '../context'; - -const ruleUuid = { - category: 'kibana', - field: 'kibana.alert.rule.uuid', - values: ['123'], - originalValue: ['123'], - isObjectArray: false, -}; -const ruleDescription = { - category: 'kibana', - field: 'kibana.alert.rule.description', - values: [ - `This is a very long description of the rule. In theory. this description is long enough that it should be cut off when displayed in collapsed mode. If it isn't then there is a problem`, - ], - originalValue: ['description'], - isObjectArray: false, -}; - -export default { - component: Description, - title: 'Flyout/Description', -}; - -const wrapper = (children: React.ReactNode, panelContextValue: RightPanelContext) => ( - -
- {children} -
-
-); -export const Rule: Story = () => { - const panelContextValue = { - dataFormattedForFieldBrowser: [ruleUuid, ruleDescription], - } as unknown as RightPanelContext; - - return wrapper(, panelContextValue); -}; - -export const Document: Story = () => { - const panelContextValue = { - dataFormattedForFieldBrowser: [ - { - category: 'kibana', - field: 'kibana.alert.rule.description', - values: ['This is a description for the document.'], - originalValue: ['description'], - isObjectArray: false, - }, - ], - } as unknown as RightPanelContext; - - return wrapper(, panelContextValue); -}; - -export const EmptyDescription: Story = () => { - const panelContextValue = { - dataFormattedForFieldBrowser: [ - ruleUuid, - { - category: 'kibana', - field: 'kibana.alert.rule.description', - values: [''], - originalValue: ['description'], - isObjectArray: false, - }, - ], - } as unknown as RightPanelContext; - - return wrapper(, panelContextValue); -}; - -export const Empty: Story = () => { - const panelContextValue = {} as unknown as RightPanelContext; - - return wrapper(, panelContextValue); -}; diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/description.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/description.test.tsx index 1cf4823d79358..f80d7c1939661 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/description.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/description.test.tsx @@ -8,14 +8,17 @@ import React from 'react'; import { render } from '@testing-library/react'; import { DESCRIPTION_TITLE_TEST_ID, RULE_SUMMARY_BUTTON_TEST_ID } from './test_ids'; -import { DOCUMENT_DESCRIPTION_TITLE, RULE_DESCRIPTION_TITLE } from './translations'; +import { + DOCUMENT_DESCRIPTION_TITLE, + PREVIEW_RULE_DETAILS, + RULE_DESCRIPTION_TITLE, +} from './translations'; import { Description } from './description'; -import { TestProviders } from '../../../common/mock'; import { RightPanelContext } from '../context'; -import { ThemeProvider } from 'styled-components'; -import { getMockTheme } from '../../../common/lib/kibana/kibana_react.mock'; - -const mockTheme = getMockTheme({ eui: { euiColorMediumShade: '#ece' } }); +import { mockGetFieldsData } from '../mocks/mock_context'; +import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; +import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; +import { PreviewPanelKey } from '../../preview'; const ruleUuid = { category: 'kibana', @@ -41,23 +44,32 @@ const ruleName = { isObjectArray: false, }; -jest.mock('../../../common/lib/kibana'); -jest.mock('../../../common/components/link_to'); +const flyoutContextValue = { + openPreviewPanel: jest.fn(), +} as unknown as ExpandableFlyoutContext; + +const panelContextValue = (dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null) => + ({ + eventId: 'event id', + indexName: 'indexName', + scopeId: 'scopeId', + dataFormattedForFieldBrowser, + getFieldsData: mockGetFieldsData, + } as unknown as RightPanelContext); + +const renderDescription = (panelContext: RightPanelContext) => + render( + + + + + + ); describe('', () => { it('should render the component', () => { - const panelContextValue = { - dataFormattedForFieldBrowser: [ruleUuid, ruleDescription, ruleName], - } as unknown as RightPanelContext; - - const { getByTestId } = render( - - - - - - - + const { getByTestId } = renderDescription( + panelContextValue([ruleUuid, ruleDescription, ruleName]) ); expect(getByTestId(DESCRIPTION_TITLE_TEST_ID)).toBeInTheDocument(); @@ -66,18 +78,8 @@ describe('', () => { }); it('should not render rule preview button if rule name is not available', () => { - const panelContextValue = { - dataFormattedForFieldBrowser: [ruleUuid, ruleDescription], - } as unknown as RightPanelContext; - - const { getByTestId, queryByTestId } = render( - - - - - - - + const { getByTestId, queryByTestId } = renderDescription( + panelContextValue([ruleUuid, ruleDescription]) ); expect(getByTestId(DESCRIPTION_TITLE_TEST_ID)).toBeInTheDocument(); @@ -86,21 +88,44 @@ describe('', () => { }); it('should render document title if document is not an alert', () => { - const panelContextValue = { - dataFormattedForFieldBrowser: [ruleDescription], - } as unknown as RightPanelContext; - - const { getByTestId } = render( - - - - - - - - ); + const { getByTestId } = renderDescription(panelContextValue([ruleDescription])); expect(getByTestId(DESCRIPTION_TITLE_TEST_ID)).toBeInTheDocument(); expect(getByTestId(DESCRIPTION_TITLE_TEST_ID)).toHaveTextContent(DOCUMENT_DESCRIPTION_TITLE); }); + + it('should render null if dataFormattedForFieldBrowser is null', () => { + const panelContext = { + ...panelContextValue([ruleUuid, ruleDescription, ruleName]), + dataFormattedForFieldBrowser: null, + } as unknown as RightPanelContext; + + const { container } = renderDescription(panelContext); + + expect(container).toBeEmptyDOMElement(); + }); + + it('should open preview panel when clicking on button', () => { + const panelContext = panelContextValue([ruleUuid, ruleDescription, ruleName]); + + const { getByTestId } = renderDescription(panelContext); + + getByTestId(RULE_SUMMARY_BUTTON_TEST_ID).click(); + + expect(flyoutContextValue.openPreviewPanel).toHaveBeenCalledWith({ + id: PreviewPanelKey, + path: { tab: 'rule-preview' }, + params: { + id: panelContext.eventId, + indexName: panelContext.indexName, + scopeId: panelContext.scopeId, + banner: { + title: PREVIEW_RULE_DETAILS, + backgroundColor: 'warning', + textColor: 'warning', + }, + ruleId: ruleUuid.values[0], + }, + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/reason.stories.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/reason.stories.tsx deleted file mode 100644 index 68cb0b3a35e31..0000000000000 --- a/x-pack/plugins/security_solution/public/flyout/right/components/reason.stories.tsx +++ /dev/null @@ -1,45 +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 { Reason } from './reason'; -import { RightPanelContext } from '../context'; -import { mockDataAsNestedObject, mockDataFormattedForFieldBrowser } from '../mocks/mock_context'; - -export default { - component: Reason, - title: 'Flyout/Reason', -}; - -export const Default: Story = () => { - const panelContextValue = { - dataAsNestedObject: mockDataAsNestedObject, - dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser, - } as unknown as RightPanelContext; - - return ( - - - - - - ); -}; - -export const Empty: Story = () => { - const panelContextValue = { - dataFormattedForFieldBrowser: {}, - } as unknown as RightPanelContext; - - return ( - - - - ); -}; diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/reason.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/reason.test.tsx index b7050d1df0fa0..3ec854cfbd815 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/reason.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/reason.test.tsx @@ -7,70 +7,85 @@ import React from 'react'; import { render } from '@testing-library/react'; -import { REASON_TITLE_TEST_ID } from './test_ids'; +import { + REASON_DETAILS_PREVIEW_BUTTON_TEST_ID, + REASON_DETAILS_TEST_ID, + REASON_TITLE_TEST_ID, +} from './test_ids'; import { Reason } from './reason'; import { RightPanelContext } from '../context'; -import { mockDataAsNestedObject, mockDataFormattedForFieldBrowser } from '../mocks/mock_context'; -import { euiDarkVars } from '@kbn/ui-theme'; -import { ThemeProvider } from 'styled-components'; +import { mockDataFormattedForFieldBrowser, mockGetFieldsData } from '../mocks/mock_context'; +import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; +import { PreviewPanelKey } from '../../preview'; +import { PREVIEW_ALERT_REASON_DETAILS } from './translations'; -describe('', () => { - it('should render the component', () => { - const panelContextValue = { - dataAsNestedObject: mockDataAsNestedObject, - dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser, - } as unknown as RightPanelContext; +const flyoutContextValue = { + openPreviewPanel: jest.fn(), +} as unknown as ExpandableFlyoutContext; - const { getByTestId } = render( - ({ eui: euiDarkVars, darkMode: true })}> - - - - - ); +const panelContextValue = { + eventId: 'event id', + indexName: 'indexName', + scopeId: 'scopeId', + dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser, + getFieldsData: mockGetFieldsData, +} as unknown as RightPanelContext; +const renderReason = (panelContext: RightPanelContext = panelContextValue) => + render( + + + + + + ); + +describe('', () => { + it('should render the component', () => { + const { getByTestId } = renderReason(); expect(getByTestId(REASON_TITLE_TEST_ID)).toBeInTheDocument(); }); it('should render null if dataFormattedForFieldBrowser is null', () => { - const panelContextValue = { - dataAsNestedObject: {}, + const panelContext = { + ...panelContextValue, + dataFormattedForFieldBrowser: null, } as unknown as RightPanelContext; - const { container } = render( - - - - ); + const { container } = renderReason(panelContext); expect(container).toBeEmptyDOMElement(); }); - it('should render null if dataAsNestedObject is null', () => { - const panelContextValue = { - dataFormattedForFieldBrowser: [], + it('should render no reason if the field is null', () => { + const panelContext = { + ...panelContextValue, + getFieldsData: () => {}, } as unknown as RightPanelContext; - const { container } = render( - - - - ); + const { getByTestId } = renderReason(panelContext); - expect(container).toBeEmptyDOMElement(); + expect(getByTestId(REASON_DETAILS_TEST_ID)).toBeEmptyDOMElement(); }); - it('should render null if renderer is null', () => { - const panelContextValue = { - dataAsNestedObject: {}, - dataFormattedForFieldBrowser: [], - } as unknown as RightPanelContext; - const { container } = render( - - - - ); + it('should open preview panel when clicking on button', () => { + const { getByTestId } = renderReason(); - expect(container).toBeEmptyDOMElement(); + getByTestId(REASON_DETAILS_PREVIEW_BUTTON_TEST_ID).click(); + + expect(flyoutContextValue.openPreviewPanel).toHaveBeenCalledWith({ + id: PreviewPanelKey, + path: { tab: 'alert-reason-preview' }, + params: { + id: panelContextValue.eventId, + indexName: panelContextValue.indexName, + scopeId: panelContextValue.scopeId, + banner: { + title: PREVIEW_ALERT_REASON_DETAILS, + backgroundColor: 'warning', + textColor: 'warning', + }, + }, + }); }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/reason.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/reason.tsx index b6633ac42c46b..b356809917973 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/reason.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/reason.tsx @@ -6,31 +6,71 @@ */ import type { FC } from 'react'; -import React, { useMemo } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; -import { REASON_DETAILS_TEST_ID, REASON_TITLE_TEST_ID } from './test_ids'; -import { ALERT_REASON_TITLE, DOCUMENT_REASON_TITLE } from './translations'; +import React, { useCallback, useMemo } from 'react'; +import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; +import { useExpandableFlyoutContext } from '@kbn/expandable-flyout'; +import { ALERT_REASON } from '@kbn/rule-data-utils'; +import { getField } from '../../shared/utils'; +import { AlertReasonPreviewPanel, PreviewPanelKey } from '../../preview'; +import { + REASON_DETAILS_PREVIEW_BUTTON_TEST_ID, + REASON_DETAILS_TEST_ID, + REASON_TITLE_TEST_ID, +} from './test_ids'; +import { + ALERT_REASON_DETAILS_TEXT, + ALERT_REASON_TITLE, + DOCUMENT_REASON_TITLE, + PREVIEW_ALERT_REASON_DETAILS, +} from './translations'; import { useBasicDataFromDetailsData } from '../../../timelines/components/side_panel/event_details/helpers'; -import { defaultRowRenderers } from '../../../timelines/components/timeline/body/renderers'; -import { getRowRenderer } from '../../../timelines/components/timeline/body/renderers/get_row_renderer'; import { useRightPanelContext } from '../context'; /** * Displays the information provided by the rowRenderer. Supports multiple types of documents. */ export const Reason: FC = () => { - const { dataAsNestedObject, dataFormattedForFieldBrowser } = useRightPanelContext(); + const { eventId, indexName, scopeId, dataFormattedForFieldBrowser, getFieldsData } = + useRightPanelContext(); const { isAlert } = useBasicDataFromDetailsData(dataFormattedForFieldBrowser); + const alertReason = getField(getFieldsData(ALERT_REASON)); - const renderer = useMemo( - () => - dataAsNestedObject != null - ? getRowRenderer({ data: dataAsNestedObject, rowRenderers: defaultRowRenderers }) - : null, - [dataAsNestedObject] + const { openPreviewPanel } = useExpandableFlyoutContext(); + const openRulePreview = useCallback(() => { + openPreviewPanel({ + id: PreviewPanelKey, + path: { tab: AlertReasonPreviewPanel }, + params: { + id: eventId, + indexName, + scopeId, + banner: { + title: PREVIEW_ALERT_REASON_DETAILS, + backgroundColor: 'warning', + textColor: 'warning', + }, + }, + }); + }, [eventId, openPreviewPanel, indexName, scopeId]); + + const viewPreview = useMemo( + () => ( + + + {ALERT_REASON_DETAILS_TEXT} + + + ), + [openRulePreview] ); - if (!dataFormattedForFieldBrowser || !dataAsNestedObject || !renderer) { + if (!dataFormattedForFieldBrowser) { return null; } @@ -38,17 +78,21 @@ export const Reason: FC = () => { -
{isAlert ? ALERT_REASON_TITLE : DOCUMENT_REASON_TITLE}
+
+ {isAlert ? ( + + +
{ALERT_REASON_TITLE}
+
+ {viewPreview} +
+ ) : ( + DOCUMENT_REASON_TITLE + )} +
- - {renderer.renderRow({ - contextId: 'event-details', - data: dataAsNestedObject, - isDraggable: false, - scopeId: 'global', - })} - + {alertReason}
); }; diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/right/components/test_ids.ts index 9e8b112851be6..7d41fe13f9fca 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/test_ids.ts +++ b/x-pack/plugins/security_solution/public/flyout/right/components/test_ids.ts @@ -46,6 +46,8 @@ export const DESCRIPTION_DETAILS_TEST_ID = 'securitySolutionDocumentDetailsFlyoutDescriptionDetails'; export const REASON_TITLE_TEST_ID = 'securitySolutionDocumentDetailsFlyoutReasonTitle'; export const REASON_DETAILS_TEST_ID = 'securitySolutionDocumentDetailsFlyoutReasonDetails'; +export const REASON_DETAILS_PREVIEW_BUTTON_TEST_ID = + 'securitySolutionDocumentDetailsFlyoutReasonDetailsPreviewButton'; export const MITRE_ATTACK_TITLE_TEST_ID = 'securitySolutionAlertDetailsFlyoutMitreAttackTitle'; export const MITRE_ATTACK_DETAILS_TEST_ID = 'securitySolutionAlertDetailsFlyoutMitreAttackDetails'; diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/translations.ts b/x-pack/plugins/security_solution/public/flyout/right/components/translations.ts index f32e9abb1d48f..a411b0f44054e 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/translations.ts +++ b/x-pack/plugins/security_solution/public/flyout/right/components/translations.ts @@ -45,6 +45,13 @@ export const RULE_SUMMARY_TEXT = i18n.translate( } ); +export const ALERT_REASON_DETAILS_TEXT = i18n.translate( + 'xpack.securitySolution.flyout.documentDetails.alertReasonDetailsText', + { + defaultMessage: 'Show full reason', + } +); + /* About section */ export const ABOUT_TITLE = i18n.translate( @@ -66,6 +73,11 @@ export const PREVIEW_RULE_DETAILS = i18n.translate( { defaultMessage: 'Preview rule details' } ); +export const PREVIEW_ALERT_REASON_DETAILS = i18n.translate( + 'xpack.securitySolution.flyout.documentDetails.previewAlertReasonDetailsText', + { defaultMessage: 'Preview alert reason' } +); + export const DOCUMENT_DESCRIPTION_TITLE = i18n.translate( 'xpack.securitySolution.flyout.documentDetails.documentDescriptionTitle', { diff --git a/x-pack/plugins/security_solution/public/flyout/right/hooks/use_process_data.ts b/x-pack/plugins/security_solution/public/flyout/right/hooks/use_process_data.ts index 72ca71badaf07..ac98ddd1df2b2 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/hooks/use_process_data.ts +++ b/x-pack/plugins/security_solution/public/flyout/right/hooks/use_process_data.ts @@ -6,6 +6,7 @@ */ import { useMemo } from 'react'; +import { ALERT_RULE_NAME, ALERT_RULE_UUID } from '@kbn/rule-data-utils'; import type { GetFieldsData } from '../../../common/hooks/use_get_fields_data'; import { getField } from '../../shared/utils'; import { useRightPanelContext } from '../context'; @@ -14,8 +15,6 @@ const FIELD_USER_NAME = 'process.entry_leader.user.name' as const; const FIELD_USER_ID = 'process.entry_leader.user.id' as const; const FIELD_PROCESS_NAME = 'process.entry_leader.name' as const; const FIELD_START_AT = 'process.entry_leader.start' as const; -const FIELD_RULE_NAME = 'kibana.alert.rule.name' as const; -const FIELD_RULE_ID = 'kibana.alert.rule.uuid' as const; const FIELD_WORKING_DIRECTORY = 'process.group_leader.working_directory' as const; const FIELD_COMMAND = 'process.command_line' as const; @@ -48,8 +47,8 @@ export const useProcessData = () => { userName: getUserDisplayName(getFieldsData), processName: getField(getFieldsData(FIELD_PROCESS_NAME)), startAt: getField(getFieldsData(FIELD_START_AT)), - ruleName: getField(getFieldsData(FIELD_RULE_NAME)), - ruleId: getField(getFieldsData(FIELD_RULE_ID)), + ruleName: getField(getFieldsData(ALERT_RULE_NAME)), + ruleId: getField(getFieldsData(ALERT_RULE_UUID)), workdir: getField(getFieldsData(FIELD_WORKING_DIRECTORY)), command: getField(getFieldsData(FIELD_COMMAND)), }), diff --git a/x-pack/plugins/security_solution/public/flyout/right/mocks/mock_context.ts b/x-pack/plugins/security_solution/public/flyout/right/mocks/mock_context.ts index 6ae872acd45ac..cdc058569d9d3 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/mocks/mock_context.ts +++ b/x-pack/plugins/security_solution/public/flyout/right/mocks/mock_context.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ALERT_RISK_SCORE, ALERT_SEVERITY } from '@kbn/rule-data-utils'; +import { ALERT_REASON, ALERT_RISK_SCORE, ALERT_SEVERITY } from '@kbn/rule-data-utils'; /** * Returns mocked data for field (mock this method: x-pack/plugins/security_solution/public/common/hooks/use_get_fields_data.ts) @@ -22,6 +22,8 @@ export const mockGetFieldsData = (field: string): string[] => { return ['host1']; case 'user.name': return ['user1']; + case ALERT_REASON: + return ['reason']; default: return []; } diff --git a/x-pack/test/functional/apps/infra/home_page.ts b/x-pack/test/functional/apps/infra/home_page.ts index 79fa4d0eea06e..7fedb18db416c 100644 --- a/x-pack/test/functional/apps/infra/home_page.ts +++ b/x-pack/test/functional/apps/infra/home_page.ts @@ -199,8 +199,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/157711 - describe.skip('alerts flyouts', () => { + describe('alerts flyouts', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); await pageObjects.common.navigateToApp('infraOps'); @@ -217,7 +216,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await pageObjects.infraHome.closeAlertFlyout(); }); - it('should open and close inventory alert flyout', async () => { + it('should open and close metrics threshold alert flyout', async () => { await pageObjects.infraHome.openMetricsThresholdAlertFlyout(); await pageObjects.infraHome.closeAlertFlyout(); }); diff --git a/x-pack/test/functional/page_objects/infra_home_page.ts b/x-pack/test/functional/page_objects/infra_home_page.ts index f9f088b106375..aed3558cfdcb4 100644 --- a/x-pack/test/functional/page_objects/infra_home_page.ts +++ b/x-pack/test/functional/page_objects/infra_home_page.ts @@ -335,20 +335,40 @@ export function InfraHomePageProvider({ getService, getPageObjects }: FtrProvide await testSubjects.missingOrFail('metrics-alert-menu'); }, + async dismissDatePickerTooltip() { + const isTooltipOpen = await testSubjects.exists(`waffleDatePickerIntervalTooltip`, { + timeout: 1000, + }); + + if (isTooltipOpen) { + await testSubjects.click(`waffleDatePickerIntervalTooltip`); + } + }, + async openInventoryAlertFlyout() { + await this.dismissDatePickerTooltip(); await testSubjects.click('infrastructure-alerts-and-rules'); await testSubjects.click('inventory-alerts-menu-option'); - await testSubjects.click('inventory-alerts-create-rule'); + + // forces date picker tooltip to close in case it pops up after Alerts and rules opens + await testSubjects.moveMouseTo('contextMenuPanelTitleButton'); + + await retry.tryForTime(1000, () => testSubjects.click('inventory-alerts-create-rule')); await testSubjects.missingOrFail('inventory-alerts-create-rule', { timeout: 30000 }); - await testSubjects.find('euiFlyoutCloseButton'); }, async openMetricsThresholdAlertFlyout() { + await this.dismissDatePickerTooltip(); await testSubjects.click('infrastructure-alerts-and-rules'); await testSubjects.click('metrics-threshold-alerts-menu-option'); - await testSubjects.click('metrics-threshold-alerts-create-rule'); + + // forces date picker tooltip to close in case it pops up after Alerts and rules opens + await testSubjects.moveMouseTo('contextMenuPanelTitleButton'); + + await retry.tryForTime(1000, () => + testSubjects.click('metrics-threshold-alerts-create-rule') + ); await testSubjects.missingOrFail('metrics-threshold-alerts-create-rule', { timeout: 30000 }); - await testSubjects.find('euiFlyoutCloseButton'); }, async closeAlertFlyout() { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/alerts_detection_callouts_index_outdated.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/alerts_detection_callouts_index_outdated.cy.ts index 59aa3a5abe793..1a1b47e925c91 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/alerts_detection_callouts_index_outdated.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/alerts_detection_callouts_index_outdated.cy.ts @@ -15,7 +15,11 @@ import { PAGE_TITLE } from '../../screens/common/page'; import { login, visitWithoutDateRange, waitForPageWithoutDateRange } from '../../tasks/login'; import { goToRuleDetails } from '../../tasks/alerts_detection_rules'; import { createRule, deleteCustomRule } from '../../tasks/api_calls/rules'; -import { getCallOut, waitForCallOutToBeShown } from '../../tasks/common/callouts'; +import { + getCallOut, + NEED_ADMIN_FOR_UPDATE_CALLOUT, + waitForCallOutToBeShown, +} from '../../tasks/common/callouts'; const loadPageAsPlatformEngineerUser = (url: string) => { login(ROLES.soc_manager); @@ -31,8 +35,6 @@ describe( 'Detections > Need Admin Callouts indicating an admin is needed to migrate the alert data set', { tags: tag.ESS }, () => { - const NEED_ADMIN_FOR_UPDATE_CALLOUT = 'need-admin-for-update-rules'; - before(() => { // First, we have to open the app on behalf of a privileged user in order to initialize it. // Otherwise the app will be disabled and show a "welcome"-like page. diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/missing_privileges_callout.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/missing_privileges_callout.cy.ts index 4ca60eebad297..767b2ecbdd5c2 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/missing_privileges_callout.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_alerts/missing_privileges_callout.cy.ts @@ -15,7 +15,12 @@ import { PAGE_TITLE } from '../../screens/common/page'; import { login, visitWithoutDateRange, waitForPageWithoutDateRange } from '../../tasks/login'; import { goToRuleDetails } from '../../tasks/alerts_detection_rules'; import { createRule, deleteCustomRule } from '../../tasks/api_calls/rules'; -import { getCallOut, waitForCallOutToBeShown, dismissCallOut } from '../../tasks/common/callouts'; +import { + getCallOut, + waitForCallOutToBeShown, + dismissCallOut, + MISSING_PRIVILEGES_CALLOUT, +} from '../../tasks/common/callouts'; const loadPageAsReadOnlyUser = (url: string) => { login(ROLES.reader); @@ -39,8 +44,6 @@ const waitForPageTitleToBeShown = () => { }; describe('Detections > Callouts', { tags: tag.ESS }, () => { - const MISSING_PRIVILEGES_CALLOUT = 'missing-user-privileges'; - before(() => { // First, we have to open the app on behalf of a privileged user in order to initialize it. // Otherwise the app will be disabled and show a "welcome"-like page. diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/authorization/all_rules_read_only.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/authorization/all_rules_read_only.cy.ts index 62f484b69427a..bd8c5743d37b2 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/authorization/all_rules_read_only.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/authorization/all_rules_read_only.cy.ts @@ -22,12 +22,11 @@ import { dismissCallOut, getCallOut, waitForCallOutToBeShown, + MISSING_PRIVILEGES_CALLOUT, } from '../../../../tasks/common/callouts'; import { login, visitWithoutDateRange } from '../../../../tasks/login'; import { SECURITY_DETECTIONS_RULES_URL } from '../../../../urls/navigation'; -const MISSING_PRIVILEGES_CALLOUT = 'missing-user-privileges'; - describe('All rules - read only', { tags: tag.ESS }, () => { before(() => { cleanKibana(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules_actions.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules_actions.cy.ts index d6000c671fb86..3b3755dc66d3f 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules_actions.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_actions/bulk_actions/bulk_edit_rules_actions.cy.ts @@ -7,6 +7,11 @@ import type { RuleActionArray } from '@kbn/securitysolution-io-ts-alerting-types'; import { ROLES } from '@kbn/security-solution-plugin/common/test'; +import { + MISSING_PRIVILEGES_CALLOUT, + waitForCallOutToBeShown, +} from '../../../../../tasks/common/callouts'; +import { createRuleAssetSavedObject } from '../../../../../helpers/rules'; import { tag } from '../../../../../tags'; import { @@ -34,6 +39,7 @@ import { waitForRulesTableToBeLoaded, selectNumberOfRules, goToEditRuleActionsSettingsOf, + disableAutoRefresh, } from '../../../../../tasks/alerts_detection_rules'; import { waitForBulkEditActionToFinish, @@ -57,28 +63,28 @@ import { getMachineLearningRule, getNewTermsRule, } from '../../../../../objects/rule'; -import { excessivelyInstallAllPrebuiltRules } from '../../../../../tasks/api_calls/prebuilt_rules'; +import { + createAndInstallMockedPrebuiltRules, + excessivelyInstallAllPrebuiltRules, + preventPrebuiltRulesPackageInstallation, +} from '../../../../../tasks/api_calls/prebuilt_rules'; const ruleNameToAssert = 'Custom rule name with actions'; const expectedNumberOfCustomRulesToBeEdited = 7; -// 7 custom rules of different types + 3 prebuilt. +// 7 custom rules of different types + 2 prebuilt. // number of selected rules doesn't matter, we only want to make sure they will be edited an no modal window displayed as for other actions -const expectedNumberOfRulesToBeEdited = expectedNumberOfCustomRulesToBeEdited + 3; +const expectedNumberOfRulesToBeEdited = expectedNumberOfCustomRulesToBeEdited + 2; const expectedExistingSlackMessage = 'Existing slack action'; const expectedSlackMessage = 'Slack action test message'; -// TODO: Fix flakiness and unskip https://github.com/elastic/kibana/issues/154721 -describe.skip( +describe( 'Detection rules, bulk edit of rule actions', { tags: [tag.ESS, tag.BROKEN_IN_SERVERLESS] }, () => { - before(() => { + beforeEach(() => { cleanKibana(); login(); - }); - - beforeEach(() => { deleteAlertsAndRules(); deleteConnectors(); cy.task('esArchiverResetKibana'); @@ -111,12 +117,27 @@ describe.skip( createRule(getNewRule({ saved_id: 'mocked', rule_id: '7' })); createSlackConnector(); + + // Prevent prebuilt rules package installation and mock two prebuilt rules + preventPrebuiltRulesPackageInstallation(); + + const RULE_1 = createRuleAssetSavedObject({ + name: 'Test rule 1', + rule_id: 'rule_1', + }); + const RULE_2 = createRuleAssetSavedObject({ + name: 'Test rule 2', + rule_id: 'rule_2', + }); + + createAndInstallMockedPrebuiltRules({ rules: [RULE_1, RULE_2] }); }); context('Restricted action privileges', () => { it("User with no privileges can't add rule actions", () => { login(ROLES.hunter_no_actions); visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL, ROLES.hunter_no_actions); + waitForCallOutToBeShown(MISSING_PRIVILEGES_CALLOUT, 'primary'); waitForRulesTableToBeLoaded(); selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited); @@ -129,8 +150,10 @@ describe.skip( context('All actions privileges', () => { beforeEach(() => { + login(); visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL); waitForRulesTableToBeLoaded(); + disableAutoRefresh(); }); it('Add a rule action to rules (existing connector)', () => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/read_only.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/read_only.cy.ts index 25b9d2e34fe2e..b11d3de105b83 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/read_only.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/read_only.cy.ts @@ -17,12 +17,11 @@ import { dismissCallOut, getCallOut, waitForCallOutToBeShown, + MISSING_PRIVILEGES_CALLOUT, } from '../../../../tasks/common/callouts'; import { login, visitWithoutDateRange } from '../../../../tasks/login'; import { EXCEPTIONS_URL } from '../../../../urls/navigation'; -const MISSING_PRIVILEGES_CALLOUT = 'missing-user-privileges'; - describe('Shared exception lists - read only', { tags: tag.ESS }, () => { before(() => { cy.task('esArchiverResetKibana'); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_preview_panel_alert_reason_preview.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_preview_panel_alert_reason_preview.cy.ts new file mode 100644 index 0000000000000..83d2dbee62212 --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_preview_panel_alert_reason_preview.cy.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 { DOCUMENT_DETAILS_FLYOUT_ALERT_REASON_PREVIEW_CONTAINER } from '../../../../screens/expandable_flyout/alert_details_preview_panel_alert_reason_preview'; +import { expandFirstAlertExpandableFlyout } from '../../../../tasks/expandable_flyout/common'; +import { clickAlertReasonButton } from '../../../../tasks/expandable_flyout/alert_details_right_panel_overview_tab'; +import { cleanKibana } from '../../../../tasks/common'; +import { login, visit } from '../../../../tasks/login'; +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'; +import { tag } from '../../../../tags'; + +describe( + 'Alert details expandable flyout rule preview panel', + { tags: [tag.ESS, tag.BROKEN_IN_SERVERLESS] }, + () => { + const rule = getNewRule(); + + beforeEach(() => { + cleanKibana(); + login(); + createRule(rule); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + expandFirstAlertExpandableFlyout(); + clickAlertReasonButton(); + }); + + describe('alert reason preview', () => { + it('should display alert reason preview', () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_ALERT_REASON_PREVIEW_CONTAINER).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_ALERT_REASON_PREVIEW_CONTAINER).should('be.visible'); + }); + }); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts index 2cdf95746dcfa..050463b70ae50 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_right_panel_overview_tab.cy.ts @@ -111,7 +111,8 @@ describe( cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_TITLE) .should('be.visible') - .and('have.text', 'Alert reason'); + .and('contain.text', 'Alert reason') + .and('contain.text', 'Show full reason'); cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_DETAILS) .should('be.visible') .and('contain.text', rule.name); diff --git a/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_preview_panel_alert_reason_preview.ts b/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_preview_panel_alert_reason_preview.ts new file mode 100644 index 0000000000000..37db919da75ab --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_preview_panel_alert_reason_preview.ts @@ -0,0 +1,13 @@ +/* + * 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 { ALERT_REASON_PREVIEW_BODY_TEST_ID } from '@kbn/security-solution-plugin/public/flyout/preview/components/test_ids'; +import { getDataTestSubjectSelector } from '../../helpers/common'; + +export const DOCUMENT_DETAILS_FLYOUT_ALERT_REASON_PREVIEW_CONTAINER = getDataTestSubjectSelector( + ALERT_REASON_PREVIEW_BODY_TEST_ID +); diff --git a/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_right_panel_overview_tab.ts b/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_right_panel_overview_tab.ts index dc7e3fdd1020e..ebfb40a3091f5 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_right_panel_overview_tab.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_right_panel_overview_tab.ts @@ -38,6 +38,7 @@ import { ANALYZER_PREVIEW_CONTENT_TEST_ID, SESSION_PREVIEW_CONTENT_TEST_ID, INSIGHTS_PREVALENCE_VALUE_TEST_ID, + REASON_DETAILS_PREVIEW_BUTTON_TEST_ID, } from '@kbn/security-solution-plugin/public/flyout/right/components/test_ids'; import { getDataTestSubjectSelector } from '../../helpers/common'; @@ -59,6 +60,8 @@ export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_TITLE = getDataTestSubjectSelector(REASON_TITLE_TEST_ID); export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_DETAILS = getDataTestSubjectSelector(REASON_DETAILS_TEST_ID); +export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_OPEN_ALERT_REASON_PREVIEW_BUTTON = + getDataTestSubjectSelector(REASON_DETAILS_PREVIEW_BUTTON_TEST_ID); export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_MITRE_ATTACK_TITLE = getDataTestSubjectSelector( MITRE_ATTACK_TITLE_TEST_ID ); diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/common/callouts.ts b/x-pack/test/security_solution_cypress/cypress/tasks/common/callouts.ts index c65a29b8aa750..802faf821f8da 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/common/callouts.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/common/callouts.ts @@ -7,6 +7,9 @@ import { callOutWithId, CALLOUT_DISMISS_BTN } from '../../screens/common/callouts'; +export const NEED_ADMIN_FOR_UPDATE_CALLOUT = 'need-admin-for-update-rules'; +export const MISSING_PRIVILEGES_CALLOUT = 'missing-user-privileges'; + export const getCallOut = (id: string, options?: Cypress.Timeoutable) => { return cy.get(callOutWithId(id), options); }; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout/alert_details_right_panel_overview_tab.ts b/x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout/alert_details_right_panel_overview_tab.ts index c87a3c2afb9fe..b94a8be090fa4 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout/alert_details_right_panel_overview_tab.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout/alert_details_right_panel_overview_tab.ts @@ -20,6 +20,8 @@ import { DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_TITLE, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_OPEN_RULE_PREVIEW_BUTTON, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_RESPONSE_SECTION_HEADER, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_TITLE, + DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_OPEN_ALERT_REASON_PREVIEW_BUTTON, } from '../../screens/expandable_flyout/alert_details_right_panel_overview_tab'; /* About section */ @@ -129,3 +131,17 @@ export const clickRuleSummaryButton = () => { .click(); }); }; + +/** + * Click `Show full reason` button to open alert reason preview panel + */ +export const clickAlertReasonButton = () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_TITLE).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_REASON_TITLE) + .should('be.visible') + .within(() => { + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_OPEN_ALERT_REASON_PREVIEW_BUTTON) + .should('be.visible') + .click(); + }); +};