[Cases] Refactor timeline and cases add alert to new case. Move postComment inside cases (#124831)

This commit is contained in:
Esteban Beltran 2022-02-10 10:26:22 +01:00 committed by GitHub
parent 4d2bd607eb
commit 05f187aae2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 263 additions and 170 deletions

View file

@ -6,11 +6,11 @@
*/
import React from 'react';
import { act, render } from '@testing-library/react';
import { act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { CreateCaseFlyout } from './create_case_flyout';
import { TestProviders } from '../../../common/mock';
import { AppMockRenderer, createAppMockRenderer } from '../../../common/mock';
jest.mock('../../../common/lib/kibana');
@ -23,27 +23,21 @@ const defaultProps = {
};
describe('CreateCaseFlyout', () => {
let mockedContext: AppMockRenderer;
beforeEach(() => {
mockedContext = createAppMockRenderer();
jest.clearAllMocks();
});
it('renders', async () => {
const { getByTestId } = render(
<TestProviders>
<CreateCaseFlyout {...defaultProps} />
</TestProviders>
);
const { getByTestId } = mockedContext.render(<CreateCaseFlyout {...defaultProps} />);
await act(async () => {
expect(getByTestId('create-case-flyout')).toBeTruthy();
});
});
it('Closing flyout calls onCloseCaseModal', async () => {
const { getByTestId } = render(
<TestProviders>
<CreateCaseFlyout {...defaultProps} />
</TestProviders>
);
it('should call onCloseCaseModal when closing the flyout', async () => {
const { getByTestId } = mockedContext.render(<CreateCaseFlyout {...defaultProps} />);
await act(async () => {
userEvent.click(getByTestId('euiFlyoutCloseButton'));
});

View file

@ -11,12 +11,14 @@ import { EuiFlyout, EuiFlyoutHeader, EuiTitle, EuiFlyoutBody } from '@elastic/eu
import * as i18n from '../translations';
import { Case } from '../../../../common/ui/types';
import { CreateCaseForm } from '../form';
import { CreateCaseForm, CreateCaseAttachment } from '../form';
import { UsePostComment } from '../../../containers/use_post_comment';
export interface CreateCaseFlyoutProps {
afterCaseCreated?: (theCase: Case) => Promise<void>;
afterCaseCreated?: (theCase: Case, postComment: UsePostComment['postComment']) => Promise<void>;
onClose: () => void;
onSuccess: (theCase: Case) => Promise<void>;
attachments?: CreateCaseAttachment;
}
const StyledFlyout = styled(EuiFlyout)`
@ -63,33 +65,36 @@ const FormWrapper = styled.div`
`;
export const CreateCaseFlyout = React.memo<CreateCaseFlyoutProps>(
({ afterCaseCreated, onClose, onSuccess }) => (
<>
<GlobalStyle />
<StyledFlyout
onClose={onClose}
data-test-subj="create-case-flyout"
// maskProps is needed in order to apply the z-index to the parent overlay element, not to the flyout only
maskProps={{ className: maskOverlayClassName }}
>
<EuiFlyoutHeader hasBorder>
<EuiTitle size="m">
<h2>{i18n.CREATE_CASE_TITLE}</h2>
</EuiTitle>
</EuiFlyoutHeader>
<StyledEuiFlyoutBody>
<FormWrapper>
<CreateCaseForm
afterCaseCreated={afterCaseCreated}
onCancel={onClose}
onSuccess={onSuccess}
withSteps={false}
/>
</FormWrapper>
</StyledEuiFlyoutBody>
</StyledFlyout>
</>
)
({ afterCaseCreated, onClose, onSuccess, attachments }) => {
return (
<>
<GlobalStyle />
<StyledFlyout
onClose={onClose}
data-test-subj="create-case-flyout"
// maskProps is needed in order to apply the z-index to the parent overlay element, not to the flyout only
maskProps={{ className: maskOverlayClassName }}
>
<EuiFlyoutHeader hasBorder>
<EuiTitle size="m">
<h2>{i18n.CREATE_CASE_TITLE}</h2>
</EuiTitle>
</EuiFlyoutHeader>
<StyledEuiFlyoutBody>
<FormWrapper>
<CreateCaseForm
afterCaseCreated={afterCaseCreated}
attachments={attachments}
onCancel={onClose}
onSuccess={onSuccess}
withSteps={false}
/>
</FormWrapper>
</StyledEuiFlyoutBody>
</StyledFlyout>
</>
);
}
);
CreateCaseFlyout.displayName = 'CreateCaseFlyout';

View file

@ -74,7 +74,7 @@ describe('CreateCaseForm', () => {
useCaseConfigureMock.mockImplementation(() => useCaseConfigureResponse);
});
it('it renders with steps', async () => {
it('renders with steps', async () => {
const wrapper = mount(
<MockHookWrapperComponent>
<CreateCaseForm {...casesFormProps} />
@ -84,7 +84,7 @@ describe('CreateCaseForm', () => {
expect(wrapper.find(`[data-test-subj="case-creation-form-steps"]`).exists()).toBeTruthy();
});
it('it renders without steps', async () => {
it('renders without steps', async () => {
const wrapper = mount(
<MockHookWrapperComponent>
<CreateCaseForm {...casesFormProps} withSteps={false} />
@ -94,7 +94,7 @@ describe('CreateCaseForm', () => {
expect(wrapper.find(`[data-test-subj="case-creation-form-steps"]`).exists()).toBeFalsy();
});
it('it renders all form fields except case selection', async () => {
it('renders all form fields except case selection', async () => {
const wrapper = mount(
<MockHookWrapperComponent>
<CreateCaseForm {...casesFormProps} />

View file

@ -23,7 +23,11 @@ import { Tags } from './tags';
import { Connector } from './connector';
import * as i18n from './translations';
import { SyncAlertsToggle } from './sync_alerts_toggle';
import { ActionConnector } from '../../../common/api';
import {
ActionConnector,
CommentRequestUserType,
CommentRequestAlertType,
} from '../../../common/api';
import { Case } from '../../containers/types';
import { CasesTimelineIntegration, CasesTimelineIntegrationProvider } from '../timeline_context';
import { InsertTimeline } from '../insert_timeline';
@ -51,6 +55,8 @@ const MySpinner = styled(EuiLoadingSpinner)`
left: 50%;
z-index: 99;
`;
export type SupportedCreateCaseAttachment = CommentRequestAlertType | CommentRequestUserType;
export type CreateCaseAttachment = SupportedCreateCaseAttachment[];
export interface CreateCaseFormFieldsProps {
connectors: ActionConnector[];
@ -62,6 +68,7 @@ export interface CreateCaseFormProps extends Pick<Partial<CreateCaseFormFieldsPr
onSuccess: (theCase: Case) => Promise<void>;
afterCaseCreated?: (theCase: Case, postComment: UsePostComment['postComment']) => Promise<void>;
timelineIntegration?: CasesTimelineIntegration;
attachments?: CreateCaseAttachment;
}
const empty: ActionConnector[] = [];
@ -157,9 +164,20 @@ export const CreateCaseFormFields: React.FC<CreateCaseFormFieldsProps> = React.m
CreateCaseFormFields.displayName = 'CreateCaseFormFields';
export const CreateCaseForm: React.FC<CreateCaseFormProps> = React.memo(
({ withSteps = true, afterCaseCreated, onCancel, onSuccess, timelineIntegration }) => (
({
withSteps = true,
afterCaseCreated,
onCancel,
onSuccess,
timelineIntegration,
attachments,
}) => (
<CasesTimelineIntegrationProvider timelineIntegration={timelineIntegration}>
<FormContext afterCaseCreated={afterCaseCreated} onSuccess={onSuccess}>
<FormContext
afterCaseCreated={afterCaseCreated}
onSuccess={onSuccess}
attachments={attachments}
>
<CreateCaseFormFields
connectors={empty}
isLoadingConnectors={false}

View file

@ -7,12 +7,12 @@
import React from 'react';
import { mount, ReactWrapper } from 'enzyme';
import { act, waitFor } from '@testing-library/react';
import { act, RenderResult, waitFor, within } from '@testing-library/react';
import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';
import { ConnectorTypes } from '../../../common/api';
import { CommentType, ConnectorTypes } from '../../../common/api';
import { useKibana } from '../../common/lib/kibana';
import { TestProviders } from '../../common/mock';
import { AppMockRenderer, createAppMockRenderer, TestProviders } from '../../common/mock';
import { usePostCase } from '../../containers/use_post_case';
import { usePostComment } from '../../containers/use_post_comment';
import { useGetTags } from '../../containers/use_get_tags';
@ -40,6 +40,7 @@ import { CreateCaseFormFields, CreateCaseFormFieldsProps } from './form';
import { SubmitCaseButton } from './submit_button';
import { usePostPushToService } from '../../containers/use_post_push_to_service';
import { Choice } from '../connectors/servicenow/types';
import userEvent from '@testing-library/user-event';
const sampleId = 'case-id';
@ -110,12 +111,30 @@ const fillForm = (wrapper: ReactWrapper) => {
});
};
const fillFormReactTestingLib = async (renderResult: RenderResult) => {
const titleInput = within(renderResult.getByTestId('caseTitle')).getByTestId('input');
userEvent.type(titleInput, sampleData.title);
const descriptionInput = renderResult.container.querySelector(
`[data-test-subj="caseDescription"] textarea`
);
if (descriptionInput) {
userEvent.type(descriptionInput, sampleData.description);
}
const caseTags = renderResult.getByTestId('caseTags');
for (let i = 0; i < sampleTags.length; i++) {
const tagsInput = await within(caseTags).findByTestId('comboBoxInput');
userEvent.type(tagsInput, `${sampleTags[i]}{enter}`);
}
};
describe('Create case', () => {
const fetchTags = jest.fn();
const onFormSubmitSuccess = jest.fn();
const afterCaseCreated = jest.fn();
const postComment = jest.fn();
let onChoicesSuccess: (values: Choice[]) => void;
let mockedContext: AppMockRenderer;
beforeAll(() => {
postCase.mockResolvedValue({
@ -149,29 +168,23 @@ describe('Create case', () => {
});
beforeEach(() => {
mockedContext = createAppMockRenderer();
jest.clearAllMocks();
});
describe('Step 1 - Case Fields', () => {
it('it renders', async () => {
const wrapper = mount(
<TestProviders>
<FormContext onSuccess={onFormSubmitSuccess}>
<CreateCaseFormFields {...defaultCreateCaseForm} />
<SubmitCaseButton />
</FormContext>
</TestProviders>
it('renders correctly', async () => {
const renderResult = mockedContext.render(
<FormContext onSuccess={onFormSubmitSuccess}>
<CreateCaseFormFields {...defaultCreateCaseForm} />
<SubmitCaseButton />
</FormContext>
);
await act(async () => {
wrapper.update();
});
expect(wrapper.find(`[data-test-subj="caseTitle"]`).first().exists()).toBeTruthy();
expect(wrapper.find(`[data-test-subj="caseDescription"]`).first().exists()).toBeTruthy();
expect(wrapper.find(`[data-test-subj="caseTags"]`).first().exists()).toBeTruthy();
expect(wrapper.find(`[data-test-subj="caseConnectors"]`).first().exists()).toBeTruthy();
expect(
wrapper.find(`[data-test-subj="case-creation-form-steps"]`).first().exists()
).toBeTruthy();
expect(renderResult.getByTestId('caseTitle')).toBeTruthy();
expect(renderResult.getByTestId('caseDescription')).toBeTruthy();
expect(renderResult.getByTestId('caseTags')).toBeTruthy();
expect(renderResult.getByTestId('caseConnectors')).toBeTruthy();
expect(renderResult.getByTestId('case-creation-form-steps')).toBeTruthy();
});
it('should post case on submit click', async () => {
@ -180,21 +193,21 @@ describe('Create case', () => {
connectors: connectorsMock,
});
const wrapper = mount(
<TestProviders>
<FormContext onSuccess={onFormSubmitSuccess}>
<CreateCaseFormFields {...defaultCreateCaseForm} />
<SubmitCaseButton />
</FormContext>
</TestProviders>
const renderResult = mockedContext.render(
<FormContext onSuccess={onFormSubmitSuccess}>
<CreateCaseFormFields {...defaultCreateCaseForm} />
<SubmitCaseButton />
</FormContext>
);
fillForm(wrapper);
wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click');
await waitFor(() => expect(postCase).toBeCalledWith(sampleData));
await fillFormReactTestingLib(renderResult);
userEvent.click(renderResult.getByTestId('create-case-submit'));
await waitFor(() => {
expect(postCase).toBeCalledWith(sampleData);
});
});
it('it does not submits the title when the length is longer than 64 characters', async () => {
it('does not submits the title when the length is longer than 64 characters', async () => {
const longTitle =
'This is a title that should not be saved as it is longer than 64 characters.';
@ -271,7 +284,7 @@ describe('Create case', () => {
);
});
it('it should select the default connector set in the configuration', async () => {
it('should select the default connector set in the configuration', async () => {
useCaseConfigureMock.mockImplementation(() => ({
...useCaseConfigureResponse,
connector: {
@ -321,7 +334,7 @@ describe('Create case', () => {
);
});
it('it should default to none if the default connector does not exist in connectors', async () => {
it('should default to none if the default connector does not exist in connectors', async () => {
useCaseConfigureMock.mockImplementation(() => ({
...useCaseConfigureResponse,
connector: {
@ -357,7 +370,7 @@ describe('Create case', () => {
});
describe('Step 2 - Connector Fields', () => {
it(`it should submit and push to Jira connector`, async () => {
it(`should submit and push to Jira connector`, async () => {
useConnectorsMock.mockReturnValue({
...sampleConnectorData,
connectors: connectorsMock,
@ -424,7 +437,7 @@ describe('Create case', () => {
});
});
it(`it should submit and push to resilient connector`, async () => {
it(`should submit and push to resilient connector`, async () => {
useConnectorsMock.mockReturnValue({
...sampleConnectorData,
connectors: connectorsMock,
@ -494,7 +507,7 @@ describe('Create case', () => {
});
});
it(`it should submit and push to servicenow itsm connector`, async () => {
it(`should submit and push to servicenow itsm connector`, async () => {
useConnectorsMock.mockReturnValue({
...sampleConnectorData,
connectors: connectorsMock,
@ -589,7 +602,7 @@ describe('Create case', () => {
});
});
it(`it should submit and push to servicenow sir connector`, async () => {
it(`should submit and push to servicenow sir connector`, async () => {
useConnectorsMock.mockReturnValue({
...sampleConnectorData,
connectors: connectorsMock,
@ -692,32 +705,28 @@ describe('Create case', () => {
});
});
it(`it should call afterCaseCreated`, async () => {
it(`should call afterCaseCreated`, async () => {
useConnectorsMock.mockReturnValue({
...sampleConnectorData,
connectors: connectorsMock,
});
const wrapper = mount(
<TestProviders>
<FormContext onSuccess={onFormSubmitSuccess} afterCaseCreated={afterCaseCreated}>
<CreateCaseFormFields {...defaultCreateCaseForm} />
<SubmitCaseButton />
</FormContext>
</TestProviders>
const wrapper = mockedContext.render(
<FormContext onSuccess={onFormSubmitSuccess} afterCaseCreated={afterCaseCreated}>
<CreateCaseFormFields {...defaultCreateCaseForm} />
<SubmitCaseButton />
</FormContext>
);
fillForm(wrapper);
expect(wrapper.find(`[data-test-subj="connector-fields-jira"]`).exists()).toBeFalsy();
wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click');
wrapper.find(`button[data-test-subj="dropdown-connector-jira-1"]`).simulate('click');
await waitFor(() => {
wrapper.update();
expect(wrapper.find(`[data-test-subj="connector-fields-jira"]`).exists()).toBeTruthy();
await fillFormReactTestingLib(wrapper);
expect(wrapper.queryByTestId('connector-fields-jira')).toBeFalsy();
userEvent.click(wrapper.getByTestId('dropdown-connectors'));
await act(async () => {
userEvent.click(wrapper.getByTestId('dropdown-connector-jira-1'));
});
expect(wrapper.getByTestId('connector-fields-jira')).toBeTruthy();
wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click');
userEvent.click(wrapper.getByTestId('create-case-submit'));
await waitFor(() => {
expect(afterCaseCreated).toHaveBeenCalledWith(
{
@ -729,15 +738,75 @@ describe('Create case', () => {
});
});
it(`it should call callbacks in correct order`, async () => {
it('should call `postComment` with the attachments after the case is created', async () => {
useConnectorsMock.mockReturnValue({
...sampleConnectorData,
connectors: connectorsMock,
});
const attachments = [
{
alertId: '1234',
index: '',
rule: {
id: '45321',
name: 'my rule',
},
owner: 'owner',
type: CommentType.alert as const,
},
{
alertId: '7896',
index: '',
rule: {
id: '445324',
name: 'my rule',
},
owner: 'second-owner',
type: CommentType.alert as const,
},
];
const wrapper = mockedContext.render(
<FormContext onSuccess={onFormSubmitSuccess} attachments={attachments}>
<CreateCaseFormFields {...defaultCreateCaseForm} />
<SubmitCaseButton />
</FormContext>
);
await fillFormReactTestingLib(wrapper);
await act(async () => {
userEvent.click(wrapper.getByTestId('create-case-submit'));
});
expect(postComment).toHaveBeenCalledWith({ caseId: 'case-id', data: attachments[0] });
expect(postComment).toHaveBeenCalledWith({ caseId: 'case-id', data: attachments[1] });
});
it(`should call callbacks in correct order`, async () => {
useConnectorsMock.mockReturnValue({
...sampleConnectorData,
connectors: connectorsMock,
});
const attachments = [
{
alertId: '1234',
index: '',
rule: {
id: '45321',
name: 'my rule',
},
owner: 'owner',
type: CommentType.alert as const,
},
];
const wrapper = mount(
<TestProviders>
<FormContext onSuccess={onFormSubmitSuccess} afterCaseCreated={afterCaseCreated}>
<FormContext
onSuccess={onFormSubmitSuccess}
afterCaseCreated={afterCaseCreated}
attachments={attachments}
>
<CreateCaseFormFields {...defaultCreateCaseForm} />
<SubmitCaseButton />
</FormContext>
@ -757,26 +826,23 @@ describe('Create case', () => {
wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click');
await waitFor(() => {
expect(postCase).toHaveBeenCalled();
expect(postComment).toHaveBeenCalled();
expect(afterCaseCreated).toHaveBeenCalled();
expect(pushCaseToExternalService).toHaveBeenCalled();
expect(onFormSubmitSuccess).toHaveBeenCalled();
});
const postCaseOrder = postCase.mock.invocationCallOrder[0];
const postCommentOrder = postComment.mock.invocationCallOrder[0];
const afterCaseOrder = afterCaseCreated.mock.invocationCallOrder[0];
const pushCaseToExternalServiceOrder = pushCaseToExternalService.mock.invocationCallOrder[0];
const onFormSubmitSuccessOrder = onFormSubmitSuccess.mock.invocationCallOrder[0];
expect(
postCaseOrder < afterCaseOrder &&
postCaseOrder < pushCaseToExternalServiceOrder &&
postCaseOrder < onFormSubmitSuccessOrder
postCaseOrder < postCommentOrder &&
postCommentOrder < afterCaseOrder &&
afterCaseOrder < pushCaseToExternalServiceOrder &&
pushCaseToExternalServiceOrder < onFormSubmitSuccessOrder
).toBe(true);
expect(
afterCaseOrder < pushCaseToExternalServiceOrder && afterCaseOrder < onFormSubmitSuccessOrder
).toBe(true);
expect(pushCaseToExternalServiceOrder < onFormSubmitSuccessOrder).toBe(true);
});
});

View file

@ -19,6 +19,7 @@ import { UsePostComment, usePostComment } from '../../containers/use_post_commen
import { useCasesContext } from '../cases_context/use_cases_context';
import { useCasesFeatures } from '../cases_context/use_cases_features';
import { getConnectorById } from '../utils';
import { CreateCaseAttachment } from './form';
const initialCaseValue: FormProps = {
description: '',
@ -34,9 +35,15 @@ interface Props {
afterCaseCreated?: (theCase: Case, postComment: UsePostComment['postComment']) => Promise<void>;
children?: JSX.Element | JSX.Element[];
onSuccess?: (theCase: Case) => Promise<void>;
attachments?: CreateCaseAttachment;
}
export const FormContext: React.FC<Props> = ({ afterCaseCreated, children, onSuccess }) => {
export const FormContext: React.FC<Props> = ({
afterCaseCreated,
children,
onSuccess,
attachments,
}) => {
const { connectors, loading: isLoadingConnectors } = useConnectors();
const { owner } = useCasesContext();
const { isSyncAlertsEnabled } = useCasesFeatures();
@ -69,6 +76,19 @@ export const FormContext: React.FC<Props> = ({ afterCaseCreated, children, onSuc
owner: selectedOwner ?? owner[0],
});
// add attachments to the case
if (updatedCase && Array.isArray(attachments)) {
// TODO currently the API only supports to add a comment at the time
// once the API is updated we should use bulk post comment #124814
// this operation is intentionally made in sequence
for (const attachment of attachments) {
await postComment({
caseId: updatedCase.id,
data: attachment,
});
}
}
if (afterCaseCreated && updatedCase) {
await afterCaseCreated(updatedCase, postComment);
}
@ -92,6 +112,7 @@ export const FormContext: React.FC<Props> = ({ afterCaseCreated, children, onSuc
owner,
afterCaseCreated,
onSuccess,
attachments,
postComment,
pushCaseToExternalService,
]

View file

@ -22,6 +22,7 @@ export const getCreateCaseFlyoutLazy = ({
afterCaseCreated,
onClose,
onSuccess,
attachments,
}: GetCreateCaseFlyoutProps) => (
<CasesProvider value={{ owner, userCanCrud, features }}>
<Suspense fallback={<EuiLoadingSpinner />}>
@ -29,6 +30,7 @@ export const getCreateCaseFlyoutLazy = ({
afterCaseCreated={afterCaseCreated}
onClose={onClose}
onSuccess={onSuccess}
attachments={attachments}
/>
</Suspense>
</CasesProvider>

View file

@ -7,7 +7,16 @@
import React, { memo, useMemo, useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { CaseStatuses, StatusAll, CasesFeatures } from '../../../../../../cases/common';
import {
GetAllCasesSelectorModalProps,
GetCreateCaseFlyoutProps,
} from '../../../../../../cases/public';
import {
CaseStatuses,
StatusAll,
CasesFeatures,
CommentType,
} from '../../../../../../cases/common';
import { TimelineItem } from '../../../../../common/search_strategy';
import { useAddToCase, normalizedEventFields } from '../../../../hooks/use_add_to_case';
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
@ -43,12 +52,12 @@ const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({
const {
onCaseClicked,
onCaseSuccess,
attachAlertToCase,
onCaseCreated,
isAllCaseModalOpen,
isCreateCaseFlyoutOpen,
} = useAddToCase({ event, casePermissions, appId, owner, onClose });
const allCasesSelectorModalProps = useMemo(() => {
const allCasesSelectorModalProps: GetAllCasesSelectorModalProps = useMemo(() => {
const { ruleId, ruleName } = normalizedEventFields(event);
return {
alertData: {
@ -86,23 +95,40 @@ const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({
dispatch(setOpenAddToNewCase({ id: eventId, isOpen: false }));
}, [dispatch, eventId]);
const createCaseFlyoutProps = useMemo(() => {
const createCaseFlyoutProps: GetCreateCaseFlyoutProps = useMemo(() => {
const { ruleId, ruleName } = normalizedEventFields(event);
const attachments = [
{
alertId: eventId,
index: eventIndex ?? '',
rule: {
id: ruleId,
name: ruleName,
},
owner,
type: CommentType.alert as const,
},
];
return {
afterCaseCreated: attachAlertToCase,
afterCaseCreated: onCaseCreated,
onClose: closeCaseFlyoutOpen,
onSuccess: onCaseSuccess,
useInsertTimeline,
owner: [owner],
userCanCrud: casePermissions?.crud ?? false,
features: casesFeatures,
attachments,
};
}, [
attachAlertToCase,
event,
eventId,
eventIndex,
owner,
onCaseCreated,
closeCaseFlyoutOpen,
onCaseSuccess,
useInsertTimeline,
owner,
casePermissions,
casePermissions?.crud,
casesFeatures,
]);
@ -113,6 +139,7 @@ const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({
</>
);
};
AddToCaseActionComponent.displayName = 'AddToCaseAction';
export const AddToCaseAction = memo(AddToCaseActionComponent);

View file

@ -51,6 +51,7 @@ const AddToNewCaseButtonComponent: React.FC<AddToNewCaseButtonProps> = ({
</>
);
};
AddToNewCaseButtonComponent.displayName = 'AddToNewCaseButton';
export const AddToNewCaseButton = memo(AddToNewCaseButtonComponent);

View file

@ -23,11 +23,7 @@ interface UseAddToCase {
addExistingCaseClick: () => void;
onCaseClicked: (theCase?: Case) => void;
onCaseSuccess: (theCase: Case) => Promise<void>;
attachAlertToCase: (
theCase: Case,
postComment?: ((arg: PostCommentArg) => Promise<void>) | undefined,
updateCase?: ((newCase: Case) => void) | undefined
) => Promise<void>;
onCaseCreated: () => Promise<void>;
isAllCaseModalOpen: boolean;
isDisabled: boolean;
userCanCrud: boolean;
@ -38,27 +34,13 @@ interface UseAddToCase {
isCreateCaseFlyoutOpen: boolean;
}
interface PostCommentArg {
caseId: string;
data: {
type: 'alert';
alertId: string | string[];
index: string | string[];
rule: { id: string | null; name: string | null };
owner: string;
};
updateCase?: (newCase: Case) => void;
}
export const useAddToCase = ({
event,
casePermissions,
appId,
owner,
onClose,
}: AddToCaseActionProps): UseAddToCase => {
const eventId = event?.ecs._id ?? '';
const eventIndex = event?.ecs._index ?? '';
const dispatch = useDispatch();
// TODO: use correct value in standalone or integrated.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -116,33 +98,10 @@ export const useAddToCase = ({
[navigateToApp, appId]
);
const attachAlertToCase = useCallback(
async (
theCase: Case,
postComment?: (arg: PostCommentArg) => Promise<void>,
updateCase?: (newCase: Case) => void
) => {
dispatch(tGridActions.setOpenAddToNewCase({ id: eventId, isOpen: false }));
const { ruleId, ruleName } = normalizedEventFields(event);
if (postComment) {
await postComment({
caseId: theCase.id,
data: {
type: 'alert',
alertId: eventId,
index: eventIndex ?? '',
rule: {
id: ruleId,
name: ruleName,
},
owner,
},
updateCase,
});
}
},
[eventId, eventIndex, owner, dispatch, event]
);
const onCaseCreated = useCallback(async () => {
dispatch(tGridActions.setOpenAddToNewCase({ id: eventId, isOpen: false }));
}, [eventId, dispatch]);
const onCaseSuccess = useCallback(
async (theCase: Case) => {
dispatch(tGridActions.setOpenAddToExistingCase({ id: eventId, isOpen: false }));
@ -185,7 +144,7 @@ export const useAddToCase = ({
addExistingCaseClick,
onCaseClicked,
onCaseSuccess,
attachAlertToCase,
onCaseCreated,
isAllCaseModalOpen,
isDisabled,
userCanCrud,