mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution] expandable flyout - show full alert reason in preview panel (#163667)
This commit is contained in:
parent
e78d61789f
commit
736b16dfcd
24 changed files with 437 additions and 262 deletions
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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('<AlertReasonPreview />', () => {
|
||||
it('should render alert reason preview', () => {
|
||||
const { getByTestId } = render(
|
||||
<PreviewPanelContext.Provider value={panelContextValue}>
|
||||
<ThemeProvider theme={mockTheme}>
|
||||
<AlertReasonPreview />
|
||||
</ThemeProvider>
|
||||
</PreviewPanelContext.Provider>
|
||||
);
|
||||
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(
|
||||
<PreviewPanelContext.Provider value={contextValue}>
|
||||
<AlertReasonPreview />
|
||||
</PreviewPanelContext.Provider>
|
||||
);
|
||||
expect(queryByTestId(ALERT_REASON_PREVIEW_BODY_TEST_ID)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -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 (
|
||||
<EuiPanel hasShadow={false} data-test-subj={ALERT_REASON_PREVIEW_BODY_TEST_ID}>
|
||||
<EuiTitle>
|
||||
<h6>{ALERT_REASON_TITLE}</h6>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="m" />
|
||||
{renderer.renderRow({
|
||||
contextId: 'event-details',
|
||||
data: dataAsNestedObject,
|
||||
isDraggable: false,
|
||||
scopeId: 'global',
|
||||
})}
|
||||
</EuiPanel>
|
||||
);
|
||||
};
|
||||
|
||||
AlertReasonPreview.displayName = 'AlertReasonPreview';
|
|
@ -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';
|
||||
|
|
|
@ -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' }
|
||||
);
|
||||
|
|
|
@ -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<PreviewPanelContext | undefined>(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 (
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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: <RulePreview />,
|
||||
footer: <RulePreviewFooter />,
|
||||
},
|
||||
{
|
||||
id: 'alert-reason-preview',
|
||||
name: ALERT_REASON_PREVIEW,
|
||||
content: <AlertReasonPreview />,
|
||||
},
|
||||
];
|
||||
|
|
|
@ -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' }
|
||||
);
|
||||
|
|
|
@ -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) => (
|
||||
<RightPanelContext.Provider value={panelContextValue}>
|
||||
<div
|
||||
css={css`
|
||||
width: 500px;
|
||||
`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</RightPanelContext.Provider>
|
||||
);
|
||||
export const Rule: Story<void> = () => {
|
||||
const panelContextValue = {
|
||||
dataFormattedForFieldBrowser: [ruleUuid, ruleDescription],
|
||||
} as unknown as RightPanelContext;
|
||||
|
||||
return wrapper(<Description />, panelContextValue);
|
||||
};
|
||||
|
||||
export const Document: Story<void> = () => {
|
||||
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(<Description />, panelContextValue);
|
||||
};
|
||||
|
||||
export const EmptyDescription: Story<void> = () => {
|
||||
const panelContextValue = {
|
||||
dataFormattedForFieldBrowser: [
|
||||
ruleUuid,
|
||||
{
|
||||
category: 'kibana',
|
||||
field: 'kibana.alert.rule.description',
|
||||
values: [''],
|
||||
originalValue: ['description'],
|
||||
isObjectArray: false,
|
||||
},
|
||||
],
|
||||
} as unknown as RightPanelContext;
|
||||
|
||||
return wrapper(<Description />, panelContextValue);
|
||||
};
|
||||
|
||||
export const Empty: Story<void> = () => {
|
||||
const panelContextValue = {} as unknown as RightPanelContext;
|
||||
|
||||
return wrapper(<Description />, panelContextValue);
|
||||
};
|
|
@ -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(
|
||||
<ExpandableFlyoutContext.Provider value={flyoutContextValue}>
|
||||
<RightPanelContext.Provider value={panelContext}>
|
||||
<Description />
|
||||
</RightPanelContext.Provider>
|
||||
</ExpandableFlyoutContext.Provider>
|
||||
);
|
||||
|
||||
describe('<Description />', () => {
|
||||
it('should render the component', () => {
|
||||
const panelContextValue = {
|
||||
dataFormattedForFieldBrowser: [ruleUuid, ruleDescription, ruleName],
|
||||
} as unknown as RightPanelContext;
|
||||
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
<RightPanelContext.Provider value={panelContextValue}>
|
||||
<ThemeProvider theme={mockTheme}>
|
||||
<Description />
|
||||
</ThemeProvider>
|
||||
</RightPanelContext.Provider>
|
||||
</TestProviders>
|
||||
const { getByTestId } = renderDescription(
|
||||
panelContextValue([ruleUuid, ruleDescription, ruleName])
|
||||
);
|
||||
|
||||
expect(getByTestId(DESCRIPTION_TITLE_TEST_ID)).toBeInTheDocument();
|
||||
|
@ -66,18 +78,8 @@ describe('<Description />', () => {
|
|||
});
|
||||
|
||||
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(
|
||||
<TestProviders>
|
||||
<RightPanelContext.Provider value={panelContextValue}>
|
||||
<ThemeProvider theme={mockTheme}>
|
||||
<Description />
|
||||
</ThemeProvider>
|
||||
</RightPanelContext.Provider>
|
||||
</TestProviders>
|
||||
const { getByTestId, queryByTestId } = renderDescription(
|
||||
panelContextValue([ruleUuid, ruleDescription])
|
||||
);
|
||||
|
||||
expect(getByTestId(DESCRIPTION_TITLE_TEST_ID)).toBeInTheDocument();
|
||||
|
@ -86,21 +88,44 @@ describe('<Description />', () => {
|
|||
});
|
||||
|
||||
it('should render document title if document is not an alert', () => {
|
||||
const panelContextValue = {
|
||||
dataFormattedForFieldBrowser: [ruleDescription],
|
||||
} as unknown as RightPanelContext;
|
||||
|
||||
const { getByTestId } = render(
|
||||
<TestProviders>
|
||||
<RightPanelContext.Provider value={panelContextValue}>
|
||||
<ThemeProvider theme={mockTheme}>
|
||||
<Description />
|
||||
</ThemeProvider>
|
||||
</RightPanelContext.Provider>
|
||||
</TestProviders>
|
||||
);
|
||||
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],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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<void> = () => {
|
||||
const panelContextValue = {
|
||||
dataAsNestedObject: mockDataAsNestedObject,
|
||||
dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser,
|
||||
} as unknown as RightPanelContext;
|
||||
|
||||
return (
|
||||
<StorybookProviders>
|
||||
<RightPanelContext.Provider value={panelContextValue}>
|
||||
<Reason />
|
||||
</RightPanelContext.Provider>
|
||||
</StorybookProviders>
|
||||
);
|
||||
};
|
||||
|
||||
export const Empty: Story<void> = () => {
|
||||
const panelContextValue = {
|
||||
dataFormattedForFieldBrowser: {},
|
||||
} as unknown as RightPanelContext;
|
||||
|
||||
return (
|
||||
<RightPanelContext.Provider value={panelContextValue}>
|
||||
<Reason />
|
||||
</RightPanelContext.Provider>
|
||||
);
|
||||
};
|
|
@ -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';
|
||||
|
||||
const flyoutContextValue = {
|
||||
openPreviewPanel: jest.fn(),
|
||||
} as unknown as ExpandableFlyoutContext;
|
||||
|
||||
const panelContextValue = {
|
||||
eventId: 'event id',
|
||||
indexName: 'indexName',
|
||||
scopeId: 'scopeId',
|
||||
dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser,
|
||||
getFieldsData: mockGetFieldsData,
|
||||
} as unknown as RightPanelContext;
|
||||
|
||||
const renderReason = (panelContext: RightPanelContext = panelContextValue) =>
|
||||
render(
|
||||
<ExpandableFlyoutContext.Provider value={flyoutContextValue}>
|
||||
<RightPanelContext.Provider value={panelContext}>
|
||||
<Reason />
|
||||
</RightPanelContext.Provider>
|
||||
</ExpandableFlyoutContext.Provider>
|
||||
);
|
||||
|
||||
describe('<Reason />', () => {
|
||||
it('should render the component', () => {
|
||||
const panelContextValue = {
|
||||
dataAsNestedObject: mockDataAsNestedObject,
|
||||
dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser,
|
||||
} as unknown as RightPanelContext;
|
||||
|
||||
const { getByTestId } = render(
|
||||
<ThemeProvider theme={() => ({ eui: euiDarkVars, darkMode: true })}>
|
||||
<RightPanelContext.Provider value={panelContextValue}>
|
||||
<Reason />
|
||||
</RightPanelContext.Provider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
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(
|
||||
<RightPanelContext.Provider value={panelContextValue}>
|
||||
<Reason />
|
||||
</RightPanelContext.Provider>
|
||||
);
|
||||
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(
|
||||
<RightPanelContext.Provider value={panelContextValue}>
|
||||
<Reason />
|
||||
</RightPanelContext.Provider>
|
||||
);
|
||||
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(
|
||||
<RightPanelContext.Provider value={panelContextValue}>
|
||||
<Reason />
|
||||
</RightPanelContext.Provider>
|
||||
);
|
||||
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',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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(
|
||||
() => (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
size="s"
|
||||
iconType="expand"
|
||||
onClick={openRulePreview}
|
||||
iconSide="right"
|
||||
data-test-subj={REASON_DETAILS_PREVIEW_BUTTON_TEST_ID}
|
||||
>
|
||||
{ALERT_REASON_DETAILS_TEXT}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
),
|
||||
[openRulePreview]
|
||||
);
|
||||
|
||||
if (!dataFormattedForFieldBrowser || !dataAsNestedObject || !renderer) {
|
||||
if (!dataFormattedForFieldBrowser) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -38,17 +78,21 @@ export const Reason: FC = () => {
|
|||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
<EuiFlexItem data-test-subj={REASON_TITLE_TEST_ID}>
|
||||
<EuiTitle size="xxs">
|
||||
<h5>{isAlert ? ALERT_REASON_TITLE : DOCUMENT_REASON_TITLE}</h5>
|
||||
<h5>
|
||||
{isAlert ? (
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<h5>{ALERT_REASON_TITLE}</h5>
|
||||
</EuiFlexItem>
|
||||
{viewPreview}
|
||||
</EuiFlexGroup>
|
||||
) : (
|
||||
DOCUMENT_REASON_TITLE
|
||||
)}
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem data-test-subj={REASON_DETAILS_TEST_ID}>
|
||||
{renderer.renderRow({
|
||||
contextId: 'event-details',
|
||||
data: dataAsNestedObject,
|
||||
isDraggable: false,
|
||||
scopeId: 'global',
|
||||
})}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem data-test-subj={REASON_DETAILS_TEST_ID}>{alertReason}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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',
|
||||
{
|
||||
|
|
|
@ -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)),
|
||||
}),
|
||||
|
|
|
@ -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 [];
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
);
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue