[Security Solution][Document Details] Refactor alert reason and rule preview into panels (#186218)

## Summary

We want to have a preview for alert/events. Currently alert reason and
rule overview are embedded in the same preview. This PR separate them
into their own panels, so that they can be called outside of document
details if needed in the future. The `DocumentDetailPreviewPanelKey`
reference is removed, but will be used again when setting the preview
for alerts/events.

No functionality changed in this PR.

This PR is part 1 of refactoring document details code for alert
preview:

1️⃣ ➡️ separating the alert reason and rule overview into their own
panels
2️⃣ refactor left and right context to share
`DocumentDetailsPanelContext`, which will reduce the duplication for
alert preview
3️⃣ set up preview context for the actual alert/event details preview

### Checklist

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
This commit is contained in:
christineweng 2024-06-20 13:29:19 -05:00 committed by GitHub
parent e3a85ddb39
commit 004daf70e7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 430 additions and 365 deletions

View file

@ -7,11 +7,11 @@
import React from 'react';
import { render } from '@testing-library/react';
import { PreviewPanelContext } from '../context';
import { mockContextValue } from '../mocks/mock_context';
import { ALERT_REASON_PREVIEW_BODY_TEST_ID } from './test_ids';
import { AlertReasonPreview } from './alert_reason_preview';
import { TestProviders } from '../../../../common/mock';
import { AlertReasonPanelContext } from './context';
import { mockContextValue } from './mocks/mock_context';
import { ALERT_REASON_BODY_TEST_ID } from './test_ids';
import { AlertReason } from './alert_reason';
import { TestProviders } from '../../../common/mock';
const panelContextValue = {
...mockContextValue,
@ -19,23 +19,23 @@ const panelContextValue = {
const NO_DATA_MESSAGE = 'There was an error displaying data.';
describe('<AlertReasonPreview />', () => {
describe('<AlertReason />', () => {
it('should render alert reason preview', () => {
const { getByTestId } = render(
<PreviewPanelContext.Provider value={panelContextValue}>
<AlertReasonPreview />
</PreviewPanelContext.Provider>,
<AlertReasonPanelContext.Provider value={panelContextValue}>
<AlertReason />
</AlertReasonPanelContext.Provider>,
{ wrapper: TestProviders }
);
expect(getByTestId(ALERT_REASON_PREVIEW_BODY_TEST_ID)).toBeInTheDocument();
expect(getByTestId(ALERT_REASON_PREVIEW_BODY_TEST_ID)).toHaveTextContent('Alert reason');
expect(getByTestId(ALERT_REASON_BODY_TEST_ID)).toBeInTheDocument();
expect(getByTestId(ALERT_REASON_BODY_TEST_ID)).toHaveTextContent('Alert reason');
});
it('should render no data message if alert reason is not available', () => {
const { getByText } = render(
<PreviewPanelContext.Provider value={{} as unknown as PreviewPanelContext}>
<AlertReasonPreview />
</PreviewPanelContext.Provider>,
<AlertReasonPanelContext.Provider value={{} as unknown as AlertReasonPanelContext}>
<AlertReason />
</AlertReasonPanelContext.Provider>,
{ wrapper: TestProviders }
);
expect(getByText(NO_DATA_MESSAGE)).toBeInTheDocument();

View file

@ -10,24 +10,24 @@ import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui';
import styled from '@emotion/styled';
import { euiThemeVars } from '@kbn/ui-theme';
import { FormattedMessage } from '@kbn/i18n-react';
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';
import { FlyoutError } from '../../../shared/components/flyout_error';
import { ALERT_REASON_BODY_TEST_ID } from './test_ids';
import { useAlertReasonPanelContext } from './context';
import { getRowRenderer } from '../../../timelines/components/timeline/body/renderers/get_row_renderer';
import { defaultRowRenderers } from '../../../timelines/components/timeline/body/renderers';
import { FlyoutError } from '../../shared/components/flyout_error';
const ReasonPreviewContainerWrapper = styled.div`
const ReasonContainerWrapper = styled.div`
overflow-x: auto;
padding-block: ${euiThemeVars.euiSizeS};
`;
const ReasonPreviewContainer = styled.div``;
const ReasonContainer = styled.div``;
/**
* Alert reason renderer on a preview panel on top of the right section of expandable flyout
*/
export const AlertReasonPreview: React.FC = () => {
const { dataAsNestedObject, scopeId } = usePreviewPanelContext();
export const AlertReason: React.FC = () => {
const { dataAsNestedObject, scopeId } = useAlertReasonPanelContext();
const renderer = useMemo(
() => getRowRenderer({ data: dataAsNestedObject, rowRenderers: defaultRowRenderers }),
@ -56,7 +56,7 @@ export const AlertReasonPreview: React.FC = () => {
}
return (
<EuiPanel hasShadow={false} data-test-subj={ALERT_REASON_PREVIEW_BODY_TEST_ID}>
<EuiPanel hasShadow={false} data-test-subj={ALERT_REASON_BODY_TEST_ID}>
<EuiTitle>
<h6>
<FormattedMessage
@ -66,13 +66,11 @@ export const AlertReasonPreview: React.FC = () => {
</h6>
</EuiTitle>
<EuiSpacer size="m" />
<ReasonPreviewContainerWrapper>
<ReasonPreviewContainer className={'eui-displayInlineBlock'}>
{rowRenderer}
</ReasonPreviewContainer>
</ReasonPreviewContainerWrapper>
<ReasonContainerWrapper>
<ReasonContainer className={'eui-displayInlineBlock'}>{rowRenderer}</ReasonContainer>
</ReasonContainerWrapper>
</EuiPanel>
);
};
AlertReasonPreview.displayName = 'AlertReasonPreview';
AlertReason.displayName = 'AlertReason';

View file

@ -6,14 +6,13 @@
*/
import React, { createContext, memo, useContext, useMemo } from 'react';
import type { DataViewBase } from '@kbn/es-query';
import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs';
import { useEventDetails } from '../shared/hooks/use_event_details';
import { FlyoutError } from '../../shared/components/flyout_error';
import { FlyoutLoading } from '../../shared/components/flyout_loading';
import type { PreviewPanelProps } from '.';
import type { AlertReasonPanelProps } from '.';
export interface PreviewPanelContext {
export interface AlertReasonPanelContext {
/**
* Id of the document
*/
@ -26,32 +25,26 @@ export interface PreviewPanelContext {
* Maintain backwards compatibility // TODO remove when possible
*/
scopeId: string;
/**
* Rule id if preview is rule details
*/
ruleId: string;
/**
* Index pattern for rule details
*/
indexPattern: DataViewBase;
/**
* An object with top level fields from the ECS object
*/
dataAsNestedObject: Ecs;
}
export const PreviewPanelContext = createContext<PreviewPanelContext | undefined>(undefined);
export const AlertReasonPanelContext = createContext<AlertReasonPanelContext | undefined>(
undefined
);
export type PreviewPanelProviderProps = {
export type AlertReasonPanelProviderProps = {
/**
* React components to render
*/
children: React.ReactNode;
} & Partial<PreviewPanelProps['params']>;
} & Partial<AlertReasonPanelProps['params']>;
export const PreviewPanelProvider = memo(
({ id, indexName, scopeId, ruleId, children }: PreviewPanelProviderProps) => {
const { dataAsNestedObject, indexPattern, loading } = useEventDetails({
export const AlertReasonPanelProvider = memo(
({ id, indexName, scopeId, children }: AlertReasonPanelProviderProps) => {
const { dataAsNestedObject, loading } = useEventDetails({
eventId: id,
indexName,
});
@ -63,12 +56,10 @@ export const PreviewPanelProvider = memo(
eventId: id,
indexName,
scopeId,
ruleId: ruleId ?? '',
indexPattern,
dataAsNestedObject,
}
: undefined,
[id, indexName, scopeId, ruleId, indexPattern, dataAsNestedObject]
[id, indexName, scopeId, dataAsNestedObject]
);
if (loading) {
@ -80,18 +71,22 @@ export const PreviewPanelProvider = memo(
}
return (
<PreviewPanelContext.Provider value={contextValue}>{children}</PreviewPanelContext.Provider>
<AlertReasonPanelContext.Provider value={contextValue}>
{children}
</AlertReasonPanelContext.Provider>
);
}
);
PreviewPanelProvider.displayName = 'PreviewPanelProvider';
AlertReasonPanelProvider.displayName = 'AlertReasonPanelProvider';
export const usePreviewPanelContext = (): PreviewPanelContext => {
const contextValue = useContext(PreviewPanelContext);
export const useAlertReasonPanelContext = (): AlertReasonPanelContext => {
const contextValue = useContext(AlertReasonPanelContext);
if (!contextValue) {
throw new Error('PreviewPanelContext can only be used within PreviewPanelContext provider');
throw new Error(
'AlertReasonPanelContext can only be used within AlertReasonPanelContext provider'
);
}
return contextValue;

View file

@ -0,0 +1,43 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { memo } from 'react';
import type { FlyoutPanelProps, PanelPath } from '@kbn/expandable-flyout';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import type { DocumentDetailsAlertReasonPanelKey } from '../shared/constants/panel_keys';
import { AlertReason } from './alert_reason';
export interface AlertReasonPanelProps extends FlyoutPanelProps {
key: typeof DocumentDetailsAlertReasonPanelKey;
path?: PanelPath;
params?: {
id: string;
indexName: string;
scopeId: string;
ruleId?: string;
};
}
/**
* Preview panel to be displayed on top of the document details expandable flyout right section
*/
export const AlertReasonPanel: React.FC = memo(() => {
return (
<EuiFlexGroup
justifyContent="spaceBetween"
direction="column"
gutterSize="none"
style={{ height: '100%' }}
>
<EuiFlexItem style={{ marginTop: '-15px' }}>
<AlertReason />
</EuiFlexItem>
</EuiFlexGroup>
);
});
AlertReasonPanel.displayName = 'AlertReasonPanel';

View file

@ -6,16 +6,14 @@
*/
import { mockDataAsNestedObject } from '../../shared/mocks/mock_data_as_nested_object';
import type { PreviewPanelContext } from '../context';
import type { AlertReasonPanelContext } from '../context';
/**
* Mock contextValue for right panel context
*/
export const mockContextValue: PreviewPanelContext = {
export const mockContextValue: AlertReasonPanelContext = {
eventId: 'eventId',
indexName: 'index',
scopeId: 'scopeId',
ruleId: '',
indexPattern: { fields: [], title: 'test index' },
dataAsNestedObject: mockDataAsNestedObject,
};

View file

@ -0,0 +1,10 @@
/*
* 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 { PREFIX } from '../../shared/test_ids';
export const ALERT_REASON_BODY_TEST_ID = `${PREFIX}AlertReasonBody` as const;

View file

@ -1,39 +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 { PREFIX } from '../../../shared/test_ids';
import { CONTENT_TEST_ID, HEADER_TEST_ID } from '../../right/components/expandable_section';
/* Rule preview */
const RULE_PREVIEW_TEST_ID = `${PREFIX}RulePreview` as const;
export const RULE_PREVIEW_TITLE_TEST_ID = `${RULE_PREVIEW_TEST_ID}RulePreviewTitle` as const;
export const RULE_PREVIEW_RULE_TITLE_SUPPRESSED_TEST_ID =
`${RULE_PREVIEW_TITLE_TEST_ID}Suppressed` as const;
export const RULE_PREVIEW_RULE_CREATED_BY_TEST_ID = `${RULE_PREVIEW_TEST_ID}CreatedByText` as const;
export const RULE_PREVIEW_RULE_UPDATED_BY_TEST_ID = `${RULE_PREVIEW_TEST_ID}UpdatedByText` as const;
export const RULE_PREVIEW_BODY_TEST_ID = `${RULE_PREVIEW_TEST_ID}Body` as const;
export const RULE_PREVIEW_ABOUT_TEST_ID = `${RULE_PREVIEW_TEST_ID}AboutSection` as const;
export const RULE_PREVIEW_ABOUT_HEADER_TEST_ID = RULE_PREVIEW_ABOUT_TEST_ID + HEADER_TEST_ID;
export const RULE_PREVIEW_ABOUT_CONTENT_TEST_ID = RULE_PREVIEW_ABOUT_TEST_ID + CONTENT_TEST_ID;
export const RULE_PREVIEW_DEFINITION_TEST_ID = `${RULE_PREVIEW_TEST_ID}DefinitionSection` as const;
export const RULE_PREVIEW_DEFINITION_HEADER_TEST_ID =
RULE_PREVIEW_DEFINITION_TEST_ID + HEADER_TEST_ID;
export const RULE_PREVIEW_DEFINITION_CONTENT_TEST_ID =
RULE_PREVIEW_DEFINITION_TEST_ID + CONTENT_TEST_ID;
export const RULE_PREVIEW_SCHEDULE_TEST_ID = `${RULE_PREVIEW_TEST_ID}ScheduleSection` as const;
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 = `${RULE_PREVIEW_TEST_ID}ActionsSection` as const;
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 = `${RULE_PREVIEW_TEST_ID}Loading` as const;
export const RULE_PREVIEW_FOOTER_TEST_ID = `${RULE_PREVIEW_TEST_ID}Footer` as const;
export const RULE_PREVIEW_NAVIGATE_TO_RULE_TEST_ID =
`${RULE_PREVIEW_FOOTER_TEST_ID}LinkToRuleDetails` as const;
export const ALERT_REASON_PREVIEW_BODY_TEST_ID = `${PREFIX}AlertReasonPreviewBody` as const;

View file

@ -1,53 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { memo, useMemo } from 'react';
import type { FlyoutPanelProps, PanelPath } from '@kbn/expandable-flyout';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import type { DocumentDetailsPreviewPanelKey } from '../shared/constants/panel_keys';
import { panels } from './panels';
export type PreviewPanelPaths = 'rule-preview' | 'alert-reason-preview';
export const RulePreviewPanel: PreviewPanelPaths = 'rule-preview';
export const AlertReasonPreviewPanel: PreviewPanelPaths = 'alert-reason-preview';
export interface PreviewPanelProps extends FlyoutPanelProps {
key: typeof DocumentDetailsPreviewPanelKey;
path?: PanelPath;
params?: {
id: string;
indexName: string;
scopeId: string;
ruleId?: string;
};
}
/**
* Preview panel to be displayed on top of the document details expandable flyout right section
*/
export const PreviewPanel: React.FC<Partial<PreviewPanelProps>> = memo(({ path }) => {
const previewPanel = useMemo(() => {
return path ? panels.find((panel) => panel.id === path.tab) : null;
}, [path]);
if (!previewPanel) {
return null;
}
return (
<EuiFlexGroup
justifyContent="spaceBetween"
direction="column"
gutterSize="none"
style={{ height: '100%' }}
>
<EuiFlexItem style={{ marginTop: '-15px' }}>{previewPanel.content}</EuiFlexItem>
<EuiFlexItem grow={false}>{previewPanel.footer}</EuiFlexItem>
</EuiFlexGroup>
);
});
PreviewPanel.displayName = 'PreviewPanel';

View file

@ -1,42 +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 { AlertReasonPreview } from './components/alert_reason_preview';
import type { PreviewPanelPaths } from '.';
import { RulePreview } from './components/rule_preview';
import { RulePreviewFooter } from './components/rule_preview_footer';
export type PreviewPanelType = Array<{
/**
* Id of the preview panel
*/
id: PreviewPanelPaths;
/**
* Main body component to be rendered in the panel
*/
content: React.ReactElement;
/**
* Footer section in the panel
*/
footer?: React.ReactElement;
}>;
/**
* Array of all preview panels
*/
export const panels: PreviewPanelType = [
{
id: 'rule-preview',
content: <RulePreview />,
footer: <RulePreviewFooter />,
},
{
id: 'alert-reason-preview',
content: <AlertReasonPreview />,
},
];

View file

@ -13,13 +13,12 @@ import {
RULE_SUMMARY_BUTTON_TEST_ID,
ALERT_DESCRIPTION_DETAILS_TEST_ID,
} from './test_ids';
import { AlertDescription } from './alert_description';
import { AlertDescription, RULE_OVERVIEW_BANNER } from './alert_description';
import { RightPanelContext } from '../context';
import { mockGetFieldsData } from '../../shared/mocks/mock_get_fields_data';
import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common';
import { DocumentDetailsPreviewPanelKey } from '../../shared/constants/panel_keys';
import { DocumentDetailsRuleOverviewPanelKey } from '../../shared/constants/panel_keys';
import { TestProviders } from '../../../../common/mock';
import { i18n } from '@kbn/i18n';
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
import type { ExpandableFlyoutApi } from '@kbn/expandable-flyout';
import { createTelemetryServiceMock } from '../../../../common/lib/telemetry/telemetry_service.mock';
@ -151,20 +150,12 @@ describe('<AlertDescription />', () => {
getByTestId(RULE_SUMMARY_BUTTON_TEST_ID).click();
expect(flyoutContextValue.openPreviewPanel).toHaveBeenCalledWith({
id: DocumentDetailsPreviewPanelKey,
path: { tab: 'rule-preview' },
id: DocumentDetailsRuleOverviewPanelKey,
params: {
id: panelContext.eventId,
indexName: panelContext.indexName,
scopeId: panelContext.scopeId,
banner: {
title: i18n.translate(
'xpack.securitySolution.flyout.right.about.description.rulePreviewTitle',
{ defaultMessage: 'Preview rule details' }
),
backgroundColor: 'warning',
textColor: 'warning',
},
banner: RULE_OVERVIEW_BANNER,
ruleId: ruleUuid.values[0],
},
});

View file

@ -21,8 +21,15 @@ import {
ALERT_DESCRIPTION_TITLE_TEST_ID,
RULE_SUMMARY_BUTTON_TEST_ID,
} from './test_ids';
import { DocumentDetailsPreviewPanelKey } from '../../shared/constants/panel_keys';
import { type PreviewPanelProps, RulePreviewPanel } from '../../preview';
import { DocumentDetailsRuleOverviewPanelKey } from '../../shared/constants/panel_keys';
export const RULE_OVERVIEW_BANNER = {
title: i18n.translate('xpack.securitySolution.flyout.right.about.description.rulePreviewTitle', {
defaultMessage: 'Preview rule details',
}),
backgroundColor: 'warning',
textColor: 'warning',
};
/**
* Displays the rule description of a signal document.
@ -36,22 +43,13 @@ export const AlertDescription: FC = () => {
);
const { openPreviewPanel } = useExpandableFlyoutApi();
const openRulePreview = useCallback(() => {
const PreviewPanelRulePreview: PreviewPanelProps['path'] = { tab: RulePreviewPanel };
openPreviewPanel({
id: DocumentDetailsPreviewPanelKey,
path: PreviewPanelRulePreview,
id: DocumentDetailsRuleOverviewPanelKey,
params: {
id: eventId,
indexName,
scopeId,
banner: {
title: i18n.translate(
'xpack.securitySolution.flyout.right.about.description.rulePreviewTitle',
{ defaultMessage: 'Preview rule details' }
),
backgroundColor: 'warning',
textColor: 'warning',
},
banner: RULE_OVERVIEW_BANNER,
ruleId,
},
});

View file

@ -9,13 +9,12 @@ import React from 'react';
import { render } from '@testing-library/react';
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
import { REASON_DETAILS_PREVIEW_BUTTON_TEST_ID, REASON_TITLE_TEST_ID } from './test_ids';
import { Reason } from './reason';
import { Reason, ALERT_REASON_BANNER } from './reason';
import { RightPanelContext } from '../context';
import { mockGetFieldsData } from '../../shared/mocks/mock_get_fields_data';
import { mockDataFormattedForFieldBrowser } from '../../shared/mocks/mock_data_formatted_for_field_browser';
import { DocumentDetailsPreviewPanelKey } from '../../shared/constants/panel_keys';
import { DocumentDetailsAlertReasonPanelKey } from '../../shared/constants/panel_keys';
import { TestProviders } from '../../../../common/mock';
import { i18n } from '@kbn/i18n';
import { type ExpandableFlyoutApi, useExpandableFlyoutApi } from '@kbn/expandable-flyout';
import { createTelemetryServiceMock } from '../../../../common/lib/telemetry/telemetry_service.mock';
@ -105,22 +104,12 @@ describe('<Reason />', () => {
getByTestId(REASON_DETAILS_PREVIEW_BUTTON_TEST_ID).click();
expect(flyoutContextValue.openPreviewPanel).toHaveBeenCalledWith({
id: DocumentDetailsPreviewPanelKey,
path: { tab: 'alert-reason-preview' },
id: DocumentDetailsAlertReasonPanelKey,
params: {
id: panelContextValue.eventId,
indexName: panelContextValue.indexName,
scopeId: panelContextValue.scopeId,
banner: {
title: i18n.translate(
'xpack.securitySolution.flyout.right.about.reason.alertReasonPreviewTitle',
{
defaultMessage: 'Preview alert reason',
}
),
backgroundColor: 'warning',
textColor: 'warning',
},
banner: ALERT_REASON_BANNER,
},
});
});

View file

@ -14,8 +14,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { useKibana } from '../../../../common/lib/kibana';
import { getField } from '../../shared/utils';
import { DocumentDetailsPreviewPanelKey } from '../../shared/constants/panel_keys';
import { AlertReasonPreviewPanel } from '../../preview';
import { DocumentDetailsAlertReasonPanelKey } from '../../shared/constants/panel_keys';
import {
REASON_DETAILS_PREVIEW_BUTTON_TEST_ID,
REASON_DETAILS_TEST_ID,
@ -24,6 +23,17 @@ import {
import { useBasicDataFromDetailsData } from '../../../../timelines/components/side_panel/event_details/helpers';
import { useRightPanelContext } from '../context';
export const ALERT_REASON_BANNER = {
title: i18n.translate(
'xpack.securitySolution.flyout.right.about.reason.alertReasonPreviewTitle',
{
defaultMessage: 'Preview alert reason',
}
),
backgroundColor: 'warning',
textColor: 'warning',
};
/**
* Displays the information provided by the rowRenderer. Supports multiple types of documents.
*/
@ -37,22 +47,12 @@ export const Reason: FC = () => {
const { openPreviewPanel } = useExpandableFlyoutApi();
const openRulePreview = useCallback(() => {
openPreviewPanel({
id: DocumentDetailsPreviewPanelKey,
path: { tab: AlertReasonPreviewPanel },
id: DocumentDetailsAlertReasonPanelKey,
params: {
id: eventId,
indexName,
scopeId,
banner: {
title: i18n.translate(
'xpack.securitySolution.flyout.right.about.reason.alertReasonPreviewTitle',
{
defaultMessage: 'Preview alert reason',
}
),
backgroundColor: 'warning',
textColor: 'warning',
},
banner: ALERT_REASON_BANNER,
},
});
telemetry.reportDetailsFlyoutOpened({

View file

@ -9,19 +9,19 @@ import { render } from '@testing-library/react';
import React from 'react';
import { TestProviders } from '../../../../common/mock';
import { mockContextValue } from '../mocks/mock_context';
import { PreviewPanelContext } from '../context';
import { RULE_PREVIEW_FOOTER_TEST_ID, RULE_PREVIEW_NAVIGATE_TO_RULE_TEST_ID } from './test_ids';
import { RulePreviewFooter } from './rule_preview_footer';
import { RuleOverviewPanelContext } from '../context';
import { RULE_OVERVIEW_FOOTER_TEST_ID, RULE_OVERVIEW_NAVIGATE_TO_RULE_TEST_ID } from './test_ids';
import { RuleFooter } from './footer';
import { useRuleDetailsLink } from '../../shared/hooks/use_rule_details_link';
jest.mock('../../shared/hooks/use_rule_details_link');
const renderRulePreviewFooter = (contextValue: PreviewPanelContext) =>
const renderRulePreviewFooter = (contextValue: RuleOverviewPanelContext) =>
render(
<TestProviders>
<PreviewPanelContext.Provider value={contextValue}>
<RulePreviewFooter />
</PreviewPanelContext.Provider>
<RuleOverviewPanelContext.Provider value={contextValue}>
<RuleFooter />
</RuleOverviewPanelContext.Provider>
</TestProviders>
);
@ -31,9 +31,9 @@ describe('<RulePreviewFooter />', () => {
const { getByTestId } = renderRulePreviewFooter(mockContextValue);
expect(getByTestId(RULE_PREVIEW_FOOTER_TEST_ID)).toBeInTheDocument();
expect(getByTestId(RULE_PREVIEW_NAVIGATE_TO_RULE_TEST_ID)).toBeInTheDocument();
expect(getByTestId(RULE_PREVIEW_NAVIGATE_TO_RULE_TEST_ID)).toHaveTextContent(
expect(getByTestId(RULE_OVERVIEW_FOOTER_TEST_ID)).toBeInTheDocument();
expect(getByTestId(RULE_OVERVIEW_NAVIGATE_TO_RULE_TEST_ID)).toBeInTheDocument();
expect(getByTestId(RULE_OVERVIEW_NAVIGATE_TO_RULE_TEST_ID)).toHaveTextContent(
'Show rule details'
);
});
@ -43,7 +43,7 @@ describe('<RulePreviewFooter />', () => {
const { queryByTestId } = renderRulePreviewFooter(mockContextValue);
expect(queryByTestId(RULE_PREVIEW_FOOTER_TEST_ID)).not.toBeInTheDocument();
expect(queryByTestId(RULE_PREVIEW_NAVIGATE_TO_RULE_TEST_ID)).not.toBeInTheDocument();
expect(queryByTestId(RULE_OVERVIEW_FOOTER_TEST_ID)).not.toBeInTheDocument();
expect(queryByTestId(RULE_OVERVIEW_NAVIGATE_TO_RULE_TEST_ID)).not.toBeInTheDocument();
});
});

View file

@ -8,26 +8,26 @@
import React, { memo } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { usePreviewPanelContext } from '../context';
import { useRuleOverviewPanelContext } from '../context';
import { FlyoutFooter } from '../../../shared/components/flyout_footer';
import { RULE_PREVIEW_FOOTER_TEST_ID, RULE_PREVIEW_NAVIGATE_TO_RULE_TEST_ID } from './test_ids';
import { RULE_OVERVIEW_FOOTER_TEST_ID, RULE_OVERVIEW_NAVIGATE_TO_RULE_TEST_ID } from './test_ids';
import { useRuleDetailsLink } from '../../shared/hooks/use_rule_details_link';
/**
* Footer in rule preview panel
*/
export const RulePreviewFooter = memo(() => {
const { ruleId } = usePreviewPanelContext();
export const RuleFooter = memo(() => {
const { ruleId } = useRuleOverviewPanelContext();
const href = useRuleDetailsLink({ ruleId });
return href ? (
<FlyoutFooter data-test-subj={RULE_PREVIEW_FOOTER_TEST_ID}>
<FlyoutFooter data-test-subj={RULE_OVERVIEW_FOOTER_TEST_ID}>
<EuiFlexGroup justifyContent="center">
<EuiFlexItem grow={false}>
<EuiLink
href={href}
target="_blank"
data-test-subj={RULE_PREVIEW_NAVIGATE_TO_RULE_TEST_ID}
data-test-subj={RULE_OVERVIEW_NAVIGATE_TO_RULE_TEST_ID}
>
{i18n.translate('xpack.securitySolution.flyout.preview.rule.viewDetailsLabel', {
defaultMessage: 'Show rule details',
@ -39,4 +39,4 @@ export const RulePreviewFooter = memo(() => {
) : null;
});
RulePreviewFooter.displayName = 'RulePreviewFooter';
RuleFooter.displayName = 'RuleFooter';

View file

@ -7,8 +7,8 @@
import React from 'react';
import { act, render } from '@testing-library/react';
import { RulePreview } from './rule_preview';
import { PreviewPanelContext } from '../context';
import { RuleOverview } from './rule_overview';
import { RuleOverviewPanelContext } from '../context';
import { mockContextValue } from '../mocks/mock_context';
import { ThemeProvider } from 'styled-components';
import { getMockTheme } from '../../../../common/lib/kibana/kibana_react.mock';
@ -23,16 +23,16 @@ import {
} from '../../../../detection_engine/rule_management_ui/components/rules_table/__mocks__/mock';
import { useGetSavedQuery } from '../../../../detections/pages/detection_engine/rules/use_get_saved_query';
import {
RULE_PREVIEW_BODY_TEST_ID,
RULE_PREVIEW_ABOUT_HEADER_TEST_ID,
RULE_PREVIEW_ABOUT_CONTENT_TEST_ID,
RULE_PREVIEW_DEFINITION_HEADER_TEST_ID,
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,
RULE_OVERVIEW_BODY_TEST_ID,
RULE_OVERVIEW_ABOUT_HEADER_TEST_ID,
RULE_OVERVIEW_ABOUT_CONTENT_TEST_ID,
RULE_OVERVIEW_DEFINITION_HEADER_TEST_ID,
RULE_OVERVIEW_DEFINITION_CONTENT_TEST_ID,
RULE_OVERVIEW_SCHEDULE_HEADER_TEST_ID,
RULE_OVERVIEW_SCHEDULE_CONTENT_TEST_ID,
RULE_OVERVIEW_ACTIONS_HEADER_TEST_ID,
RULE_OVERVIEW_ACTIONS_CONTENT_TEST_ID,
RULE_OVERVIEW_LOADING_TEST_ID,
} from './test_ids';
jest.mock('../../../../common/lib/kibana');
@ -58,9 +58,9 @@ const renderRulePreview = () =>
<TestProviders>
<ThemeProvider theme={mockTheme}>
<TestProvider>
<PreviewPanelContext.Provider value={contextValue}>
<RulePreview />
</PreviewPanelContext.Provider>
<RuleOverviewPanelContext.Provider value={contextValue}>
<RuleOverview />
</RuleOverviewPanelContext.Provider>
</TestProvider>
</ThemeProvider>
</TestProviders>
@ -88,19 +88,19 @@ describe('<RulePreview />', () => {
const { getByTestId } = renderRulePreview();
await act(async () => {
expect(getByTestId(RULE_PREVIEW_BODY_TEST_ID)).toBeInTheDocument();
expect(getByTestId(RULE_PREVIEW_ABOUT_HEADER_TEST_ID)).toBeInTheDocument();
expect(getByTestId(RULE_PREVIEW_ABOUT_HEADER_TEST_ID)).toHaveTextContent('About');
expect(getByTestId(RULE_PREVIEW_ABOUT_CONTENT_TEST_ID)).toBeInTheDocument();
expect(getByTestId(RULE_PREVIEW_DEFINITION_HEADER_TEST_ID)).toBeInTheDocument();
expect(getByTestId(RULE_PREVIEW_DEFINITION_HEADER_TEST_ID)).toHaveTextContent('Definition');
expect(getByTestId(RULE_PREVIEW_DEFINITION_CONTENT_TEST_ID)).toBeInTheDocument();
expect(getByTestId(RULE_PREVIEW_SCHEDULE_HEADER_TEST_ID)).toBeInTheDocument();
expect(getByTestId(RULE_PREVIEW_SCHEDULE_HEADER_TEST_ID)).toHaveTextContent('Schedule');
expect(getByTestId(RULE_PREVIEW_SCHEDULE_CONTENT_TEST_ID)).toBeInTheDocument();
expect(getByTestId(RULE_PREVIEW_ACTIONS_HEADER_TEST_ID)).toBeInTheDocument();
expect(getByTestId(RULE_PREVIEW_ACTIONS_HEADER_TEST_ID)).toHaveTextContent('Actions');
expect(getByTestId(RULE_PREVIEW_ACTIONS_CONTENT_TEST_ID)).toBeInTheDocument();
expect(getByTestId(RULE_OVERVIEW_BODY_TEST_ID)).toBeInTheDocument();
expect(getByTestId(RULE_OVERVIEW_ABOUT_HEADER_TEST_ID)).toBeInTheDocument();
expect(getByTestId(RULE_OVERVIEW_ABOUT_HEADER_TEST_ID)).toHaveTextContent('About');
expect(getByTestId(RULE_OVERVIEW_ABOUT_CONTENT_TEST_ID)).toBeInTheDocument();
expect(getByTestId(RULE_OVERVIEW_DEFINITION_HEADER_TEST_ID)).toBeInTheDocument();
expect(getByTestId(RULE_OVERVIEW_DEFINITION_HEADER_TEST_ID)).toHaveTextContent('Definition');
expect(getByTestId(RULE_OVERVIEW_DEFINITION_CONTENT_TEST_ID)).toBeInTheDocument();
expect(getByTestId(RULE_OVERVIEW_SCHEDULE_HEADER_TEST_ID)).toBeInTheDocument();
expect(getByTestId(RULE_OVERVIEW_SCHEDULE_HEADER_TEST_ID)).toHaveTextContent('Schedule');
expect(getByTestId(RULE_OVERVIEW_SCHEDULE_CONTENT_TEST_ID)).toBeInTheDocument();
expect(getByTestId(RULE_OVERVIEW_ACTIONS_HEADER_TEST_ID)).toBeInTheDocument();
expect(getByTestId(RULE_OVERVIEW_ACTIONS_HEADER_TEST_ID)).toHaveTextContent('Actions');
expect(getByTestId(RULE_OVERVIEW_ACTIONS_CONTENT_TEST_ID)).toBeInTheDocument();
});
});
@ -116,8 +116,8 @@ describe('<RulePreview />', () => {
const { queryByTestId } = renderRulePreview();
await act(async () => {
expect(queryByTestId(RULE_PREVIEW_ACTIONS_HEADER_TEST_ID)).not.toBeInTheDocument();
expect(queryByTestId(RULE_PREVIEW_ACTIONS_CONTENT_TEST_ID)).not.toBeInTheDocument();
expect(queryByTestId(RULE_OVERVIEW_ACTIONS_HEADER_TEST_ID)).not.toBeInTheDocument();
expect(queryByTestId(RULE_OVERVIEW_ACTIONS_CONTENT_TEST_ID)).not.toBeInTheDocument();
});
});
@ -126,7 +126,7 @@ describe('<RulePreview />', () => {
mockGetStepsData.mockReturnValue({});
const { getByTestId } = renderRulePreview();
await act(async () => {
expect(getByTestId(RULE_PREVIEW_LOADING_TEST_ID)).toBeInTheDocument();
expect(getByTestId(RULE_OVERVIEW_LOADING_TEST_ID)).toBeInTheDocument();
});
});
@ -135,7 +135,7 @@ describe('<RulePreview />', () => {
mockGetStepsData.mockReturnValue({});
const { queryByTestId, getByText } = renderRulePreview();
await act(async () => {
expect(queryByTestId(RULE_PREVIEW_BODY_TEST_ID)).not.toBeInTheDocument();
expect(queryByTestId(RULE_OVERVIEW_BODY_TEST_ID)).not.toBeInTheDocument();
expect(getByText(NO_DATA_MESSAGE)).toBeInTheDocument();
});
});

View file

@ -8,11 +8,11 @@ import React, { memo, useState, useEffect } from 'react';
import { EuiText, EuiHorizontalRule, EuiSpacer, EuiPanel } from '@elastic/eui';
import { css } from '@emotion/css';
import { FormattedMessage } from '@kbn/i18n-react';
import { usePreviewPanelContext } from '../context';
import { useRuleOverviewPanelContext } from '../context';
import { ExpandableSection } from '../../right/components/expandable_section';
import { useRuleWithFallback } from '../../../../detection_engine/rule_management/logic/use_rule_with_fallback';
import { getStepsData } from '../../../../detections/pages/detection_engine/rules/helpers';
import { RulePreviewTitle } from './rule_preview_title';
import { RuleTitle } from './rule_title';
import { RuleAboutSection } from '../../../../detection_engine/rule_management/components/rule_details/rule_about_section';
import { RuleScheduleSection } from '../../../../detection_engine/rule_management/components/rule_details/rule_schedule_section';
import { RuleDefinitionSection } from '../../../../detection_engine/rule_management/components/rule_details/rule_definition_section';
@ -20,12 +20,12 @@ import { StepRuleActionsReadOnly } from '../../../../detection_engine/rule_creat
import { FlyoutLoading } from '../../../shared/components/flyout_loading';
import { FlyoutError } from '../../../shared/components/flyout_error';
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,
RULE_OVERVIEW_BODY_TEST_ID,
RULE_OVERVIEW_ABOUT_TEST_ID,
RULE_OVERVIEW_DEFINITION_TEST_ID,
RULE_OVERVIEW_SCHEDULE_TEST_ID,
RULE_OVERVIEW_ACTIONS_TEST_ID,
RULE_OVERVIEW_LOADING_TEST_ID,
} from './test_ids';
import type { RuleResponse } from '../../../../../common/api/detection_engine';
@ -47,8 +47,8 @@ const panelViewStyle = css`
/**
* Rule summary on a preview panel on top of the right section of expandable flyout
*/
export const RulePreview = memo(() => {
const { ruleId } = usePreviewPanelContext();
export const RuleOverview = memo(() => {
const { ruleId } = useRuleOverviewPanelContext();
const [rule, setRule] = useState<RuleResponse | null>(null);
const {
rule: maybeRule,
@ -71,15 +71,15 @@ export const RulePreview = memo(() => {
const hasActions = ruleActionsData != null && (hasNotificationActions || hasResponseActions);
return ruleLoading ? (
<FlyoutLoading data-test-subj={RULE_PREVIEW_LOADING_TEST_ID} />
<FlyoutLoading data-test-subj={RULE_OVERVIEW_LOADING_TEST_ID} />
) : rule ? (
<EuiPanel
hasBorder={false}
hasShadow={false}
data-test-subj={RULE_PREVIEW_BODY_TEST_ID}
data-test-subj={RULE_OVERVIEW_BODY_TEST_ID}
className="eui-yScroll"
>
<RulePreviewTitle rule={rule} isSuppressed={!isExistingRule} />
<RuleTitle rule={rule} isSuppressed={!isExistingRule} />
<EuiHorizontalRule margin="s" />
<EuiSpacer size="s" />
<ExpandableSection
@ -90,7 +90,7 @@ export const RulePreview = memo(() => {
/>
}
expanded
data-test-subj={RULE_PREVIEW_ABOUT_TEST_ID}
data-test-subj={RULE_OVERVIEW_ABOUT_TEST_ID}
>
<EuiText size="s">{rule.description}</EuiText>
<EuiSpacer size="s" />
@ -112,7 +112,7 @@ export const RulePreview = memo(() => {
/>
}
expanded={false}
data-test-subj={RULE_PREVIEW_DEFINITION_TEST_ID}
data-test-subj={RULE_OVERVIEW_DEFINITION_TEST_ID}
>
<RuleDefinitionSection
rule={rule}
@ -130,7 +130,7 @@ export const RulePreview = memo(() => {
/>
}
expanded={false}
data-test-subj={RULE_PREVIEW_SCHEDULE_TEST_ID}
data-test-subj={RULE_OVERVIEW_SCHEDULE_TEST_ID}
>
<RuleScheduleSection rule={rule} type="row" rowGutterSize="s" className={panelViewStyle} />
</ExpandableSection>
@ -144,7 +144,7 @@ export const RulePreview = memo(() => {
/>
}
expanded={false}
data-test-subj={RULE_PREVIEW_ACTIONS_TEST_ID}
data-test-subj={RULE_OVERVIEW_ACTIONS_TEST_ID}
>
<StepRuleActionsReadOnly addPadding={false} defaultValues={ruleActionsData} />
</ExpandableSection>
@ -157,4 +157,4 @@ export const RulePreview = memo(() => {
);
});
RulePreview.displayName = 'RulePreview';
RuleOverview.displayName = 'RuleOverview';

View file

@ -7,16 +7,16 @@
import React from 'react';
import { render } from '@testing-library/react';
import type { RulePreviewTitleProps } from './rule_preview_title';
import { RulePreviewTitle } from './rule_preview_title';
import type { RuleTitleProps } from './rule_title';
import { RuleTitle } from './rule_title';
import { TestProvider as ExpandableFlyoutTestProvider } from '@kbn/expandable-flyout/src/test/provider';
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,
RULE_PREVIEW_RULE_TITLE_SUPPRESSED_TEST_ID,
RULE_OVERVIEW_TITLE_TEST_ID,
RULE_OVERVIEW_RULE_CREATED_BY_TEST_ID,
RULE_OVERVIEW_RULE_UPDATED_BY_TEST_ID,
RULE_OVERVIEW_RULE_TITLE_SUPPRESSED_TEST_ID,
} from './test_ids';
const defaultProps = {
@ -24,23 +24,23 @@ const defaultProps = {
isSuppressed: false,
};
const renderRulePreviewTitle = (props: RulePreviewTitleProps) =>
const renderRuleOverviewTitle = (props: RuleTitleProps) =>
render(
<TestProviders>
<ExpandableFlyoutTestProvider>
<RulePreviewTitle {...props} />
<RuleTitle {...props} />
</ExpandableFlyoutTestProvider>
</TestProviders>
);
describe('<RulePreviewTitle />', () => {
describe('<RuleOverviewTitle />', () => {
it('should render title and its components', () => {
const { getByTestId, queryByTestId } = renderRulePreviewTitle(defaultProps);
const { getByTestId, queryByTestId } = renderRuleOverviewTitle(defaultProps);
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();
expect(queryByTestId(RULE_PREVIEW_RULE_TITLE_SUPPRESSED_TEST_ID)).not.toBeInTheDocument();
expect(getByTestId(RULE_OVERVIEW_TITLE_TEST_ID)).toBeInTheDocument();
expect(getByTestId(RULE_OVERVIEW_RULE_CREATED_BY_TEST_ID)).toBeInTheDocument();
expect(getByTestId(RULE_OVERVIEW_RULE_UPDATED_BY_TEST_ID)).toBeInTheDocument();
expect(queryByTestId(RULE_OVERVIEW_RULE_TITLE_SUPPRESSED_TEST_ID)).not.toBeInTheDocument();
});
it('should render deleted rule badge', () => {
@ -48,7 +48,7 @@ describe('<RulePreviewTitle />', () => {
...defaultProps,
isSuppressed: true,
};
const { getByTestId } = renderRulePreviewTitle(props);
expect(getByTestId(RULE_PREVIEW_RULE_TITLE_SUPPRESSED_TEST_ID)).toBeInTheDocument();
const { getByTestId } = renderRuleOverviewTitle(props);
expect(getByTestId(RULE_OVERVIEW_RULE_TITLE_SUPPRESSED_TEST_ID)).toBeInTheDocument();
});
});

View file

@ -10,14 +10,14 @@ import { EuiTitle, EuiText, EuiSpacer, EuiFlexGroup, EuiFlexItem, EuiBadge } fro
import { DELETED_RULE } from '../../../../detection_engine/rule_details_ui/pages/rule_details/translations';
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,
RULE_PREVIEW_RULE_TITLE_SUPPRESSED_TEST_ID,
RULE_OVERVIEW_TITLE_TEST_ID,
RULE_OVERVIEW_RULE_CREATED_BY_TEST_ID,
RULE_OVERVIEW_RULE_UPDATED_BY_TEST_ID,
RULE_OVERVIEW_RULE_TITLE_SUPPRESSED_TEST_ID,
} from './test_ids';
import type { RuleResponse } from '../../../../../common/api/detection_engine';
export interface RulePreviewTitleProps {
export interface RuleTitleProps {
/**
* Rule object that represents relevant information about a rule
*/
@ -29,30 +29,30 @@ export interface RulePreviewTitleProps {
}
/**
* Title component that shows basic information of a rule. This is displayed above rule preview body in rule preview panel
* Title component that shows basic information of a rule. This is displayed above rule overview body
*/
export const RulePreviewTitle: React.FC<RulePreviewTitleProps> = ({ rule, isSuppressed }) => {
export const RuleTitle: React.FC<RuleTitleProps> = ({ rule, isSuppressed }) => {
return (
<div data-test-subj={RULE_PREVIEW_TITLE_TEST_ID}>
<div data-test-subj={RULE_OVERVIEW_TITLE_TEST_ID}>
<EuiTitle>
<h6>{rule.name}</h6>
</EuiTitle>
{isSuppressed && (
<>
<EuiSpacer size="s" />
<EuiBadge data-test-subj={RULE_PREVIEW_RULE_TITLE_SUPPRESSED_TEST_ID} title="">
<EuiBadge data-test-subj={RULE_OVERVIEW_RULE_TITLE_SUPPRESSED_TEST_ID} title="">
{DELETED_RULE}
</EuiBadge>
</>
)}
<EuiSpacer size="s" />
<EuiFlexGroup gutterSize="xs" direction="column">
<EuiFlexItem data-test-subj={RULE_PREVIEW_RULE_CREATED_BY_TEST_ID}>
<EuiFlexItem data-test-subj={RULE_OVERVIEW_RULE_CREATED_BY_TEST_ID}>
<EuiText size="xs">
<CreatedBy createdBy={rule?.created_by} createdAt={rule?.created_at} />
</EuiText>
</EuiFlexItem>
<EuiFlexItem data-test-subj={RULE_PREVIEW_RULE_UPDATED_BY_TEST_ID}>
<EuiFlexItem data-test-subj={RULE_OVERVIEW_RULE_UPDATED_BY_TEST_ID}>
<EuiText size="xs">
<UpdatedBy updatedBy={rule?.updated_by} updatedAt={rule?.updated_at} />
</EuiText>
@ -62,4 +62,4 @@ export const RulePreviewTitle: React.FC<RulePreviewTitleProps> = ({ rule, isSupp
);
};
RulePreviewTitle.displayName = 'RulePreviewTitle';
RuleTitle.displayName = 'RuleTitle';

View file

@ -0,0 +1,46 @@
/*
* 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 { PREFIX } from '../../../shared/test_ids';
import { CONTENT_TEST_ID, HEADER_TEST_ID } from '../../right/components/expandable_section';
const RULE_OVERVIEW_TEST_ID = `${PREFIX}RuleOverview` as const;
export const RULE_OVERVIEW_TITLE_TEST_ID = `${RULE_OVERVIEW_TEST_ID}RuleOverviewTitle` as const;
export const RULE_OVERVIEW_RULE_TITLE_SUPPRESSED_TEST_ID =
`${RULE_OVERVIEW_TITLE_TEST_ID}Suppressed` as const;
export const RULE_OVERVIEW_RULE_CREATED_BY_TEST_ID =
`${RULE_OVERVIEW_TEST_ID}CreatedByText` as const;
export const RULE_OVERVIEW_RULE_UPDATED_BY_TEST_ID =
`${RULE_OVERVIEW_TEST_ID}UpdatedByText` as const;
export const RULE_OVERVIEW_BODY_TEST_ID = `${RULE_OVERVIEW_TEST_ID}Body` as const;
export const RULE_OVERVIEW_ABOUT_TEST_ID = `${RULE_OVERVIEW_TEST_ID}AboutSection` as const;
export const RULE_OVERVIEW_ABOUT_HEADER_TEST_ID = RULE_OVERVIEW_ABOUT_TEST_ID + HEADER_TEST_ID;
export const RULE_OVERVIEW_ABOUT_CONTENT_TEST_ID = RULE_OVERVIEW_ABOUT_TEST_ID + CONTENT_TEST_ID;
export const RULE_OVERVIEW_DEFINITION_TEST_ID =
`${RULE_OVERVIEW_TEST_ID}DefinitionSection` as const;
export const RULE_OVERVIEW_DEFINITION_HEADER_TEST_ID =
RULE_OVERVIEW_DEFINITION_TEST_ID + HEADER_TEST_ID;
export const RULE_OVERVIEW_DEFINITION_CONTENT_TEST_ID =
RULE_OVERVIEW_DEFINITION_TEST_ID + CONTENT_TEST_ID;
export const RULE_OVERVIEW_SCHEDULE_TEST_ID = `${RULE_OVERVIEW_TEST_ID}ScheduleSection` as const;
export const RULE_OVERVIEW_SCHEDULE_HEADER_TEST_ID =
RULE_OVERVIEW_SCHEDULE_TEST_ID + HEADER_TEST_ID;
export const RULE_OVERVIEW_SCHEDULE_CONTENT_TEST_ID =
RULE_OVERVIEW_SCHEDULE_TEST_ID + CONTENT_TEST_ID;
export const RULE_OVERVIEW_ACTIONS_TEST_ID = `${RULE_OVERVIEW_TEST_ID}ActionsSection` as const;
export const RULE_OVERVIEW_ACTIONS_HEADER_TEST_ID = RULE_OVERVIEW_ACTIONS_TEST_ID + HEADER_TEST_ID;
export const RULE_OVERVIEW_ACTIONS_CONTENT_TEST_ID =
RULE_OVERVIEW_ACTIONS_TEST_ID + CONTENT_TEST_ID;
export const RULE_OVERVIEW_LOADING_TEST_ID = `${RULE_OVERVIEW_TEST_ID}Loading` as const;
export const RULE_OVERVIEW_FOOTER_TEST_ID = `${RULE_OVERVIEW_TEST_ID}Footer` as const;
export const RULE_OVERVIEW_NAVIGATE_TO_RULE_TEST_ID =
`${RULE_OVERVIEW_FOOTER_TEST_ID}LinkToRuleDetails` as const;

View file

@ -0,0 +1,58 @@
/*
* 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, { createContext, memo, useContext, useMemo } from 'react';
import { FlyoutError } from '../../shared/components/flyout_error';
import type { RuleOverviewPanelProps } from '.';
export interface RuleOverviewPanelContext {
/**
* Rule id if preview is rule details
*/
ruleId: string;
}
export const RuleOverviewPanelContext = createContext<RuleOverviewPanelContext | undefined>(
undefined
);
export type RuleOverviewPanelProviderProps = {
/**
* React components to render
*/
children: React.ReactNode;
} & Partial<RuleOverviewPanelProps['params']>;
export const RuleOverviewPanelProvider = memo(
({ ruleId, children }: RuleOverviewPanelProviderProps) => {
const contextValue = useMemo(() => (ruleId ? { ruleId } : undefined), [ruleId]);
if (!contextValue) {
return <FlyoutError />;
}
return (
<RuleOverviewPanelContext.Provider value={contextValue}>
{children}
</RuleOverviewPanelContext.Provider>
);
}
);
RuleOverviewPanelProvider.displayName = 'RuleOverviewPanelProvider';
export const useRuleOverviewPanelContext = (): RuleOverviewPanelContext => {
const contextValue = useContext(RuleOverviewPanelContext);
if (!contextValue) {
throw new Error(
'RuleOverviewPanelContext can only be used within RuleOverviewPanelContext provider'
);
}
return contextValue;
};

View file

@ -0,0 +1,43 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { memo } from 'react';
import type { FlyoutPanelProps } from '@kbn/expandable-flyout';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import type { DocumentDetailsRuleOverviewPanelKey } from '../shared/constants/panel_keys';
import { RuleOverview } from './components/rule_overview';
import { RuleFooter } from './components/footer';
export interface RuleOverviewPanelProps extends FlyoutPanelProps {
key: typeof DocumentDetailsRuleOverviewPanelKey;
params: {
ruleId: string;
};
}
/**
* Displays a rule overview panel
*/
export const RuleOverviewPanel: React.FC = memo(() => {
return (
<EuiFlexGroup
justifyContent="spaceBetween"
direction="column"
gutterSize="none"
style={{ height: '100%' }}
>
<EuiFlexItem style={{ marginTop: '-15px' }}>
<RuleOverview />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<RuleFooter />
</EuiFlexItem>
</EuiFlexGroup>
);
});
RuleOverviewPanel.displayName = 'RuleOverviewPanel';

View file

@ -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 type { RuleOverviewPanelContext } from '../context';
/**
* Mock contextValue for rule overview panel context
*/
export const mockContextValue: RuleOverviewPanelContext = {
ruleId: 'rule id',
};

View file

@ -8,4 +8,7 @@
export const DocumentDetailsRightPanelKey = 'document-details-right' as const;
export const DocumentDetailsLeftPanelKey = 'document-details-left' as const;
export const DocumentDetailsPreviewPanelKey = 'document-details-preview' as const;
export const DocumentDetailsIsolateHostPanelKey = 'document-details-isolate-host' as const;
export const DocumentDetailsAlertReasonPanelKey = 'document-details-alert-reason' as const;
export const DocumentDetailsRuleOverviewPanelKey = 'document-details-rule-overview' as const;

View file

@ -11,8 +11,9 @@ import { useEuiTheme } from '@elastic/eui';
import {
DocumentDetailsIsolateHostPanelKey,
DocumentDetailsLeftPanelKey,
DocumentDetailsPreviewPanelKey,
DocumentDetailsRightPanelKey,
DocumentDetailsAlertReasonPanelKey,
DocumentDetailsRuleOverviewPanelKey,
} from './document_details/shared/constants/panel_keys';
import type { IsolateHostPanelProps } from './document_details/isolate_host';
import { IsolateHostPanel } from './document_details/isolate_host';
@ -23,9 +24,12 @@ import { RightPanelProvider } from './document_details/right/context';
import type { LeftPanelProps } from './document_details/left';
import { LeftPanel } from './document_details/left';
import { LeftPanelProvider } from './document_details/left/context';
import type { PreviewPanelProps } from './document_details/preview';
import { PreviewPanel } from './document_details/preview';
import { PreviewPanelProvider } from './document_details/preview/context';
import type { AlertReasonPanelProps } from './document_details/alert_reason';
import { AlertReasonPanel } from './document_details/alert_reason';
import { AlertReasonPanelProvider } from './document_details/alert_reason/context';
import type { RuleOverviewPanelProps } from './document_details/rule_overview';
import { RuleOverviewPanel } from './document_details/rule_overview';
import { RuleOverviewPanelProvider } from './document_details/rule_overview/context';
import type { UserPanelExpandableFlyoutProps } from './entity_details/user_right';
import { UserPanel, UserPanelKey } from './entity_details/user_right';
import type { UserDetailsPanelProps } from './entity_details/user_details_left';
@ -57,11 +61,19 @@ const expandableFlyoutDocumentsPanels: ExpandableFlyoutProps['registeredPanels']
),
},
{
key: DocumentDetailsPreviewPanelKey,
key: DocumentDetailsAlertReasonPanelKey,
component: (props) => (
<PreviewPanelProvider {...(props as PreviewPanelProps).params}>
<PreviewPanel path={props.path as PreviewPanelProps['path']} />
</PreviewPanelProvider>
<AlertReasonPanelProvider {...(props as AlertReasonPanelProps).params}>
<AlertReasonPanel />
</AlertReasonPanelProvider>
),
},
{
key: DocumentDetailsRuleOverviewPanelKey,
component: (props) => (
<RuleOverviewPanelProvider {...(props as RuleOverviewPanelProps).params}>
<RuleOverviewPanel />
</RuleOverviewPanelProvider>
),
},
{

View file

@ -8,5 +8,5 @@
import { getDataTestSubjectSelector } from '../../helpers/common';
export const DOCUMENT_DETAILS_FLYOUT_ALERT_REASON_PREVIEW_CONTAINER = getDataTestSubjectSelector(
'securitySolutionFlyoutAlertReasonPreviewBody'
'securitySolutionFlyoutAlertReasonBody'
);

View file

@ -8,30 +8,30 @@
import { getDataTestSubjectSelector } from '../../helpers/common';
export const DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_TITLE = getDataTestSubjectSelector(
'securitySolutionFlyoutRulePreviewRulePreviewTitle'
'securitySolutionFlyoutRuleOverviewRuleOverviewTitle'
);
export const DOCUMENT_DETAILS_FLYOUT_CREATED_BY = getDataTestSubjectSelector(
'securitySolutionFlyoutRulePreviewCreatedByText'
'securitySolutionFlyoutRuleOverviewCreatedByText'
);
export const DOCUMENT_DETAILS_FLYOUT_UPDATED_BY = getDataTestSubjectSelector(
'securitySolutionFlyoutRulePreviewUpdatedByText'
'securitySolutionFlyoutRuleOverviewUpdatedByText'
);
export const DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_ABOUT_SECTION_HEADER = getDataTestSubjectSelector(
'securitySolutionFlyoutRulePreviewAboutSectionHeader'
'securitySolutionFlyoutRuleOverviewAboutSectionHeader'
);
export const DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_ABOUT_SECTION_CONTENT =
getDataTestSubjectSelector('securitySolutionFlyoutRulePreviewAboutSectionContent');
getDataTestSubjectSelector('securitySolutionFlyoutRuleOverviewAboutSectionContent');
export const DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_DEFINITION_SECTION_HEADER =
getDataTestSubjectSelector('securitySolutionFlyoutRulePreviewDefinitionSectionHeader');
getDataTestSubjectSelector('securitySolutionFlyoutRuleOverviewDefinitionSectionHeader');
export const DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_DEFINITION_SECTION_CONTENT =
getDataTestSubjectSelector('securitySolutionFlyoutRulePreviewDefinitionSectionContent');
getDataTestSubjectSelector('securitySolutionFlyoutRuleOverviewDefinitionSectionContent');
export const DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_SCHEDULE_SECTION_HEADER =
getDataTestSubjectSelector('securitySolutionFlyoutRulePreviewScheduleSectionHeader');
getDataTestSubjectSelector('securitySolutionFlyoutRuleOverviewScheduleSectionHeader');
export const DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_SCHEDULE_SECTION_CONTENT =
getDataTestSubjectSelector('securitySolutionFlyoutRulePreviewScheduleSectionContent');
getDataTestSubjectSelector('securitySolutionFlyoutRuleOverviewScheduleSectionContent');
export const DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_FOOTER = getDataTestSubjectSelector(
'securitySolutionFlyoutRulePreviewFooter'
'securitySolutionFlyoutRuleOverviewFooter'
);
export const DOCUMENT_DETAILS_FLYOUT_RULE_PREVIEW_FOOTER_LINK = getDataTestSubjectSelector(
'securitySolutionFlyoutRulePreviewFooterLinkToRuleDetails'
'securitySolutionFlyoutRuleOverviewFooterLinkToRuleDetails'
);