mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Security Solution] expandable flyout - add status to flyout header (#161942)
This commit is contained in:
parent
f7faa8217b
commit
68b8ac3fef
12 changed files with 241 additions and 10 deletions
|
@ -43,6 +43,7 @@ import {
|
|||
DOCUMENT_DETAILS_FLYOUT_HEADER_RISK_SCORE_VALUE,
|
||||
DOCUMENT_DETAILS_FLYOUT_HEADER_SEVERITY,
|
||||
DOCUMENT_DETAILS_FLYOUT_HEADER_SEVERITY_VALUE,
|
||||
DOCUMENT_DETAILS_FLYOUT_HEADER_STATUS,
|
||||
DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE,
|
||||
DOCUMENT_DETAILS_FLYOUT_JSON_TAB,
|
||||
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB,
|
||||
|
@ -85,6 +86,8 @@ describe(
|
|||
|
||||
cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_CHAT_BUTTON).should('be.visible');
|
||||
|
||||
cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_STATUS).should('be.visible');
|
||||
|
||||
cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_RISK_SCORE).should('be.visible');
|
||||
cy.get(DOCUMENT_DETAILS_FLYOUT_HEADER_RISK_SCORE_VALUE)
|
||||
.should('be.visible')
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
FLYOUT_HEADER_RISK_SCORE_VALUE_TEST_ID,
|
||||
FLYOUT_HEADER_SEVERITY_TITLE_TEST_ID,
|
||||
FLYOUT_HEADER_SEVERITY_VALUE_TEST_ID,
|
||||
FLYOUT_HEADER_STATUS_BUTTON_TEST_ID,
|
||||
FLYOUT_HEADER_TITLE_TEST_ID,
|
||||
} from '../../../public/flyout/right/components/test_ids';
|
||||
|
||||
|
@ -42,6 +43,9 @@ export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB =
|
|||
getDataTestSubjectSelector(OVERVIEW_TAB_TEST_ID);
|
||||
export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB = getDataTestSubjectSelector(TABLE_TAB_TEST_ID);
|
||||
export const DOCUMENT_DETAILS_FLYOUT_JSON_TAB = getDataTestSubjectSelector(JSON_TAB_TEST_ID);
|
||||
export const DOCUMENT_DETAILS_FLYOUT_HEADER_STATUS = getDataTestSubjectSelector(
|
||||
FLYOUT_HEADER_STATUS_BUTTON_TEST_ID
|
||||
);
|
||||
export const DOCUMENT_DETAILS_FLYOUT_HEADER_RISK_SCORE = getDataTestSubjectSelector(
|
||||
FLYOUT_HEADER_RISK_SCORE_TITLE_TEST_ID
|
||||
);
|
||||
|
|
|
@ -91,6 +91,7 @@ exports[`Event Details Overview Cards renders rows and spacers correctly 1`] = `
|
|||
<button
|
||||
aria-label="Click to change alert status"
|
||||
class="euiBadge c3 emotion-euiBadge-primary-clickable"
|
||||
data-test-subj="rule-status-badge"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__content emotion-euiBadge__content"
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context';
|
||||
import { RightPanelContext } from '../context';
|
||||
import {
|
||||
FLYOUT_HEADER_CHAT_BUTTON_TEST_ID,
|
||||
|
@ -21,7 +22,7 @@ import moment from 'moment-timezone';
|
|||
import { useDateFormat, useTimeZone } from '../../../common/lib/kibana';
|
||||
import { mockDataFormattedForFieldBrowser, mockGetFieldsData } from '../mocks/mock_context';
|
||||
import { useAssistant } from '../hooks/use_assistant';
|
||||
import { MockAssistantProvider } from '../../../common/mock/mock_assistant_provider';
|
||||
import { TestProvidersComponent } from '../../../common/mock';
|
||||
|
||||
jest.mock('../../../common/lib/kibana');
|
||||
jest.mock('../hooks/use_assistant');
|
||||
|
@ -31,13 +32,17 @@ moment.tz.setDefault('UTC');
|
|||
|
||||
const dateFormat = 'MMM D, YYYY @ HH:mm:ss.SSS';
|
||||
|
||||
const flyoutContextValue = {} as unknown as ExpandableFlyoutContext;
|
||||
|
||||
const renderHeader = (contextValue: RightPanelContext) =>
|
||||
render(
|
||||
<MockAssistantProvider>
|
||||
<RightPanelContext.Provider value={contextValue}>
|
||||
<HeaderTitle />
|
||||
</RightPanelContext.Provider>
|
||||
</MockAssistantProvider>
|
||||
<TestProvidersComponent>
|
||||
<ExpandableFlyoutContext.Provider value={flyoutContextValue}>
|
||||
<RightPanelContext.Provider value={contextValue}>
|
||||
<HeaderTitle />
|
||||
</RightPanelContext.Provider>
|
||||
</ExpandableFlyoutContext.Provider>
|
||||
</TestProvidersComponent>
|
||||
);
|
||||
|
||||
describe('<HeaderTitle />', () => {
|
||||
|
|
|
@ -10,6 +10,7 @@ import React, { memo } from 'react';
|
|||
import { NewChatById } from '@kbn/elastic-assistant';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { DocumentStatus } from './status';
|
||||
import { useAssistant } from '../hooks/use_assistant';
|
||||
import {
|
||||
ALERT_SUMMARY_CONVERSATION_ID,
|
||||
|
@ -67,10 +68,17 @@ export const HeaderTitle: FC = memo(() => {
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="m" />
|
||||
{timestamp && <PreferenceFormattedDate value={new Date(timestamp)} />}
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup direction="row" gutterSize="l">
|
||||
<EuiSpacer size="xs" />
|
||||
<EuiFlexGroup direction="row" gutterSize="m">
|
||||
<EuiFlexItem grow={false}>
|
||||
<DocumentStatus />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
{timestamp && <PreferenceFormattedDate value={new Date(timestamp)} />}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="xs" />
|
||||
<EuiFlexGroup direction="row" gutterSize="m">
|
||||
<EuiFlexItem grow={false}>
|
||||
<DocumentSeverity />
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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 { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context';
|
||||
import { StorybookProviders } from '../../../common/mock/storybook_providers';
|
||||
import { DocumentStatus } from './status';
|
||||
import { RightPanelContext } from '../context';
|
||||
import { mockBrowserFields, mockDataFormattedForFieldBrowser } from '../mocks/mock_context';
|
||||
|
||||
export default {
|
||||
component: DocumentStatus,
|
||||
title: 'Flyout/Status',
|
||||
};
|
||||
|
||||
const flyoutContextValue = {
|
||||
closeFlyout: () => {},
|
||||
} as unknown as ExpandableFlyoutContext;
|
||||
|
||||
export const Default: Story<void> = () => {
|
||||
const contextValue = {
|
||||
eventId: 'eventId',
|
||||
browserFields: mockBrowserFields,
|
||||
dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser,
|
||||
scopeId: 'alerts-page',
|
||||
} as unknown as RightPanelContext;
|
||||
|
||||
return (
|
||||
<StorybookProviders>
|
||||
<ExpandableFlyoutContext.Provider value={flyoutContextValue}>
|
||||
<RightPanelContext.Provider value={contextValue}>
|
||||
<DocumentStatus />
|
||||
</RightPanelContext.Provider>
|
||||
</ExpandableFlyoutContext.Provider>
|
||||
</StorybookProviders>
|
||||
);
|
||||
};
|
||||
|
||||
export const Empty: Story<void> = () => {
|
||||
const contextValue = {
|
||||
eventId: 'eventId',
|
||||
browserFields: {},
|
||||
dataFormattedForFieldBrowser: [],
|
||||
scopeId: 'scopeId',
|
||||
} as unknown as RightPanelContext;
|
||||
|
||||
return (
|
||||
<ExpandableFlyoutContext.Provider value={flyoutContextValue}>
|
||||
<RightPanelContext.Provider value={contextValue}>
|
||||
<DocumentStatus />
|
||||
</RightPanelContext.Provider>
|
||||
</ExpandableFlyoutContext.Provider>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context';
|
||||
import { RightPanelContext } from '../context';
|
||||
import { DocumentStatus } from './status';
|
||||
import { mockDataFormattedForFieldBrowser } from '../mocks/mock_context';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
import { useAlertsActions } from '../../../detections/components/alerts_table/timeline_actions/use_alerts_actions';
|
||||
import { FLYOUT_HEADER_STATUS_BUTTON_TEST_ID } from './test_ids';
|
||||
|
||||
jest.mock('../../../detections/components/alerts_table/timeline_actions/use_alerts_actions');
|
||||
|
||||
const flyoutContextValue = {
|
||||
closeFlyout: jest.fn(),
|
||||
} as unknown as ExpandableFlyoutContext;
|
||||
|
||||
const renderStatus = (contextValue: RightPanelContext) =>
|
||||
render(
|
||||
<TestProviders>
|
||||
<ExpandableFlyoutContext.Provider value={flyoutContextValue}>
|
||||
<RightPanelContext.Provider value={contextValue}>
|
||||
<DocumentStatus />
|
||||
</RightPanelContext.Provider>
|
||||
</ExpandableFlyoutContext.Provider>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
const actionItem = {
|
||||
key: 'key',
|
||||
name: 'name',
|
||||
'data-test-subj': 'data-test-subj',
|
||||
};
|
||||
|
||||
(useAlertsActions as jest.Mock).mockReturnValue({
|
||||
actionItems: [actionItem],
|
||||
});
|
||||
|
||||
describe('<DocumentStatus />', () => {
|
||||
it('should render status information', () => {
|
||||
const contextValue = {
|
||||
eventId: 'eventId',
|
||||
browserFields: {},
|
||||
dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser,
|
||||
scopeId: 'scopeId',
|
||||
} as unknown as RightPanelContext;
|
||||
|
||||
const { getByTestId, getByText } = renderStatus(contextValue);
|
||||
|
||||
expect(getByTestId(FLYOUT_HEADER_STATUS_BUTTON_TEST_ID)).toBeInTheDocument();
|
||||
expect(getByText('open')).toBeInTheDocument();
|
||||
|
||||
getByTestId(FLYOUT_HEADER_STATUS_BUTTON_TEST_ID).click();
|
||||
expect(getByTestId('data-test-subj')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render empty component', () => {
|
||||
const contextValue = {
|
||||
eventId: 'eventId',
|
||||
browserFields: {},
|
||||
dataFormattedForFieldBrowser: [],
|
||||
scopeId: 'scopeId',
|
||||
} as unknown as RightPanelContext;
|
||||
|
||||
const { container } = renderStatus(contextValue);
|
||||
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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 { FC } from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { find } from 'lodash/fp';
|
||||
import { useExpandableFlyoutContext } from '@kbn/expandable-flyout';
|
||||
import type {
|
||||
EnrichedFieldInfo,
|
||||
EnrichedFieldInfoWithValues,
|
||||
} from '../../../common/components/event_details/types';
|
||||
import { SIGNAL_STATUS_FIELD_NAME } from '../../../timelines/components/timeline/body/renderers/constants';
|
||||
import { StatusPopoverButton } from '../../../common/components/event_details/overview/status_popover_button';
|
||||
import { useRightPanelContext } from '../context';
|
||||
import { getEnrichedFieldInfo } from '../../../common/components/event_details/helpers';
|
||||
|
||||
/**
|
||||
* Checks if the field info has data to convert EnrichedFieldInfo into EnrichedFieldInfoWithValues
|
||||
*/
|
||||
function hasData(fieldInfo?: EnrichedFieldInfo): fieldInfo is EnrichedFieldInfoWithValues {
|
||||
return !!fieldInfo && Array.isArray(fieldInfo.values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Document details status displayed in flyout right section header
|
||||
*/
|
||||
export const DocumentStatus: FC = () => {
|
||||
const { closeFlyout } = useExpandableFlyoutContext();
|
||||
const { eventId, browserFields, dataFormattedForFieldBrowser, scopeId } = useRightPanelContext();
|
||||
|
||||
const statusData = useMemo(() => {
|
||||
const item = find(
|
||||
{ field: SIGNAL_STATUS_FIELD_NAME, category: 'kibana' },
|
||||
dataFormattedForFieldBrowser
|
||||
);
|
||||
return (
|
||||
item &&
|
||||
getEnrichedFieldInfo({
|
||||
eventId,
|
||||
contextId: scopeId,
|
||||
scopeId,
|
||||
browserFields: browserFields || {},
|
||||
item,
|
||||
})
|
||||
);
|
||||
}, [browserFields, dataFormattedForFieldBrowser, eventId, scopeId]);
|
||||
|
||||
if (!statusData || !hasData(statusData)) return null;
|
||||
|
||||
return (
|
||||
<StatusPopoverButton
|
||||
eventId={eventId}
|
||||
contextId={scopeId}
|
||||
enrichedFieldInfo={statusData}
|
||||
scopeId={scopeId}
|
||||
handleOnEventClosed={closeFlyout}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
DocumentStatus.displayName = 'DocumentStatus';
|
|
@ -14,6 +14,7 @@ export const EXPAND_DETAILS_BUTTON_TEST_ID =
|
|||
'securitySolutionDocumentDetailsFlyoutHeaderExpandDetailButton';
|
||||
export const COLLAPSE_DETAILS_BUTTON_TEST_ID =
|
||||
'securitySolutionDocumentDetailsFlyoutHeaderCollapseDetailButton';
|
||||
export const FLYOUT_HEADER_STATUS_BUTTON_TEST_ID = 'rule-status-badge';
|
||||
export const FLYOUT_HEADER_SEVERITY_TITLE_TEST_ID =
|
||||
'securitySolutionAlertDetailsFlyoutHeaderSeverityTitle';
|
||||
export const FLYOUT_HEADER_SEVERITY_VALUE_TEST_ID = 'severity';
|
||||
|
|
|
@ -31,6 +31,13 @@ export const SEVERITY_TITLE = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const STATUS_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.flyout.documentDetails.statusTitle',
|
||||
{
|
||||
defaultMessage: 'Status',
|
||||
}
|
||||
);
|
||||
|
||||
export const RISK_SCORE_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.flyout.documentDetails.riskScoreTitle',
|
||||
{
|
||||
|
|
|
@ -77,6 +77,8 @@ export const RightPanelProvider = ({
|
|||
children,
|
||||
}: RightPanelProviderProps) => {
|
||||
const currentSpaceId = useSpaceId();
|
||||
// TODO Replace getAlertIndexAlias way to retrieving the eventIndex with the GET /_alias
|
||||
// https://github.com/elastic/kibana/issues/113063
|
||||
const eventIndex = indexName ? getAlertIndexAlias(indexName, currentSpaceId) ?? indexName : '';
|
||||
const [{ pageName }] = useRouteSpy();
|
||||
const sourcererScope =
|
||||
|
|
|
@ -57,6 +57,7 @@ const RuleStatusComponent: React.FC<Props> = ({
|
|||
onClickAriaLabel={onClickAriaLabel}
|
||||
iconType={iconType}
|
||||
iconSide={iconSide}
|
||||
data-test-subj="rule-status-badge"
|
||||
>
|
||||
{value}
|
||||
</StyledEuiBadge>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue