Skip to content

Commit

Permalink
[Security Solution][Cases] Re-enable timeline functionality (#96496)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelolo24 authored Apr 13, 2021
1 parent 47b4a0f commit 636524a
Show file tree
Hide file tree
Showing 19 changed files with 461 additions and 162 deletions.
24 changes: 22 additions & 2 deletions x-pack/plugins/cases/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ cases: CasesUiStart;
cases.getCreateCase({
onCancel: handleSetIsCancel,
onSuccess,
timelineIntegration?: {
plugins: {
parsingPlugin,
processingPluginRenderer,
uiPlugin,
},
hooks: {
useInsertTimeline,
},
},
})
```

Expand Down Expand Up @@ -70,11 +80,16 @@ Arguments:
|createCaseNavigation|`CasesNavigation` route configuration for create cases page
|getCaseDetailHrefWithCommentId|`(commentId: string) => string;` callback to generate the case details url with a comment id reference from the case id and comment id
|onComponentInitialized?|`() => void;` callback when component has initialized
|renderInvestigateInTimelineActionComponent?|: `(alertIds: string[]) => JSX.Element;` space to render `InvestigateInTimelineActionComponent`
|renderTimelineDetailsPanel?|: `() => JSX.Element;` space to render `TimelineDetailsPanel`
|ruleDetailsNavigation|: `CasesNavigation<string | null | undefined, 'configurable'>`
|showAlertDetails|: `(alertId: string, index: string) => void;` callback to show alert details
|subCaseId?|: `string;` subcase id
|timelineIntegration?.editor_plugins|: Plugins needed for integrating timeline into markdown editor.
|timelineIntegration?.editor_plugins.parsingPlugin|: `Plugin;`
|timelineIntegration?.editor_plugins.processingPluginRenderer|: `React.FC<TimelineProcessingPluginRendererProps & { position: EuiMarkdownAstNodePosition }>`
|timelineIntegration?.editor_plugins.uiPlugin?|: `EuiMarkdownEditorUiPlugin`
|timelineIntegration?.hooks.useInsertTimeline|: `(value: string, onChange: (newValue: string) => void): UseInsertTimelineReturn`
|timelineIntegration?.ui?.renderInvestigateInTimelineActionComponent?|: `(alertIds: string[]) => JSX.Element;` space to render `InvestigateInTimelineActionComponent`
|timelineIntegration?.ui?renderTimelineDetailsPanel?|: `() => JSX.Element;` space to render `TimelineDetailsPanel`
|useFetchAlertData|: `(alertIds: string[]) => [boolean, Record<string, Ecs>];` fetch alerts
|userCanCrud|: `boolean;` user permissions to crud

Expand All @@ -89,6 +104,11 @@ Arguments:
|afterCaseCreated?|`(theCase: Case) => Promise<void>;` callback passing newly created case before pushCaseToExternalService is called
|onCancel|`() => void;` callback when create case is canceled
|onSuccess|`(theCase: Case) => Promise<void>;` callback passing newly created case after pushCaseToExternalService is called
|timelineIntegration?.editor_plugins|: Plugins needed for integrating timeline into markdown editor.
|timelineIntegration?.editor_plugins.parsingPlugin|: `Plugin;`
|timelineIntegration?.editor_plugins.processingPluginRenderer|: `React.FC<TimelineProcessingPluginRendererProps & { position: EuiMarkdownAstNodePosition }>`
|timelineIntegration?.editor_plugins.uiPlugin?|: `EuiMarkdownEditorUiPlugin`
|timelineIntegration?.hooks.useInsertTimeline|: `(value: string, onChange: (newValue: string) => void): UseInsertTimelineReturn`

UI component:
![Create Component][create-img]
Expand Down
33 changes: 33 additions & 0 deletions x-pack/plugins/cases/public/components/__mock__/timeline.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* 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 { useTimelineContext } from '../timeline_context/use_timeline_context';
jest.mock('../timeline_context');

const mockTimelineComponent = (name: string) => <span data-test-subj={name}>{name}</span>;

export const timelineIntegrationMock = {
editor_plugins: {
parsingPlugin: jest.fn(),
processingPluginRenderer: () => mockTimelineComponent('plugin-renderer'),
uiPlugin: {
name: 'mock-timeline',
button: { label: 'mock-timeline-button', iconType: 'mock-timeline-icon' },
editor: () => mockTimelineComponent('plugin-timeline-editor'),
},
},
hooks: {
useInsertTimeline: jest.fn(),
},
ui: {
renderInvestigateInTimelineActionComponent: () =>
mockTimelineComponent('investigate-in-timeline'),
renderTimelineDetailsPanel: () => mockTimelineComponent('timeline-details-panel'),
},
};

export const useTimelineContextMock = useTimelineContext as jest.Mock;
61 changes: 31 additions & 30 deletions x-pack/plugins/cases/public/components/add_comment/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,20 @@
import React from 'react';
import { mount } from 'enzyme';
import { waitFor, act } from '@testing-library/react';
// import { noop } from 'lodash/fp';
import { noop } from 'lodash/fp';

import { TestProviders } from '../../common/mock';
import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router';

import { CommentRequest, CommentType } from '../../../common';
// TODO: Timeline Integration
// import { useInsertTimeline } from '../use_insert_timeline';
import { usePostComment } from '../../containers/use_post_comment';
import { AddComment, AddCommentRefObject } from '.';
import { CasesTimelineIntegrationProvider } from '../timeline_context';
import { timelineIntegrationMock } from '../__mock__/timeline';

jest.mock('../../containers/use_post_comment');
// TODO: Timeline Integration
// jest.mock('../use_insert_timeline');

const usePostCommentMock = usePostComment as jest.Mock;
// TODO: Timeline Integration
// const useInsertTimelineMock = useInsertTimeline as jest.Mock;
const onCommentSaving = jest.fn();
const onCommentPosted = jest.fn();
const postComment = jest.fn();
Expand Down Expand Up @@ -150,27 +146,32 @@ describe('AddComment ', () => {
);
});

// TODO: Should re-enable the insert timeline action
// xit('it should insert a timeline', async () => {
// let attachTimeline = noop;
// useInsertTimelineMock.mockImplementation((comment, onTimelineAttached) => {
// attachTimeline = onTimelineAttached;
// });

// const wrapper = mount(
// <TestProviders>
// <Router history={mockHistory}>
// <AddComment {...{ ...addCommentProps }} />
// </Router>
// </TestProviders>
// );

// act(() => {
// attachTimeline('[title](url)');
// });

// await waitFor(() => {
// expect(wrapper.find(`[data-test-subj="add-comment"] textarea`).text()).toBe('[title](url)');
// });
// });
it('it should insert a timeline', async () => {
const useInsertTimelineMock = jest.fn();
let attachTimeline = noop;
useInsertTimelineMock.mockImplementation((comment, onTimelineAttached) => {
attachTimeline = onTimelineAttached;
});

const mockTimelineIntegration = { ...timelineIntegrationMock };
mockTimelineIntegration.hooks.useInsertTimeline = useInsertTimelineMock;

const wrapper = mount(
<TestProviders>
<CasesTimelineIntegrationProvider timelineIntegration={mockTimelineIntegration}>
<Router history={mockHistory}>
<AddComment {...{ ...addCommentProps }} />
</Router>
</CasesTimelineIntegrationProvider>
</TestProviders>
);

act(() => {
attachTimeline('[title](url)');
});

await waitFor(() => {
expect(wrapper.find(`[data-test-subj="add-comment"] textarea`).text()).toBe('[title](url)');
});
});
});
14 changes: 2 additions & 12 deletions x-pack/plugins/cases/public/components/add_comment/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@ import { Form, useForm, UseField, useFormData } from '../../common/shared_import

import * as i18n from './translations';
import { schema, AddCommentFormSchema } from './schema';

// TODO: Handle use insert in timeline
// import { useInsertTimeline } from '../use_insert_timeline';

import { InsertTimeline } from '../insert_timeline';
const MySpinner = styled(EuiLoadingSpinner)`
position: absolute;
top: 50%;
Expand Down Expand Up @@ -73,14 +70,6 @@ export const AddComment = React.memo(
addQuote,
}));

// TODO: Timeline integration
// const onTimelineAttached = useCallback(
// (newValue: string) => setFieldValue(fieldName, newValue),
// [setFieldValue]
// );

// useInsertTimeline(comment ?? '', onTimelineAttached);

const onSubmit = useCallback(async () => {
const { isValid, data } = await submit();
if (isValid) {
Expand Down Expand Up @@ -123,6 +112,7 @@ export const AddComment = React.memo(
),
}}
/>
<InsertTimeline fieldName="comment" />
</Form>
</span>
);
Expand Down
57 changes: 29 additions & 28 deletions x-pack/plugins/cases/public/components/case_view/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,28 +40,31 @@ import {
import { StatusActionButton } from '../status/button';
import * as i18n from './translations';
import { Ecs } from '../../common/ecs_types';
import { CasesTimelineIntegration, CasesTimelineIntegrationProvider } from '../timeline_context';
import { useTimelineContext } from '../timeline_context/use_timeline_context';
import { CasesNavigation } from '../links';

// TODO: All below imports depend on Timeline or SecuritySolution in some form or another
// import { SpyRoute } from '../../../common/utils/route/spy_routes';

const gutterTimeline = '70px'; // seems to be a timeline reference from the original file
export interface CaseViewProps {
export interface CaseViewComponentProps {
allCasesNavigation: CasesNavigation;
caseDetailsNavigation: CasesNavigation;
caseId: string;
configureCasesNavigation: CasesNavigation;
getCaseDetailHrefWithCommentId: (commentId: string) => string;
onComponentInitialized?: () => void;
renderInvestigateInTimelineActionComponent?: (alertIds: string[]) => JSX.Element;
renderTimelineDetailsPanel?: () => JSX.Element;
ruleDetailsNavigation: CasesNavigation<string | null | undefined, 'configurable'>;
showAlertDetails: (alertId: string, index: string) => void;
subCaseId?: string;
useFetchAlertData: (alertIds: string[]) => [boolean, Record<string, Ecs>];
userCanCrud: boolean;
}

export interface CaseViewProps extends CaseViewComponentProps {
timelineIntegration?: CasesTimelineIntegration;
}
export interface OnUpdateFields {
key: keyof Case;
value: Case[keyof Case];
Expand All @@ -85,7 +88,7 @@ const MyEuiHorizontalRule = styled(EuiHorizontalRule)`
}
`;

export interface CaseComponentProps extends CaseViewProps {
export interface CaseComponentProps extends CaseViewComponentProps {
fetchCase: () => void;
caseData: Case;
updateCase: (newCase: Case) => void;
Expand All @@ -101,8 +104,6 @@ export const CaseComponent = React.memo<CaseComponentProps>(
getCaseDetailHrefWithCommentId,
fetchCase,
onComponentInitialized,
renderInvestigateInTimelineActionComponent,
renderTimelineDetailsPanel,
ruleDetailsNavigation,
showAlertDetails,
subCaseId,
Expand All @@ -112,6 +113,7 @@ export const CaseComponent = React.memo<CaseComponentProps>(
}) => {
const [initLoadingData, setInitLoadingData] = useState(true);
const init = useRef(true);
const timelineUi = useTimelineContext()?.ui;

const {
caseUserActions,
Expand Down Expand Up @@ -399,7 +401,7 @@ export const CaseComponent = React.memo<CaseComponentProps>(
onShowAlertDetails={onShowAlertDetails}
onUpdateField={onUpdateField}
renderInvestigateInTimelineActionComponent={
renderInvestigateInTimelineActionComponent
timelineUi?.renderInvestigateInTimelineActionComponent
}
updateCase={updateCase}
useFetchAlertData={useFetchAlertData}
Expand Down Expand Up @@ -470,7 +472,7 @@ export const CaseComponent = React.memo<CaseComponentProps>(
</EuiFlexGroup>
</MyWrapper>
</WhitePageWrapper>
{renderTimelineDetailsPanel ? renderTimelineDetailsPanel() : null}
{timelineUi?.renderTimelineDetailsPanel ? timelineUi.renderTimelineDetailsPanel() : null}
{/* TODO: Determine spyroute changes */}
{/* <SpyRoute state={spyState} pageName={SecurityPageName.case} /> */}
</>
Expand All @@ -486,11 +488,10 @@ export const CaseView = React.memo(
configureCasesNavigation,
getCaseDetailHrefWithCommentId,
onComponentInitialized,
renderInvestigateInTimelineActionComponent,
renderTimelineDetailsPanel,
ruleDetailsNavigation,
showAlertDetails,
subCaseId,
timelineIntegration,
useFetchAlertData,
userCanCrud,
}: CaseViewProps) => {
Expand All @@ -510,24 +511,24 @@ export const CaseView = React.memo(

return (
data && (
<CaseComponent
allCasesNavigation={allCasesNavigation}
caseData={data}
caseDetailsNavigation={caseDetailsNavigation}
caseId={caseId}
configureCasesNavigation={configureCasesNavigation}
getCaseDetailHrefWithCommentId={getCaseDetailHrefWithCommentId}
fetchCase={fetchCase}
onComponentInitialized={onComponentInitialized}
renderInvestigateInTimelineActionComponent={renderInvestigateInTimelineActionComponent}
renderTimelineDetailsPanel={renderTimelineDetailsPanel}
ruleDetailsNavigation={ruleDetailsNavigation}
showAlertDetails={showAlertDetails}
subCaseId={subCaseId}
updateCase={updateCase}
useFetchAlertData={useFetchAlertData}
userCanCrud={userCanCrud}
/>
<CasesTimelineIntegrationProvider timelineIntegration={timelineIntegration}>
<CaseComponent
allCasesNavigation={allCasesNavigation}
caseData={data}
caseDetailsNavigation={caseDetailsNavigation}
caseId={caseId}
configureCasesNavigation={configureCasesNavigation}
getCaseDetailHrefWithCommentId={getCaseDetailHrefWithCommentId}
fetchCase={fetchCase}
onComponentInitialized={onComponentInitialized}
ruleDetailsNavigation={ruleDetailsNavigation}
showAlertDetails={showAlertDetails}
subCaseId={subCaseId}
updateCase={updateCase}
useFetchAlertData={useFetchAlertData}
userCanCrud={userCanCrud}
/>
</CasesTimelineIntegrationProvider>
)
);
}
Expand Down
Loading

0 comments on commit 636524a

Please sign in to comment.