mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Security Solution] add session view component to expandable flyout (#154597)
This commit is contained in:
parent
dbb8e2ebec
commit
ec6bc2db15
11 changed files with 243 additions and 9 deletions
|
@ -100,9 +100,7 @@ describe.skip('Alert details expandable flyout left panel', { testIsolation: fal
|
|||
it('should display content when switching buttons', () => {
|
||||
openVisualizeTab();
|
||||
openSessionView();
|
||||
cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_CONTENT)
|
||||
.should('be.visible')
|
||||
.and('have.text', 'Session view');
|
||||
cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_CONTENT).should('be.visible');
|
||||
|
||||
openGraphAnalyzer();
|
||||
cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_CONTENT).should('be.visible');
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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_VISUALIZE_TAB_SESSION_VIEW_NO_DATA } from '../../../screens/document_expandable_flyout';
|
||||
import {
|
||||
expandFirstAlertExpandableFlyout,
|
||||
expandDocumentDetailsExpandableFlyoutLeftSection,
|
||||
} from '../../../tasks/document_expandable_flyout';
|
||||
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';
|
||||
|
||||
// Skipping these for now as the feature is protected behind a feature flag set to false by default
|
||||
// To run the tests locally, add 'securityFlyoutEnabled' in the Cypress config.ts here https://github.com/elastic/kibana/blob/main/x-pack/test/security_solution_cypress/config.ts#L50
|
||||
describe.skip(
|
||||
'Alert details expandable flyout left panel session view',
|
||||
{ testIsolation: false },
|
||||
() => {
|
||||
before(() => {
|
||||
cleanKibana();
|
||||
login();
|
||||
createRule(getNewRule());
|
||||
visit(ALERTS_URL);
|
||||
waitForAlertsToPopulate();
|
||||
expandFirstAlertExpandableFlyout();
|
||||
expandDocumentDetailsExpandableFlyoutLeftSection();
|
||||
});
|
||||
|
||||
it('should display session view no data message', () => {
|
||||
cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_NO_DATA)
|
||||
.should('be.visible')
|
||||
.and('contain.text', 'No data to render')
|
||||
.and('contain.text', 'No process events found for this query');
|
||||
});
|
||||
|
||||
it('should display session view component', () => {});
|
||||
}
|
||||
);
|
|
@ -135,6 +135,8 @@ export const DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_BUTTON = getData
|
|||
);
|
||||
export const DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_CONTENT =
|
||||
getDataTestSubjectSelector(SESSION_VIEW_TEST_ID);
|
||||
export const DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_NO_DATA =
|
||||
getDataTestSubjectSelector('sessionView:sessionViewProcessEventsEmpty');
|
||||
export const DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_BUTTON =
|
||||
getDataTestSubjectSelector(VISUALIZE_TAB_GRAPH_ANALYZER_BUTTON_TEST_ID);
|
||||
export const DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_CONTENT =
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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 { SessionView } from './session_view';
|
||||
import type { LeftPanelContext } from '../context';
|
||||
import { LeftFlyoutContext } from '../context';
|
||||
|
||||
export default {
|
||||
component: SessionView,
|
||||
title: 'Flyout/SessionView',
|
||||
};
|
||||
|
||||
// TODO to get this working, we need to spent some time getting all the foundation items for storybook
|
||||
// (ReduxStoreProvider, CellActionsProvider...) similarly to how it was done for the TestProvidersComponent
|
||||
// see ticket https://github.com/elastic/security-team/issues/6223
|
||||
// export const Default: Story<void> = () => {
|
||||
// const contextValue = {
|
||||
// getFieldsData: () => {},
|
||||
// } as unknown as LeftPanelContext;
|
||||
//
|
||||
// return (
|
||||
// <LeftFlyoutContext.Provider value={contextValue}>
|
||||
// <SessionView />
|
||||
// </LeftFlyoutContext.Provider>
|
||||
// );
|
||||
// };
|
||||
|
||||
export const Error: Story<void> = () => {
|
||||
const contextValue = {
|
||||
getFieldsData: () => {},
|
||||
} as unknown as LeftPanelContext;
|
||||
|
||||
return (
|
||||
<LeftFlyoutContext.Provider value={contextValue}>
|
||||
<SessionView />
|
||||
</LeftFlyoutContext.Provider>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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 '@testing-library/jest-dom';
|
||||
import type { LeftPanelContext } from '../context';
|
||||
import { LeftFlyoutContext } from '../context';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
import { SESSION_VIEW_ERROR_TEST_ID, SESSION_VIEW_TEST_ID } from './test_ids';
|
||||
import { SessionView } from './session_view';
|
||||
|
||||
jest.mock('../../../common/lib/kibana', () => {
|
||||
const originalModule = jest.requireActual('../../../common/lib/kibana');
|
||||
return {
|
||||
...originalModule,
|
||||
useKibana: jest.fn().mockReturnValue({
|
||||
services: {
|
||||
sessionView: {
|
||||
getSessionView: jest.fn().mockReturnValue(<div />),
|
||||
},
|
||||
},
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe('<SessionView />', () => {
|
||||
it('renders session view correctly', () => {
|
||||
const contextValue = {
|
||||
getFieldsData: () => 'id',
|
||||
} as unknown as LeftPanelContext;
|
||||
|
||||
const wrapper = render(
|
||||
<TestProviders>
|
||||
<LeftFlyoutContext.Provider value={contextValue}>
|
||||
<SessionView />
|
||||
</LeftFlyoutContext.Provider>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.getByTestId(SESSION_VIEW_TEST_ID)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render error message on null eventId', () => {
|
||||
const contextValue = {
|
||||
getFieldsData: () => {},
|
||||
} as unknown as LeftPanelContext;
|
||||
|
||||
const wrapper = render(
|
||||
<TestProviders>
|
||||
<LeftFlyoutContext.Provider value={contextValue}>
|
||||
<SessionView />
|
||||
</LeftFlyoutContext.Provider>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.getByTestId(SESSION_VIEW_ERROR_TEST_ID)).toBeInTheDocument();
|
||||
expect(wrapper.getByText('Unable to display session view')).toBeInTheDocument();
|
||||
expect(wrapper.getByText('There was an error displaying session view')).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -7,16 +7,46 @@
|
|||
|
||||
import type { FC } from 'react';
|
||||
import React from 'react';
|
||||
import { EuiText } from '@elastic/eui';
|
||||
import { SESSION_VIEW_TEST_ID } from './test_ids';
|
||||
import { EuiEmptyPrompt } from '@elastic/eui';
|
||||
import { getField } from '../../shared/utils';
|
||||
import { ERROR_MESSAGE, ERROR_TITLE } from '../../shared/translations';
|
||||
import { SESSION_VIEW_ERROR_MESSAGE } from './translations';
|
||||
import { SESSION_VIEW_ERROR_TEST_ID, SESSION_VIEW_TEST_ID } from './test_ids';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import { useLeftPanelContext } from '../context';
|
||||
|
||||
export const SESSION_VIEW_ID = 'session_view';
|
||||
const SESSION_ENTITY_ID = 'process.entry_leader.entity_id';
|
||||
|
||||
/**
|
||||
* Session view displayed in the document details expandable flyout left section under the Visualize tab
|
||||
*/
|
||||
export const SessionView: FC = () => {
|
||||
return <EuiText data-test-subj={SESSION_VIEW_TEST_ID}>{'Session view'}</EuiText>;
|
||||
const { sessionView } = useKibana().services;
|
||||
const { getFieldsData } = useLeftPanelContext();
|
||||
|
||||
const sessionEntityId = getField(getFieldsData(SESSION_ENTITY_ID));
|
||||
|
||||
if (!sessionEntityId) {
|
||||
return (
|
||||
<EuiEmptyPrompt
|
||||
iconType="error"
|
||||
color="danger"
|
||||
title={<h2>{ERROR_TITLE(SESSION_VIEW_ERROR_MESSAGE)}</h2>}
|
||||
body={<p>{ERROR_MESSAGE(SESSION_VIEW_ERROR_MESSAGE)}</p>}
|
||||
data-test-subj={SESSION_VIEW_ERROR_TEST_ID}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-test-subj={SESSION_VIEW_TEST_ID}>
|
||||
{sessionView.getSessionView({
|
||||
sessionEntityId,
|
||||
isFullScreen: true,
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
SessionView.displayName = 'SessionView';
|
||||
|
|
|
@ -9,6 +9,7 @@ export const ANALYZER_GRAPH_TEST_ID = 'securitySolutionDocumentDetailsFlyoutAnal
|
|||
export const ANALYZE_GRAPH_ERROR_TEST_ID =
|
||||
'securitySolutionDocumentDetailsFlyoutAnalyzerGraphError';
|
||||
export const SESSION_VIEW_TEST_ID = 'securitySolutionDocumentDetailsFlyoutSessionView';
|
||||
export const SESSION_VIEW_ERROR_TEST_ID = 'securitySolutionDocumentDetailsFlyoutSessionViewError';
|
||||
export const ENTITIES_DETAILS_TEST_ID = 'securitySolutionDocumentDetailsFlyoutEntitiesDetails';
|
||||
export const THREAT_INTELLIGENCE_DETAILS_TEST_ID =
|
||||
'securitySolutionDocumentDetailsFlyoutThreatIntelligenceDetails';
|
||||
|
|
|
@ -13,3 +13,10 @@ export const ANALYZER_ERROR_MESSAGE = i18n.translate(
|
|||
defaultMessage: 'analyzer',
|
||||
}
|
||||
);
|
||||
|
||||
export const SESSION_VIEW_ERROR_MESSAGE = i18n.translate(
|
||||
'xpack.securitySolution.flyout.sessionViewErrorTitle',
|
||||
{
|
||||
defaultMessage: 'session view',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -6,6 +6,16 @@
|
|||
*/
|
||||
|
||||
import React, { createContext, useContext, useMemo } from 'react';
|
||||
import { EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
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 { useGetFieldsData } from '../../common/hooks/use_get_fields_data';
|
||||
import { useRouteSpy } from '../../common/utils/route/use_route_spy';
|
||||
import { useSpaceId } from '../../common/hooks/use_space_id';
|
||||
import { getAlertIndexAlias } from '../../timelines/components/side_panel/event_details/helpers';
|
||||
import type { LeftPanelProps } from '.';
|
||||
|
||||
export interface LeftPanelContext {
|
||||
|
@ -17,6 +27,10 @@ export interface LeftPanelContext {
|
|||
* Name of the index used in the parent's page
|
||||
*/
|
||||
indexName: string;
|
||||
/**
|
||||
* Retrieves searchHit values for the provided field
|
||||
*/
|
||||
getFieldsData: (field: string) => unknown | unknown[];
|
||||
}
|
||||
|
||||
export const LeftFlyoutContext = createContext<LeftPanelContext | undefined>(undefined);
|
||||
|
@ -29,11 +43,40 @@ export type LeftPanelProviderProps = {
|
|||
} & Partial<LeftPanelProps['params']>;
|
||||
|
||||
export const LeftPanelProvider = ({ id, indexName, children }: LeftPanelProviderProps) => {
|
||||
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 [loading, _, searchHit] = useTimelineEventsDetails({
|
||||
indexName: eventIndex,
|
||||
eventId: id ?? '',
|
||||
runtimeMappings: sourcererDataView.runtimeMappings,
|
||||
skip: !id,
|
||||
});
|
||||
const getFieldsData = useGetFieldsData(searchHit?.fields);
|
||||
|
||||
const contextValue = useMemo(
|
||||
() => (id && indexName ? { eventId: id, indexName } : undefined),
|
||||
[id, indexName]
|
||||
() => (id && indexName ? { eventId: id, indexName, getFieldsData } : undefined),
|
||||
[id, indexName, getFieldsData]
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<EuiFlexItem
|
||||
css={css`
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`}
|
||||
>
|
||||
<EuiLoadingSpinner size="xxl" />
|
||||
</EuiFlexItem>
|
||||
);
|
||||
}
|
||||
|
||||
return <LeftFlyoutContext.Provider value={contextValue}>{children}</LeftFlyoutContext.Provider>;
|
||||
};
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import { SecurityPageName } from '../../../common/constants';
|
|||
import { SourcererScopeName } from '../../common/store/sourcerer/model';
|
||||
import { useSourcererDataView } from '../../common/containers/sourcerer';
|
||||
import type { RightPanelProps } from '.';
|
||||
import type { GetFieldsData } from '../../common/hooks/use_get_fields_data';
|
||||
import { useGetFieldsData } from '../../common/hooks/use_get_fields_data';
|
||||
|
||||
export interface RightPanelContext {
|
||||
|
@ -57,7 +58,7 @@ export interface RightPanelContext {
|
|||
/**
|
||||
* Retrieves searchHit values for the provided field
|
||||
*/
|
||||
getFieldsData: (field: string) => unknown | unknown[];
|
||||
getFieldsData: GetFieldsData;
|
||||
}
|
||||
|
||||
export const RightPanelContext = createContext<RightPanelContext | undefined>(undefined);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue