mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution] Add analyzer to expandable flyout (#153709)
## Summary This PR build on previously merged PR (https://github.com/elastic/kibana/pull/152150), and adds analyzer to the left section of expandable flyout under `Visualize`->`Analyzer Graph`.  **How to test** - add `xpack.securitySolution.enableExperimental: ['securityFlyoutEnabled']` to the `kibana.dev.json` file - go to the Alerts page, and click on the expand detail button on any row of the table - then click on the expand details button in the flyout's header - click `Visualize`, then `Analyzer Graph` ### Checklist Delete any items that are not applicable to this PR. - [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 --------- Co-authored-by: PhilippeOberti <philippe.oberti@elastic.co>
This commit is contained in:
parent
9517d067b5
commit
d210aff066
11 changed files with 290 additions and 19 deletions
|
@ -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 { ANALYZER_NODE } from '../../../screens/alerts';
|
||||
import { DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_CONTENT } from '../../../screens/document_expandable_flyout';
|
||||
import {
|
||||
expandFirstAlertExpandableFlyout,
|
||||
openGraphAnalyzer,
|
||||
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 analyzer graph',
|
||||
{ testIsolation: false },
|
||||
() => {
|
||||
before(() => {
|
||||
cleanKibana();
|
||||
login();
|
||||
createRule(getNewRule());
|
||||
visit(ALERTS_URL);
|
||||
waitForAlertsToPopulate();
|
||||
expandFirstAlertExpandableFlyout();
|
||||
expandDocumentDetailsExpandableFlyoutLeftSection();
|
||||
openGraphAnalyzer();
|
||||
});
|
||||
|
||||
it('should display analyzer graph and node list', () => {
|
||||
cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_GRAPH_ANALYZER_CONTENT).should('be.visible');
|
||||
cy.get(ANALYZER_NODE).first().should('be.visible');
|
||||
});
|
||||
}
|
||||
);
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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 { Provider as ReduxStoreProvider } from 'react-redux';
|
||||
import { configureStore } from '@reduxjs/toolkit';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public';
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import { sourcererReducer } from '../../../common/store/sourcerer';
|
||||
import { inputsReducer } from '../../../common/store/inputs';
|
||||
import type { LeftPanelContext } from '../context';
|
||||
import { LeftFlyoutContext } from '../context';
|
||||
import { AnalyzeGraph } from './analyze_graph';
|
||||
|
||||
export default {
|
||||
component: AnalyzeGraph,
|
||||
title: 'Flyout/AnalyzeGraph',
|
||||
};
|
||||
|
||||
// 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 = {
|
||||
// eventId: 'some_id',
|
||||
// } as unknown as LeftPanelContext;
|
||||
//
|
||||
// return (
|
||||
// <LeftFlyoutContext.Provider value={contextValue}>
|
||||
// <AnalyzeGraph />
|
||||
// </LeftFlyoutContext.Provider>
|
||||
// );
|
||||
// };
|
||||
|
||||
export const Error: Story<void> = () => {
|
||||
const store = configureStore({
|
||||
reducer: {
|
||||
inputs: inputsReducer,
|
||||
sourcerer: sourcererReducer,
|
||||
},
|
||||
});
|
||||
const services = {
|
||||
data: {},
|
||||
notifications: {
|
||||
toasts: {
|
||||
addError: () => {},
|
||||
addSuccess: () => {},
|
||||
addWarning: () => {},
|
||||
remove: () => {},
|
||||
},
|
||||
},
|
||||
} as unknown as CoreStart;
|
||||
const KibanaReactContext = createKibanaReactContext({ ...services });
|
||||
|
||||
const contextValue = {
|
||||
eventId: null,
|
||||
} as unknown as LeftPanelContext;
|
||||
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<ReduxStoreProvider store={store}>
|
||||
<KibanaReactContext.Provider>
|
||||
<LeftFlyoutContext.Provider value={contextValue}>
|
||||
<AnalyzeGraph />
|
||||
</LeftFlyoutContext.Provider>
|
||||
</KibanaReactContext.Provider>
|
||||
</ReduxStoreProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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 { AnalyzeGraph } from './analyze_graph';
|
||||
import { ANALYZE_GRAPH_ERROR_TEST_ID, ANALYZER_GRAPH_TEST_ID } from './test_ids';
|
||||
|
||||
jest.mock('react-router-dom', () => {
|
||||
const actual = jest.requireActual('react-router-dom');
|
||||
return { ...actual, useLocation: jest.fn().mockReturnValue({ pathname: '' }) };
|
||||
});
|
||||
|
||||
jest.mock('../../../resolver/view/use_resolver_query_params_cleaner');
|
||||
|
||||
const mockDispatch = jest.fn();
|
||||
jest.mock('react-redux', () => {
|
||||
const original = jest.requireActual('react-redux');
|
||||
|
||||
return {
|
||||
...original,
|
||||
useDispatch: () => mockDispatch,
|
||||
};
|
||||
});
|
||||
|
||||
describe('<AnalyzeGraph />', () => {
|
||||
it('renders analyzer graph correctly', () => {
|
||||
const contextValue = {
|
||||
eventId: 'eventId',
|
||||
} as unknown as LeftPanelContext;
|
||||
|
||||
const wrapper = render(
|
||||
<TestProviders>
|
||||
<LeftFlyoutContext.Provider value={contextValue}>
|
||||
<AnalyzeGraph />
|
||||
</LeftFlyoutContext.Provider>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.getByTestId(ANALYZER_GRAPH_TEST_ID)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render error message on null eventId', () => {
|
||||
const contextValue = {
|
||||
eventId: null,
|
||||
} as unknown as LeftPanelContext;
|
||||
|
||||
const wrapper = render(
|
||||
<TestProviders>
|
||||
<LeftFlyoutContext.Provider value={contextValue}>
|
||||
<AnalyzeGraph />
|
||||
</LeftFlyoutContext.Provider>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.getByTestId(ANALYZE_GRAPH_ERROR_TEST_ID)).toBeInTheDocument();
|
||||
expect(wrapper.getByText('Unable to display analyzer')).toBeInTheDocument();
|
||||
expect(wrapper.getByText('There was an error displaying analyzer')).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -7,8 +7,15 @@
|
|||
|
||||
import type { FC } from 'react';
|
||||
import React from 'react';
|
||||
import { EuiText } from '@elastic/eui';
|
||||
import { ANALYZER_GRAPH_TEST_ID } from './test_ids';
|
||||
import { EuiEmptyPrompt } from '@elastic/eui';
|
||||
|
||||
import { ANALYZER_ERROR_MESSAGE } from './translations';
|
||||
import { useLeftPanelContext } from '../context';
|
||||
import { ANALYZE_GRAPH_ERROR_TEST_ID, ANALYZER_GRAPH_TEST_ID } from './test_ids';
|
||||
import { Resolver } from '../../../resolver/view';
|
||||
import { useTimelineDataFilters } from '../../../timelines/containers/use_timeline_data_filters';
|
||||
import { ERROR_TITLE, ERROR_MESSAGE } from '../../shared/translations';
|
||||
import { isActiveTimeline } from '../../../helpers';
|
||||
|
||||
export const ANALYZE_GRAPH_ID = 'analyze_graph';
|
||||
|
||||
|
@ -16,7 +23,35 @@ export const ANALYZE_GRAPH_ID = 'analyze_graph';
|
|||
* Analyzer graph view displayed in the document details expandable flyout left section under the Visualize tab
|
||||
*/
|
||||
export const AnalyzeGraph: FC = () => {
|
||||
return <EuiText data-test-subj={ANALYZER_GRAPH_TEST_ID}>{'Analyzer graph'}</EuiText>;
|
||||
const { eventId } = useLeftPanelContext();
|
||||
const scopeId = 'fly-out';
|
||||
const { from, to, shouldUpdate, selectedPatterns } = useTimelineDataFilters(
|
||||
isActiveTimeline(scopeId)
|
||||
);
|
||||
|
||||
if (!eventId) {
|
||||
return (
|
||||
<EuiEmptyPrompt
|
||||
iconType="error"
|
||||
color="danger"
|
||||
title={<h2>{ERROR_TITLE(ANALYZER_ERROR_MESSAGE)}</h2>}
|
||||
body={<p>{ERROR_MESSAGE(ANALYZER_ERROR_MESSAGE)}</p>}
|
||||
data-test-subj={ANALYZE_GRAPH_ERROR_TEST_ID}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-test-subj={ANALYZER_GRAPH_TEST_ID}>
|
||||
<Resolver
|
||||
databaseDocumentID={eventId}
|
||||
resolverComponentInstanceID={scopeId}
|
||||
indices={selectedPatterns}
|
||||
shouldUpdate={shouldUpdate}
|
||||
filters={{ from, to }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
AnalyzeGraph.displayName = 'AnalyzeGraph';
|
||||
|
|
|
@ -6,4 +6,6 @@
|
|||
*/
|
||||
|
||||
export const ANALYZER_GRAPH_TEST_ID = 'securitySolutionDocumentDetailsFlyoutAnalyzerGraph';
|
||||
export const ANALYZE_GRAPH_ERROR_TEST_ID =
|
||||
'securitySolutionDocumentDetailsFlyoutAnalyzerGraphError';
|
||||
export const SESSION_VIEW_TEST_ID = 'securitySolutionDocumentDetailsFlyoutSessionView';
|
||||
|
|
|
@ -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 { i18n } from '@kbn/i18n';
|
||||
|
||||
export const ANALYZER_ERROR_MESSAGE = i18n.translate(
|
||||
'xpack.securitySolution.flyout.analyzerErrorTitle',
|
||||
{
|
||||
defaultMessage: 'analyzer',
|
||||
}
|
||||
);
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import type { FC } from 'react';
|
||||
import React, { memo, useState } from 'react';
|
||||
import React, { memo, useState, useCallback } from 'react';
|
||||
import { EuiButtonGroup, EuiSpacer } from '@elastic/eui';
|
||||
import type { EuiButtonGroupOptionProps } from '@elastic/eui/src/components/button/button_group/button_group';
|
||||
import {
|
||||
|
@ -21,6 +21,8 @@ import {
|
|||
VISUALIZE_BUTTONGROUP_OPTIONS,
|
||||
} from './translations';
|
||||
import { SESSION_VIEW_ID, SessionView } from '../components/session_view';
|
||||
import { ALERTS_ACTIONS } from '../../../common/lib/apm/user_actions';
|
||||
import { useStartTransaction } from '../../../common/lib/apm/use_start_transaction';
|
||||
|
||||
const visualizeButtons: EuiButtonGroupOptionProps[] = [
|
||||
{
|
||||
|
@ -40,9 +42,16 @@ const visualizeButtons: EuiButtonGroupOptionProps[] = [
|
|||
*/
|
||||
export const VisualizeTab: FC = memo(() => {
|
||||
const [activeVisualizationId, setActiveVisualizationId] = useState(SESSION_VIEW_ID);
|
||||
const onChangeCompressed = (optionId: string) => {
|
||||
setActiveVisualizationId(optionId);
|
||||
};
|
||||
const { startTransaction } = useStartTransaction();
|
||||
const onChangeCompressed = useCallback(
|
||||
(optionId: string) => {
|
||||
setActiveVisualizationId(optionId);
|
||||
if (optionId === ANALYZE_GRAPH_ID) {
|
||||
startTransaction({ name: ALERTS_ACTIONS.OPEN_ANALYZER });
|
||||
}
|
||||
},
|
||||
[startTransaction]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -8,8 +8,9 @@
|
|||
import { EuiEmptyPrompt } from '@elastic/eui';
|
||||
import type { FC } from 'react';
|
||||
import React, { memo } from 'react';
|
||||
import { DOCUMENT_ERROR_DETAILS, DOCUMENT_ERROR_TITLE } from './translations';
|
||||
import { JSON_TAB_ERROR_TEST_ID } from './test_ids';
|
||||
import { ERROR_MESSAGE, ERROR_TITLE } from './translations';
|
||||
import { ERROR_TITLE, ERROR_MESSAGE } from '../../shared/translations';
|
||||
import { JsonView } from '../../../common/components/event_details/json_view';
|
||||
import { useRightPanelContext } from '../context';
|
||||
|
||||
|
@ -24,8 +25,8 @@ export const JsonTab: FC = memo(() => {
|
|||
<EuiEmptyPrompt
|
||||
iconType="error"
|
||||
color="danger"
|
||||
title={<h2>{ERROR_TITLE}</h2>}
|
||||
body={<p>{ERROR_MESSAGE}</p>}
|
||||
title={<h2>{ERROR_TITLE(DOCUMENT_ERROR_TITLE)}</h2>}
|
||||
body={<p>{ERROR_MESSAGE(DOCUMENT_ERROR_DETAILS)}</p>}
|
||||
data-test-subj={JSON_TAB_ERROR_TEST_ID}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
import type { FC } from 'react';
|
||||
import React, { memo } from 'react';
|
||||
import { EuiEmptyPrompt } from '@elastic/eui';
|
||||
import { ERROR_MESSAGE, ERROR_TITLE } from './translations';
|
||||
import { DOCUMENT_ERROR_DETAILS, DOCUMENT_ERROR_TITLE } from './translations';
|
||||
import { ERROR_TITLE, ERROR_MESSAGE } from '../../shared/translations';
|
||||
import { TimelineTabs } from '../../../../common/types';
|
||||
import { EventFieldsBrowser } from '../../../common/components/event_details/event_fields_browser';
|
||||
import { useRightPanelContext } from '../context';
|
||||
|
@ -25,8 +26,8 @@ export const TableTab: FC = memo(() => {
|
|||
<EuiEmptyPrompt
|
||||
iconType="error"
|
||||
color="danger"
|
||||
title={<h2>{ERROR_TITLE}</h2>}
|
||||
body={<p>{ERROR_MESSAGE}</p>}
|
||||
title={<h2>{ERROR_TITLE(DOCUMENT_ERROR_TITLE)}</h2>}
|
||||
body={<p>{ERROR_MESSAGE(DOCUMENT_ERROR_DETAILS)}</p>}
|
||||
data-test-subj={TABLE_TAB_ERROR_TEST_ID}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -7,14 +7,16 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const ERROR_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.flyout.documentDetails.errorTitle',
|
||||
export const DOCUMENT_ERROR_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.flyout.documentErrorTitle',
|
||||
{
|
||||
defaultMessage: 'Unable to display document information',
|
||||
defaultMessage: 'document information',
|
||||
}
|
||||
);
|
||||
|
||||
export const ERROR_MESSAGE = i18n.translate(
|
||||
'xpack.securitySolution.flyout.documentDetails.errorMessage',
|
||||
{ defaultMessage: 'There was an error displaying the document fields and values' }
|
||||
export const DOCUMENT_ERROR_DETAILS = i18n.translate(
|
||||
'xpack.securitySolution.flyout.documentErrorMessage',
|
||||
{
|
||||
defaultMessage: 'the document fields and values',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
|
||||
export const ERROR_TITLE = (title: string) =>
|
||||
i18n.translate('xpack.securitySolution.flyout.errorTitle', {
|
||||
values: { title },
|
||||
defaultMessage: 'Unable to display {title}',
|
||||
});
|
||||
|
||||
export const ERROR_MESSAGE = (message: string) =>
|
||||
i18n.translate('xpack.securitySolution.flyout.errorMessage', {
|
||||
values: { message },
|
||||
defaultMessage: 'There was an error displaying {message}',
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue