Skip to content

Commit

Permalink
[Security Solution] expandable flyout - add status to flyout header (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
PhilippeOberti authored Jul 18, 2023
1 parent f7faa82 commit 68b8ac3
Show file tree
Hide file tree
Showing 12 changed files with 241 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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
);
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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');
Expand All @@ -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 />', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
@@ -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>
);
};
Original file line number Diff line number Diff line change
@@ -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();
});
});
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const RuleStatusComponent: React.FC<Props> = ({
onClickAriaLabel={onClickAriaLabel}
iconType={iconType}
iconSide={iconSide}
data-test-subj="rule-status-badge"
>
{value}
</StyledEuiBadge>
Expand Down

0 comments on commit 68b8ac3

Please sign in to comment.