Skip to content

Commit

Permalink
[Security Solution] Security Assistant: context provider hooks elastic#6
Browse files Browse the repository at this point in the history


- adds hooks that provide context to the Security Assistant
  • Loading branch information
andrew-goldstein authored May 10, 2023
1 parent 3473f39 commit 767523c
Show file tree
Hide file tree
Showing 19 changed files with 488 additions and 166 deletions.
41 changes: 22 additions & 19 deletions x-pack/plugins/security_solution/public/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { MlCapabilitiesProvider } from '../common/components/ml/permissions/ml_c
import { GlobalToaster, ManageGlobalToaster } from '../common/components/toasters';
import { KibanaContextProvider, useKibana, useUiSetting$ } from '../common/lib/kibana';
import type { State } from '../common/store';

import { SecurityAssistantProvider } from '../security_assistant/security_assistant_context';
import type { StartServices } from '../types';
import { PageRouter } from './routes';
import { UserPrivilegesProvider } from '../common/components/user_privileges/user_privileges_context';
Expand All @@ -50,6 +50,7 @@ const StartAppComponent: FC<StartAppComponent> = ({
const {
i18n,
application: { capabilities },
http,
uiActions,
} = useKibana().services;
const [darkMode] = useUiSetting$<boolean>(DEFAULT_DARK_MODE);
Expand All @@ -60,25 +61,27 @@ const StartAppComponent: FC<StartAppComponent> = ({
<ReduxStoreProvider store={store}>
<KibanaThemeProvider theme$={theme$}>
<EuiThemeProvider darkMode={darkMode}>
<MlCapabilitiesProvider>
<UserPrivilegesProvider kibanaCapabilities={capabilities}>
<ManageUserInfo>
<ReactQueryClientProvider>
<CellActionsProvider
getTriggerCompatibleActions={uiActions.getTriggerCompatibleActions}
>
<PageRouter
history={history}
onAppLeave={onAppLeave}
setHeaderActionMenu={setHeaderActionMenu}
<SecurityAssistantProvider httpFetch={http.fetch}>
<MlCapabilitiesProvider>
<UserPrivilegesProvider kibanaCapabilities={capabilities}>
<ManageUserInfo>
<ReactQueryClientProvider>
<CellActionsProvider
getTriggerCompatibleActions={uiActions.getTriggerCompatibleActions}
>
{children}
</PageRouter>
</CellActionsProvider>
</ReactQueryClientProvider>
</ManageUserInfo>
</UserPrivilegesProvider>
</MlCapabilitiesProvider>
<PageRouter
history={history}
onAppLeave={onAppLeave}
setHeaderActionMenu={setHeaderActionMenu}
>
{children}
</PageRouter>
</CellActionsProvider>
</ReactQueryClientProvider>
</ManageUserInfo>
</UserPrivilegesProvider>
</MlCapabilitiesProvider>
</SecurityAssistantProvider>
</EuiThemeProvider>
</KibanaThemeProvider>
<ErrorToastDispatcher />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@
* 2.0.
*/

import React, { useMemo } from 'react';

import type { BrowserFields } from '../../../../common/search_strategy/index_fields';
import { SummaryView } from './summary_view';
import React, { useCallback, useEffect, useMemo } from 'react';

import type { TimelineEventsDetailsItem } from '../../../../common/search_strategy';

import type { BrowserFields } from '../../../../common/search_strategy/index_fields';
import { getSummaryRows } from './get_alert_summary_rows';
import { useSecurityAssistantContext } from '../../../security_assistant/security_assistant_context';
import { SummaryView } from './summary_view';
import * as i18n from './translations';
import { getAutoRunPromptFromEventDetailsItem } from '../../../security_assistant/prompt/helpers';
import { getPromptContextFromEventDetailsItem } from '../../../security_assistant/prompt_context/helpers';
import { getUniquePromptContextId } from '../../../security_assistant/security_assistant_context/helpers';

const AlertSummaryViewComponent: React.FC<{
browserFields: BrowserFields;
Expand All @@ -29,13 +32,44 @@ const AlertSummaryViewComponent: React.FC<{
[browserFields, data, eventId, isDraggable, scopeId, isReadOnly]
);

const { registerPromptContext, unRegisterPromptContext } = useSecurityAssistantContext();
const promptContextId = useMemo(() => getUniquePromptContextId(), []);

const getPromptContext = useCallback(
async () => getPromptContextFromEventDetailsItem(data),
[data]
);
const getAutoRunPrompt = useCallback(
async () => getAutoRunPromptFromEventDetailsItem(data),
[data]
);

useEffect(() => {
registerPromptContext({
category: 'alert',
description: i18n.ALERT_SUMMARY_VIEW_CONTEXT_DESCRIPTION,
id: promptContextId,
getPromptContext,
getAutoRunPrompt,
tooltip: i18n.ALERT_SUMMARY_VIEW_CONTEXT_TOOLTIP,
});

return () => unRegisterPromptContext(promptContextId);
}, [
getAutoRunPrompt,
getPromptContext,
promptContextId,
registerPromptContext,
unRegisterPromptContext,
]);

return (
<SummaryView
data={data}
rows={summaryRows}
title={title}
goToTable={goToTable}
isReadOnly={isReadOnly}
promptContextId={promptContextId}
rows={summaryRows}
title={title}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ const enrichedHostIpData: AlertSummaryRow['description'] = {
};

const mockCount = 90019001;
const promptContextId = 'abcd';

jest.mock('../../containers/alerts/use_alert_prevalence', () => ({
useAlertPrevalence: () => ({
loading: false,
Expand All @@ -66,7 +68,12 @@ describe('Summary View', () => {
test('should show an empty table', () => {
render(
<TestProviders>
<SummaryView data={[]} goToTable={jest.fn()} title="Test Summary View" rows={[]} />
<SummaryView
goToTable={jest.fn()}
title="Test Summary View"
promptContextId={promptContextId}
rows={[]}
/>
</TestProviders>
);
expect(screen.getByText('No items found')).toBeInTheDocument();
Expand All @@ -85,9 +92,9 @@ describe('Summary View', () => {
render(
<TestProviders>
<SummaryView
data={[]}
goToTable={jest.fn()}
title="Test Summary View"
promptContextId={promptContextId}
rows={sampleRows}
/>
</TestProviders>
Expand Down Expand Up @@ -118,9 +125,9 @@ describe('Summary View', () => {
render(
<TestProviders>
<SummaryView
data={[]}
goToTable={jest.fn()}
title="Test Summary View"
promptContextId={promptContextId}
rows={sampleRows}
isReadOnly={true}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
} from '@elastic/eui';
import React from 'react';

import type { TimelineEventsDetailsItem } from '../../../../common/search_strategy';
import type { AlertSummaryRow } from './helpers';
import { NewChat } from '../../../security_assistant/new_chat';
import * as i18n from './translations';
Expand Down Expand Up @@ -69,17 +68,17 @@ const rowProps = {
};

const SummaryViewComponent: React.FC<{
data: TimelineEventsDetailsItem[];
goToTable: () => void;
title: string;
promptContextId: string;
rows: AlertSummaryRow[];
isReadOnly?: boolean;
}> = ({ data, goToTable, rows, title, isReadOnly }) => {
}> = ({ goToTable, promptContextId, rows, title, isReadOnly }) => {
const columns = isReadOnly ? baseColumns : allColumns;

return (
<div>
<NewChat data={data} />
<NewChat promptContextId={promptContextId} />
<EuiFlexGroup>
<EuiFlexItem>
<EuiTitle size="xxxs">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,17 @@ export const ALERT_REASON = i18n.translate('xpack.securitySolution.eventDetails.
export const VIEW_ALL_FIELDS = i18n.translate('xpack.securitySolution.eventDetails.viewAllFields', {
defaultMessage: 'View all fields in table',
});

export const ALERT_SUMMARY_VIEW_CONTEXT_DESCRIPTION = i18n.translate(
'xpack.securitySolution.alertSummaryView.contextDescription',
{
defaultMessage: 'Alert (summary view)',
}
);

export const ALERT_SUMMARY_VIEW_CONTEXT_TOOLTIP = i18n.translate(
'xpack.securitySolution.alertSummaryView.contextTooltip',
{
defaultMessage: 'Use this alert for context',
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ import React from 'react';
import { Provider as ReduxStoreProvider } from 'react-redux';
import { BehaviorSubject, Subject } from 'rxjs';
import { ThemeProvider } from 'styled-components';
import { httpServiceMock } from '@kbn/core-http-browser-mocks';
import type { CoreStart } from '@kbn/core/public';
import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public';
import { I18nProvider } from '@kbn/i18n-react';
import { CellActionsProvider } from '@kbn/cell-actions';
import { SecurityAssistantProvider } from '../../security_assistant/security_assistant_context';
import { createStore } from '../store';
import { mockGlobalState } from './global_state';
import { SUB_PLUGINS_REDUCER } from './utils';
Expand Down Expand Up @@ -52,13 +54,17 @@ const KibanaReactContext = createKibanaReactContext(coreMock);
*/
export const StorybookProviders: React.FC = ({ children }) => {
const store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
const http = httpServiceMock.createSetupContract({ basePath: '/test' });

return (
<I18nProvider>
<KibanaReactContext.Provider>
<CellActionsProvider getTriggerCompatibleActions={() => Promise.resolve([])}>
<ReduxStoreProvider store={store}>
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>
{children}
<SecurityAssistantProvider httpFetch={http.fetch}>
{children}
</SecurityAssistantProvider>
</ThemeProvider>
</ReduxStoreProvider>
</CellActionsProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type { Store } from 'redux';
import { BehaviorSubject } from 'rxjs';
import { ThemeProvider } from 'styled-components';
import type { Capabilities } from '@kbn/core/public';
import { httpServiceMock } from '@kbn/core-http-browser-mocks';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

import type { Action } from '@kbn/ui-actions-plugin/public';
Expand All @@ -34,6 +35,7 @@ import { SUB_PLUGINS_REDUCER } from './utils';
import { createSecuritySolutionStorageMock, localStorageMock } from './mock_local_storage';
import { CASES_FEATURE_ID } from '../../../common/constants';
import { UserPrivilegesProvider } from '../components/user_privileges/user_privileges_context';
import { SecurityAssistantProvider } from '../../security_assistant/security_assistant_context';

const state: State = mockGlobalState;

Expand Down Expand Up @@ -61,22 +63,26 @@ export const TestProvidersComponent: React.FC<Props> = ({
cellActions = [],
}) => {
const queryClient = new QueryClient();
const http = httpServiceMock.createSetupContract({ basePath: '/test' });

return (
<I18nProvider>
<MockKibanaContextProvider>
<ReduxStoreProvider store={store}>
<ThemeProvider theme={() => ({ eui: euiDarkVars, darkMode: true })}>
<QueryClientProvider client={queryClient}>
<ExpandableFlyoutProvider>
<ConsoleManager>
<CellActionsProvider
getTriggerCompatibleActions={() => Promise.resolve(cellActions)}
>
<DragDropContext onDragEnd={onDragEnd}>{children}</DragDropContext>
</CellActionsProvider>
</ConsoleManager>
</ExpandableFlyoutProvider>
</QueryClientProvider>
<SecurityAssistantProvider httpFetch={http.fetch}>
<QueryClientProvider client={queryClient}>
<ExpandableFlyoutProvider>
<ConsoleManager>
<CellActionsProvider
getTriggerCompatibleActions={() => Promise.resolve(cellActions)}
>
<DragDropContext onDragEnd={onDragEnd}>{children}</DragDropContext>
</CellActionsProvider>
</ConsoleManager>
</ExpandableFlyoutProvider>
</QueryClientProvider>
</SecurityAssistantProvider>
</ThemeProvider>
</ReduxStoreProvider>
</MockKibanaContextProvider>
Expand All @@ -93,28 +99,36 @@ const TestProvidersWithPrivilegesComponent: React.FC<Props> = ({
store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage),
onDragEnd = jest.fn(),
cellActions = [],
}) => (
<I18nProvider>
<MockKibanaContextProvider>
<ReduxStoreProvider store={store}>
<ThemeProvider theme={() => ({ eui: euiDarkVars, darkMode: true })}>
<UserPrivilegesProvider
kibanaCapabilities={
{
siem: { show: true, crud: true },
[CASES_FEATURE_ID]: { read_cases: true, crud_cases: false },
} as unknown as Capabilities
}
>
<CellActionsProvider getTriggerCompatibleActions={() => Promise.resolve(cellActions)}>
<DragDropContext onDragEnd={onDragEnd}>{children}</DragDropContext>
</CellActionsProvider>
</UserPrivilegesProvider>
</ThemeProvider>
</ReduxStoreProvider>
</MockKibanaContextProvider>
</I18nProvider>
);
}) => {
const http = httpServiceMock.createSetupContract({ basePath: '/test' });

return (
<I18nProvider>
<MockKibanaContextProvider>
<ReduxStoreProvider store={store}>
<ThemeProvider theme={() => ({ eui: euiDarkVars, darkMode: true })}>
<SecurityAssistantProvider httpFetch={http.fetch}>
<UserPrivilegesProvider
kibanaCapabilities={
{
siem: { show: true, crud: true },
[CASES_FEATURE_ID]: { read_cases: true, crud_cases: false },
} as unknown as Capabilities
}
>
<CellActionsProvider
getTriggerCompatibleActions={() => Promise.resolve(cellActions)}
>
<DragDropContext onDragEnd={onDragEnd}>{children}</DragDropContext>
</CellActionsProvider>
</UserPrivilegesProvider>
</SecurityAssistantProvider>
</ThemeProvider>
</ReduxStoreProvider>
</MockKibanaContextProvider>
</I18nProvider>
);
};

export const TestProviders = React.memo(TestProvidersComponent);
export const TestProvidersWithPrivileges = React.memo(TestProvidersWithPrivilegesComponent);
Expand Down
Loading

0 comments on commit 767523c

Please sign in to comment.