diff --git a/packages/kbn-expandable-flyout/src/components/preview_section.tsx b/packages/kbn-expandable-flyout/src/components/preview_section.tsx index 72aa87e388150..1bb3f84d1b5f5 100644 --- a/packages/kbn-expandable-flyout/src/components/preview_section.tsx +++ b/packages/kbn-expandable-flyout/src/components/preview_section.tsx @@ -17,13 +17,12 @@ import { } from '@elastic/eui'; import React from 'react'; import { css } from '@emotion/react'; - import { has } from 'lodash'; import { - PREVIEW_SECTION, PREVIEW_SECTION_BACK_BUTTON, PREVIEW_SECTION_CLOSE_BUTTON, PREVIEW_SECTION_HEADER, + PREVIEW_SECTION, } from './test_ids'; import { useExpandableFlyoutContext } from '../..'; import { BACK_BUTTON, CLOSE_BUTTON } from './translations'; @@ -124,28 +123,21 @@ export const PreviewSection: React.FC = ({ ); return ( - <> -
+
= ({ {component} - +
); }; diff --git a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_preview_panel_rule_preview.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_preview_panel_rule_preview.cy.ts index 1795a2e623802..116162983f0b2 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_preview_panel_rule_preview.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_preview_panel_rule_preview.cy.ts @@ -9,6 +9,9 @@ import { expandFirstAlertExpandableFlyout } from '../../../../tasks/expandable_f import { DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_SECTION, DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_HEADER, + DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_TITLE, + DOCUMENT_DETAILS_FLYOUT_CREATED_BY, + DOCUMENT_DETAILS_FLYOUT_UPDATED_BY, DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_BODY, DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_ABOUT_SECTION_HEADER, DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_ABOUT_SECTION_CONTENT, @@ -52,10 +55,14 @@ describe( cy.log('rule preview panel'); cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_SECTION).scrollIntoView(); - cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_SECTION).should('be.visible'); cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_HEADER).should('be.visible'); cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_BODY).should('be.visible'); - cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_FOOTER).should('be.visible'); + + cy.log('title'); + cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_TITLE).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_TITLE).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_CREATED_BY).should('be.visible'); + cy.get(DOCUMENT_DETAILS_FLYOUT_UPDATED_BY).should('be.visible'); cy.log('about'); @@ -84,6 +91,10 @@ describe( .and('contain.text', 'Schedule'); cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_SCHEDULE_SECTION_CONTENT).should('be.visible'); toggleRulePreviewScheduleSection(); + + cy.log('footer'); + cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_FOOTER).scrollIntoView(); + cy.get(DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_FOOTER).should('be.visible'); }); }); } diff --git a/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_preview_panel_rule_preview.ts b/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_preview_panel_rule_preview.ts index af278e6e5dd0f..5ccb58e1ef969 100644 --- a/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_preview_panel_rule_preview.ts +++ b/x-pack/plugins/security_solution/cypress/screens/expandable_flyout/alert_details_preview_panel_rule_preview.ts @@ -6,6 +6,9 @@ */ import { + RULE_PREVIEW_TITLE_TEST_ID, + RULE_PREVIEW_RULE_CREATED_BY_TEST_ID, + RULE_PREVIEW_RULE_UPDATED_BY_TEST_ID, RULE_PREVIEW_BODY_TEST_ID, RULE_PREVIEW_ABOUT_HEADER_TEST_ID, RULE_PREVIEW_ABOUT_CONTENT_TEST_ID, @@ -23,6 +26,18 @@ export const DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_SECTION = export const DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_HEADER = getDataTestSubjectSelector('previewSectionHeader'); +export const DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_TITLE = getDataTestSubjectSelector( + RULE_PREVIEW_TITLE_TEST_ID +); + +export const DOCUMENT_DETAILS_FLYOUT_CREATED_BY = getDataTestSubjectSelector( + RULE_PREVIEW_RULE_CREATED_BY_TEST_ID +); + +export const DOCUMENT_DETAILS_FLYOUT_UPDATED_BY = getDataTestSubjectSelector( + RULE_PREVIEW_RULE_UPDATED_BY_TEST_ID +); + export const DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_BODY = getDataTestSubjectSelector(RULE_PREVIEW_BODY_TEST_ID); diff --git a/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_right_panel_overview_tab.ts b/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_right_panel_overview_tab.ts index 0f39535b77134..f1f4f2a74d429 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_right_panel_overview_tab.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/expandable_flyout/alert_details_right_panel_overview_tab.ts @@ -130,6 +130,7 @@ export const clickInvestigationGuideButton = () => { * Click `Rule summary` button to open rule preview panel */ export const clickRuleSummaryButton = () => { + cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_TITLE).scrollIntoView(); cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_DESCRIPTION_TITLE) .should('be.visible') .within(() => { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx index bebd373b3c2ab..791db19a92dcd 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx @@ -22,7 +22,6 @@ import type { Filter } from '@kbn/es-query'; import { i18n as i18nTranslate } from '@kbn/i18n'; import { Routes, Route } from '@kbn/shared-ux-router'; -import { FormattedMessage } from '@kbn/i18n-react'; import { noop, omit } from 'lodash/fp'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useParams } from 'react-router-dom'; @@ -53,7 +52,6 @@ import { import { useKibana } from '../../../../common/lib/kibana'; import type { UpdateDateRange } from '../../../../common/components/charts/common'; import { FiltersGlobal } from '../../../../common/components/filters_global'; -import { FormattedDate } from '../../../../common/components/formatted_date'; import { getDetectionEngineUrl, getRuleDetailsTabUrl, @@ -81,6 +79,7 @@ import { getStepsData, redirectToDetections, } from '../../../../detections/pages/detection_engine/rules/helpers'; +import { CreatedBy, UpdatedBy } from '../../../../detections/components/rules/rule_info'; import { useGlobalTime } from '../../../../common/containers/use_global_time'; import { inputsSelectors } from '../../../../common/store/inputs'; import { setAbsoluteRangeDatePicker } from '../../../../common/store/inputs/actions'; @@ -468,33 +467,9 @@ const RuleDetailsPageComponent: React.FC = ({ () => rule ? ( [ - - ), - }} - />, + , rule?.updated_by != null ? ( - - ), - }} - /> + ) : ( '' ), diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.tsx index 6cfc11acedbef..d3d79b76b1d02 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.tsx @@ -236,6 +236,13 @@ const OverrideColumn = styled(EuiFlexItem)` text-overflow: ellipsis; `; +const OverrideValueColumn = styled(EuiFlexItem)` + width: 30px; + max-width: 30px; + overflow: hidden; + text-overflow: ellipsis; +`; + export const buildSeverityDescription = (severity: AboutStepSeverity): ListItems[] => [ { title: i18nSeverity.DEFAULT_SEVERITY, @@ -248,7 +255,7 @@ export const buildSeverityDescription = (severity: AboutStepSeverity): ListItems return { title: index === 0 ? i18nSeverity.SEVERITY_MAPPING : '', description: ( - + {`${severityItem.field}:`} - + {defaultToEmptyTag(severityItem.value)} - + @@ -293,7 +300,7 @@ export const buildRiskScoreDescription = (riskScore: AboutStepRiskScore): ListIt return { title: index === 0 ? i18nRiskScore.RISK_SCORE_MAPPING : '', description: ( - + { columns?: 'multi' | 'single' | 'singleSplit'; data: unknown; indexPatterns?: DataViewBase; schema: FormSchema; + isInPanelView?: boolean; // Option to show description list in smaller font } export const StepRuleDescriptionComponent = ({ @@ -80,6 +88,7 @@ export const StepRuleDescriptionComponent = ({ columns = 'multi', indexPatterns, schema, + isInPanelView, }: StepRuleDescriptionProps) => { const kibana = useKibana(); const license = useLicense(); @@ -126,6 +135,16 @@ export const StepRuleDescriptionComponent = ({ ); } + if (isInPanelView) { + return ( + + + + + + ); + } + return ( diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_info/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_info/index.test.tsx new file mode 100644 index 0000000000000..2d78ecbb05f1b --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_info/index.test.tsx @@ -0,0 +1,68 @@ +/* + * 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 { CreatedBy, UpdatedBy } from '.'; +import { render } from '@testing-library/react'; +import { TestProviders } from '../../../../common/mock'; + +describe('Rule related info', () => { + describe('', () => { + it('should render created correctly when by and date are passed', () => { + const { getByTestId } = render( + + + + ); + expect(getByTestId('createdBy')).toHaveTextContent( + 'Created by: test on Jan 1, 2023 @ 22:01:00.000' + ); + }); + + it('should render created unknown when created by is not available', () => { + const { getByTestId } = render( + + + + ); + expect(getByTestId('createdBy')).toHaveTextContent( + 'Created by: Unknown on Jan 1, 2023 @ 22:01:00.000' + ); + }); + }); + describe('', () => { + it('should render updated by correctly when by and date are passed', () => { + const { getByTestId } = render( + + + + ); + expect(getByTestId('updatedBy')).toHaveTextContent( + 'Updated by: test on Jan 1, 2023 @ 22:01:00.000' + ); + }); + + it('should render updated by correctly when updated by is not available', () => { + const { getByTestId } = render( + + + + ); + expect(getByTestId('updatedBy')).toHaveTextContent( + 'Updated by: Unknown on Jan 1, 2023 @ 22:01:00.000' + ); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_info/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_info/index.tsx new file mode 100644 index 0000000000000..edb9aa23275f4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_info/index.tsx @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { UNKNOWN_TEXT } from './translations'; +import { FormattedDate } from '../../../../common/components/formatted_date'; + +interface CreatedByProps { + createdBy?: string; + createdAt?: string; + ['data-test-subj']?: string; +} + +/** + * Created by and created at text that are shown on rule details and rule preview in expandable flyout + */ +export const CreatedBy: React.FC = ({ + createdBy, + createdAt, + 'data-test-subj': dataTestSubj, +}) => { + return ( +
+ + ), + }} + /> +
+ ); +}; + +CreatedBy.displayName = 'CreatedBy'; + +interface UpdatedByProps { + updatedBy?: string; + updatedAt?: string; + ['data-test-subj']?: string; +} + +/** + * Updated by and updated at text that are shown on rule details and rule preview in expandable flyout + */ +export const UpdatedBy: React.FC = ({ + updatedBy, + updatedAt, + 'data-test-subj': dataTestSubj, +}) => { + return ( +
+ + ), + }} + /> +
+ ); +}; + +UpdatedBy.displayName = 'UpdatedBy'; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_info/translations.ts b/x-pack/plugins/security_solution/public/detections/components/rules/rule_info/translations.ts new file mode 100644 index 0000000000000..5a17540b0c90d --- /dev/null +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_info/translations.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const UNKNOWN_TEXT = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleInfo.UnknownText', + { + defaultMessage: 'Unknown', + } +); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx index cd2d8a3fa6e51..7052c29ce7881 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx @@ -57,6 +57,7 @@ interface StepAboutRuleReadOnlyProps { addPadding: boolean; descriptionColumns: 'multi' | 'single' | 'singleSplit'; defaultValues: AboutStepRule; + isInPanelView?: boolean; // Option to show description list in smaller font } const ThreeQuartersContainer = styled.div` @@ -367,10 +368,16 @@ const StepAboutRuleReadOnlyComponent: FC = ({ addPadding, defaultValues: data, descriptionColumns, + isInPanelView = false, }) => { return ( - + ); }; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx index 08d9775c0b2e8..779fe5f35a820 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx @@ -116,6 +116,7 @@ interface StepDefineRuleReadOnlyProps { descriptionColumns: 'multi' | 'single' | 'singleSplit'; defaultValues: DefineStepRule; indexPattern: DataViewBase; + isInPanelView?: boolean; // Option to show description list in smaller font } export const MyLabelButton = styled(EuiButtonEmpty)` @@ -908,6 +909,7 @@ const StepDefineRuleReadOnlyComponent: FC = ({ defaultValues: data, descriptionColumns, indexPattern, + isInPanelView = false, }) => { const dataForDescription: Partial = getStepDataDataSource(data); @@ -918,6 +920,7 @@ const StepDefineRuleReadOnlyComponent: FC = ({ schema={filterRuleFieldsForType(schema, data.ruleType)} data={filterRuleFieldsForType(dataForDescription, data.ruleType)} indexPatterns={indexPattern} + isInPanelView={isInPanelView} /> ); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_schedule_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_schedule_rule/index.tsx index a4971a66972e7..30699d60912cb 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_schedule_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_schedule_rule/index.tsx @@ -27,6 +27,7 @@ interface StepScheduleRuleReadOnlyProps { addPadding: boolean; descriptionColumns: 'multi' | 'single' | 'singleSplit'; defaultValues: ScheduleStepRule; + isInPanelView?: boolean; // Option to show description list in smaller font } const StepScheduleRuleComponent: FC = ({ @@ -69,10 +70,16 @@ const StepScheduleRuleReadOnlyComponent: FC = ({ addPadding, defaultValues: data, descriptionColumns, + isInPanelView = false, }) => { return ( - + ); }; diff --git a/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview.test.tsx b/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview.test.tsx index a0dc9c64d76d9..fc9b8616d5fd7 100644 --- a/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview.test.tsx @@ -12,7 +12,16 @@ import { PreviewPanelContext } from '../context'; import { mockContextValue } from '../mocks/mock_preview_panel_context'; import { mockFlyoutContextValue } from '../../shared/mocks/mock_flyout_context'; import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; +import { ThemeProvider } from 'styled-components'; +import { getMockTheme } from '../../../common/lib/kibana/kibana_react.mock'; +import { TestProviders } from '../../../common/mock'; import { useRuleWithFallback } from '../../../detection_engine/rule_management/logic/use_rule_with_fallback'; +import { getStepsData } from '../../../detections/pages/detection_engine/rules/helpers'; +import { + mockAboutStepRule, + mockDefineStepRule, + mockScheduleStepRule, +} from '../../../detection_engine/rule_management_ui/components/rules_table/__mocks__/mock'; import { RULE_PREVIEW_BODY_TEST_ID, RULE_PREVIEW_ABOUT_HEADER_TEST_ID, @@ -21,27 +30,57 @@ import { RULE_PREVIEW_DEFINITION_CONTENT_TEST_ID, RULE_PREVIEW_SCHEDULE_HEADER_TEST_ID, RULE_PREVIEW_SCHEDULE_CONTENT_TEST_ID, + RULE_PREVIEW_ACTIONS_HEADER_TEST_ID, + RULE_PREVIEW_ACTIONS_CONTENT_TEST_ID, + RULE_PREVIEW_LOADING_TEST_ID, } from './test_ids'; +jest.mock('../../../common/lib/kibana'); + const mockUseRuleWithFallback = useRuleWithFallback as jest.Mock; jest.mock('../../../detection_engine/rule_management/logic/use_rule_with_fallback'); +const mockGetStepsData = getStepsData as jest.Mock; +jest.mock('../../../detections/pages/detection_engine/rules/helpers'); + +const mockTheme = getMockTheme({ eui: { euiColorMediumShade: '#ece' } }); + const contextValue = { ...mockContextValue, ruleId: 'rule id', }; + describe('', () => { + beforeEach(() => { + // (useAppToasts as jest.Mock).mockReturnValue(useAppToastsValueMock); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + it('should render rule preview and its sub sections', () => { mockUseRuleWithFallback.mockReturnValue({ rule: { name: 'rule name', description: 'rule description' }, }); + mockGetStepsData.mockReturnValue({ + aboutRuleData: mockAboutStepRule(), + defineRuleData: mockDefineStepRule(), + scheduleRuleData: mockScheduleStepRule(), + ruleActionsData: { actions: ['action'] }, + }); const { getByTestId } = render( - - - - - + + + + + + + + + ); + expect(getByTestId(RULE_PREVIEW_BODY_TEST_ID)).toBeInTheDocument(); expect(getByTestId(RULE_PREVIEW_ABOUT_HEADER_TEST_ID)).toBeInTheDocument(); expect(getByTestId(RULE_PREVIEW_ABOUT_CONTENT_TEST_ID)).toBeInTheDocument(); @@ -49,16 +88,63 @@ describe('', () => { expect(getByTestId(RULE_PREVIEW_DEFINITION_CONTENT_TEST_ID)).toBeInTheDocument(); expect(getByTestId(RULE_PREVIEW_SCHEDULE_HEADER_TEST_ID)).toBeInTheDocument(); expect(getByTestId(RULE_PREVIEW_SCHEDULE_CONTENT_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(RULE_PREVIEW_ACTIONS_HEADER_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(RULE_PREVIEW_ACTIONS_CONTENT_TEST_ID)).toBeInTheDocument(); + }); + + it('should not render actions if action is not available', () => { + mockUseRuleWithFallback.mockReturnValue({ + rule: { name: 'rule name', description: 'rule description' }, + }); + mockGetStepsData.mockReturnValue({ + aboutRuleData: mockAboutStepRule(), + defineRuleData: mockDefineStepRule(), + scheduleRuleData: mockScheduleStepRule(), + }); + const { queryByTestId } = render( + + + + + + + + + + ); + + expect(queryByTestId(RULE_PREVIEW_ACTIONS_HEADER_TEST_ID)).not.toBeInTheDocument(); + expect(queryByTestId(RULE_PREVIEW_ACTIONS_CONTENT_TEST_ID)).not.toBeInTheDocument(); + }); + + it('should render loading spinner when rule is loading', () => { + mockUseRuleWithFallback.mockReturnValue({ loading: true, rule: null }); + const { getByTestId } = render( + + + + + + + + + + ); + expect(getByTestId(RULE_PREVIEW_LOADING_TEST_ID)).toBeInTheDocument(); }); it('should not render rule preview when rule is null', () => { mockUseRuleWithFallback.mockReturnValue({}); const { queryByTestId } = render( - - - - - + + + + + + + + + ); expect(queryByTestId(RULE_PREVIEW_BODY_TEST_ID)).not.toBeInTheDocument(); }); diff --git a/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview.tsx b/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview.tsx index 8090fe263beb5..0a78f6a29141f 100644 --- a/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview.tsx @@ -4,40 +4,35 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - import React, { memo, useState, useEffect } from 'react'; -import { - EuiTitle, - EuiText, - EuiHorizontalRule, - EuiSpacer, - EuiPanel, - EuiLoadingSpinner, -} from '@elastic/eui'; +import { EuiText, EuiHorizontalRule, EuiSpacer, EuiPanel, EuiLoadingSpinner } from '@elastic/eui'; +import type { Rule } from '../../../detection_engine/rule_management/logic'; import { usePreviewPanelContext } from '../context'; import { ExpandableSection } from '../../right/components/expandable_section'; import { useRuleWithFallback } from '../../../detection_engine/rule_management/logic/use_rule_with_fallback'; -import type { Rule } from '../../../detection_engine/rule_management/logic'; +import { getStepsData } from '../../../detections/pages/detection_engine/rules/helpers'; +import { RulePreviewTitle } from './rule_preview_title'; +import { StepAboutRuleReadOnly } from '../../../detections/components/rules/step_about_rule'; +import { StepDefineRuleReadOnly } from '../../../detections/components/rules/step_define_rule'; +import { StepScheduleRuleReadOnly } from '../../../detections/components/rules/step_schedule_rule'; +import { StepRuleActionsReadOnly } from '../../../detections/components/rules/step_rule_actions'; import { RULE_PREVIEW_BODY_TEST_ID, RULE_PREVIEW_ABOUT_TEST_ID, RULE_PREVIEW_DEFINITION_TEST_ID, RULE_PREVIEW_SCHEDULE_TEST_ID, + RULE_PREVIEW_ACTIONS_TEST_ID, + RULE_PREVIEW_LOADING_TEST_ID, } from './test_ids'; -import { - RULE_PREVIEW_ABOUT_TEXT, - RULE_PREVIEW_DEFINITION_TEXT, - RULE_PREVIEW_SCHEDULE_TEXT, -} from './translations'; +import * as i18n from './translations'; /** * Rule summary on a preview panel on top of the right section of expandable flyout */ export const RulePreview: React.FC = memo(() => { - const { ruleId } = usePreviewPanelContext(); + const { ruleId, indexPattern } = usePreviewPanelContext(); const [rule, setRule] = useState(null); - - const { rule: maybeRule, loading } = useRuleWithFallback(ruleId ?? ''); + const { rule: maybeRule, loading: ruleLoading } = useRuleWithFallback(ruleId ?? ''); // persist rule until refresh is complete useEffect(() => { @@ -46,42 +41,88 @@ export const RulePreview: React.FC = memo(() => { } }, [maybeRule]); - if (loading) { - return ; - } + const { aboutRuleData, defineRuleData, scheduleRuleData, ruleActionsData } = + rule != null + ? getStepsData({ rule, detailsView: true }) + : { + aboutRuleData: null, + defineRuleData: null, + scheduleRuleData: null, + ruleActionsData: null, + }; + + const hasNotificationActions = Boolean(ruleActionsData?.actions?.length); + const hasResponseActions = Boolean(ruleActionsData?.responseActions?.length); + const hasActions = ruleActionsData != null && (hasNotificationActions || hasResponseActions); return rule ? ( - - -
{rule.name}
-
- + + + {rule.description} - {'About'} - - - - {'Definition'} - - - - {'Schedule'} + {aboutRuleData && ( + + )} + + {defineRuleData && ( + <> + + + + + + )} + {scheduleRuleData && ( + <> + + + + + + )} + {hasActions && ( + + + + )} + ) : ruleLoading ? ( + ) : null; }); diff --git a/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview_title.test.tsx b/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview_title.test.tsx new file mode 100644 index 0000000000000..589d0d4e3b456 --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview_title.test.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { RulePreviewTitle } from './rule_preview_title'; +import { mockFlyoutContextValue } from '../../shared/mocks/mock_flyout_context'; +import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context'; +import { TestProviders } from '../../../common/mock'; +import type { Rule } from '../../../detection_engine/rule_management/logic'; +import { + RULE_PREVIEW_TITLE_TEST_ID, + RULE_PREVIEW_RULE_CREATED_BY_TEST_ID, + RULE_PREVIEW_RULE_UPDATED_BY_TEST_ID, +} from './test_ids'; + +const defaultProps = { + rule: { id: 'id' } as Rule, +}; + +describe('', () => { + it('should render title and its components', () => { + const { getByTestId } = render( + + + + + + ); + expect(getByTestId(RULE_PREVIEW_TITLE_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(RULE_PREVIEW_RULE_CREATED_BY_TEST_ID)).toBeInTheDocument(); + expect(getByTestId(RULE_PREVIEW_RULE_UPDATED_BY_TEST_ID)).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview_title.tsx b/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview_title.tsx new file mode 100644 index 0000000000000..8a937b5a727af --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview_title.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 from 'react'; +import { EuiTitle, EuiText, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import type { Rule } from '../../../detection_engine/rule_management/logic'; +import { CreatedBy, UpdatedBy } from '../../../detections/components/rules/rule_info'; +import { + RULE_PREVIEW_TITLE_TEST_ID, + RULE_PREVIEW_RULE_CREATED_BY_TEST_ID, + RULE_PREVIEW_RULE_UPDATED_BY_TEST_ID, +} from './test_ids'; + +interface RulePreviewTitleProps { + /** + * Rule object that represents relevant information about a rule + */ + rule: Rule; +} + +/** + * Title component that shows basic information of a rule. This is displayed above rule preview body in rule preview panel + */ +export const RulePreviewTitle: React.FC = ({ rule }) => { + return ( +
+ +
{rule.name}
+
+ + + + + + + + + + + + + +
+ ); +}; + +RulePreviewTitle.displayName = 'RulePreviewTitle'; 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 175992ba45200..32a26d3f87db9 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 @@ -9,6 +9,12 @@ import { CONTENT_TEST_ID, HEADER_TEST_ID } from '../../right/components/expandab /* Rule preview */ +export const RULE_PREVIEW_TITLE_TEST_ID = 'securitySolutionDocumentDetailsFlyoutRulePreviewTitle'; +export const RULE_PREVIEW_RULE_CREATED_BY_TEST_ID = + 'securitySolutionDocumentDetailsFlyoutRulePreviewCreatedByText'; +export const RULE_PREVIEW_RULE_UPDATED_BY_TEST_ID = + 'securitySolutionDocumentDetailsFlyoutRulePreviewUpdatedByText'; + export const RULE_PREVIEW_BODY_TEST_ID = 'securitySolutionDocumentDetailsFlyoutRulePreviewBody'; export const RULE_PREVIEW_ABOUT_TEST_ID = `securitySolutionDocumentDetailsFlyoutRulePreviewAboutSection`; export const RULE_PREVIEW_ABOUT_HEADER_TEST_ID = RULE_PREVIEW_ABOUT_TEST_ID + HEADER_TEST_ID; @@ -24,5 +30,12 @@ export const RULE_PREVIEW_SCHEDULE_TEST_ID = export const RULE_PREVIEW_SCHEDULE_HEADER_TEST_ID = RULE_PREVIEW_SCHEDULE_TEST_ID + HEADER_TEST_ID; export const RULE_PREVIEW_SCHEDULE_CONTENT_TEST_ID = RULE_PREVIEW_SCHEDULE_TEST_ID + CONTENT_TEST_ID; +export const RULE_PREVIEW_ACTIONS_TEST_ID = + 'securitySolutionDocumentDetailsFlyoutRulePreviewActionsSection'; +export const RULE_PREVIEW_ACTIONS_HEADER_TEST_ID = RULE_PREVIEW_ACTIONS_TEST_ID + HEADER_TEST_ID; +export const RULE_PREVIEW_ACTIONS_CONTENT_TEST_ID = RULE_PREVIEW_ACTIONS_TEST_ID + CONTENT_TEST_ID; +export const RULE_PREVIEW_LOADING_TEST_ID = + 'securitySolutionDocumentDetailsFlyoutRulePreviewLoadingSpinner'; +export const RULE_PREVIEW_HEADER_TEST_ID = 'securitySolutionDocumentDetailsFlyoutRulePreviewHeader'; export const RULE_PREVIEW_FOOTER_TEST_ID = 'securitySolutionDocumentDetailsFlyoutRulePreviewFooter'; export const RULE_PREVIEW_NAVIGATE_TO_RULE_TEST_ID = 'goToRuleDetails'; 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 dce205f9f142f..8112b796c1d39 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 @@ -26,3 +26,13 @@ export const RULE_PREVIEW_SCHEDULE_TEXT = i18n.translate( 'xpack.securitySolution.flyout.documentDetails.rulePreviewScheduleSectionText', { defaultMessage: 'Schedule' } ); + +export const RULE_PREVIEW_ACTIONS_TEXT = i18n.translate( + 'xpack.securitySolution.flyout.documentDetails.rulePreviewActionsSectionText', + { defaultMessage: 'Actions' } +); + +export const ENABLE_RULE_TEXT = i18n.translate( + 'xpack.securitySolution.flyout.documentDetails.rulePreviewEnableRuleText', + { defaultMessage: 'Enable' } +); 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 134036221fdf2..521303635c25d 100644 --- a/x-pack/plugins/security_solution/public/flyout/preview/context.tsx +++ b/x-pack/plugins/security_solution/public/flyout/preview/context.tsx @@ -6,7 +6,12 @@ */ import React, { createContext, useContext, useMemo } from 'react'; +import type { DataViewBase } from '@kbn/es-query'; 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'; export interface PreviewPanelContext { /** @@ -25,6 +30,10 @@ export interface PreviewPanelContext { * Rule id if preview is rule details */ ruleId: string; + /** + * Index pattern for rule details + */ + indexPattern: DataViewBase; } export const PreviewPanelContext = createContext(undefined); @@ -43,6 +52,12 @@ export const PreviewPanelProvider = ({ ruleId, children, }: PreviewPanelProviderProps) => { + const [{ pageName }] = useRouteSpy(); + const sourcererScope = + pageName === SecurityPageName.detections + ? SourcererScopeName.detections + : SourcererScopeName.default; + const sourcererDataView = useSourcererDataView(sourcererScope); const contextValue = useMemo( () => id && indexName && scopeId @@ -51,9 +66,10 @@ export const PreviewPanelProvider = ({ indexName, scopeId, ruleId: ruleId ?? '', + indexPattern: sourcererDataView.indexPattern, } : undefined, - [id, indexName, scopeId, ruleId] + [id, indexName, scopeId, ruleId, sourcererDataView.indexPattern] ); 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 a75e6b4890541..f06157e408a45 100644 --- a/x-pack/plugins/security_solution/public/flyout/preview/index.tsx +++ b/x-pack/plugins/security_solution/public/flyout/preview/index.tsx @@ -6,7 +6,6 @@ */ import React, { memo, useMemo } from 'react'; -import { css } from '@emotion/react'; import type { FlyoutPanelProps } from '@kbn/expandable-flyout'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { panels } from './panels'; @@ -21,7 +20,6 @@ export interface PreviewPanelProps extends FlyoutPanelProps { id: string; indexName: string; scopeId: string; - banner?: string; ruleId?: string; }; } @@ -38,14 +36,13 @@ export const PreviewPanel: React.FC> = memo(({ path } return null; } return ( - - - {previewPanel.content} - + + {previewPanel.content} {previewPanel.footer} ); 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 b9a5140ce20d3..4e9f9cc43d8ba 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 @@ -15,4 +15,5 @@ export const mockContextValue: PreviewPanelContext = { indexName: 'index', scopeId: 'scopeId', ruleId: '', + indexPattern: { fields: [], title: 'test index' }, }; 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 7813e92bf702a..b9aee26bdd577 100644 --- a/x-pack/plugins/security_solution/public/flyout/preview/panels.tsx +++ b/x-pack/plugins/security_solution/public/flyout/preview/panels.tsx @@ -27,7 +27,7 @@ export type PreviewPanelType = Array<{ /** * Footer section in the panel */ - footer: React.ReactElement; + footer?: React.ReactElement; }>; /** diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/description.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/description.tsx index c68ed0c39a26e..4f416c97fbb70 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/description.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/description.tsx @@ -61,7 +61,7 @@ export const Description: FC = () => {