Skip to content

Commit

Permalink
finish the full flow with pushing to service now
Browse files Browse the repository at this point in the history
  • Loading branch information
XavierM committed Mar 22, 2020
1 parent 8c79033 commit af7488a
Show file tree
Hide file tree
Showing 27 changed files with 362 additions and 146 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,18 @@ import { CaseUserActions } from './types';

interface CaseUserActionsState {
caseUserActions: CaseUserActions[];
firstIndexPushToService: number;
hasDataToPush: boolean;
isLoading: boolean;
isError: boolean;
lastIndexPushToService: number;
}

const initialData: CaseUserActionsState = {
caseUserActions: [],
firstIndexPushToService: -1,
lastIndexPushToService: -1,
hasDataToPush: false,
isLoading: true,
isError: false,
};
Expand All @@ -28,6 +34,25 @@ interface UseGetCaseUserActions extends CaseUserActionsState {
fetchCaseUserActions: (caseId: string) => void;
}

const getPushedInfo = (
caseUserActions: CaseUserActions[]
): { firstIndexPushToService: number; lastIndexPushToService: number; hasDataToPush: boolean } => {
const firstIndexPushToService = caseUserActions.findIndex(
cua => cua.action === 'push-to-service'
);
const lastIndexPushToService = caseUserActions
.map(cua => cua.action)
.lastIndexOf('push-to-service');

const hasDataToPush =
lastIndexPushToService === -1 || lastIndexPushToService < caseUserActions.length - 1;
return {
firstIndexPushToService,
lastIndexPushToService,
hasDataToPush,
};
};

export const useGetCaseUserActions = (caseId: string): UseGetCaseUserActions => {
const [caseUserActionsState, setCaseUserActionsState] = useState<CaseUserActionsState>(
initialData
Expand All @@ -48,15 +73,12 @@ export const useGetCaseUserActions = (caseId: string): UseGetCaseUserActions =>
const response = await getCaseUserActions(thisCaseId, abortCtrl.signal);
if (!didCancel) {
// Attention Future developer
// We are removing the first item because it will always the creation of the case
// We are removing the first item because it will always be the creation of the case
// and we do not want it to simplify our life
const caseUserActions = !isEmpty(response) ? response.slice(1) : [];
setCaseUserActionsState({
caseUserActions: !isEmpty(response)
? [
...response.slice(1),
{ ...response[response.length - 1], actionId: 33, action: 'push-to-service' },
]
: [],
caseUserActions,
...getPushedInfo(caseUserActions),
isLoading: false,
isError: false,
});
Expand All @@ -70,6 +92,9 @@ export const useGetCaseUserActions = (caseId: string): UseGetCaseUserActions =>
});
setCaseUserActionsState({
caseUserActions: [],
firstIndexPushToService: -1,
lastIndexPushToService: -1,
hasDataToPush: false,
isLoading: false,
isError: true,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,21 @@ export const data: Case = {
export const dataClosed: Case = {
...caseClosedProps.initialData,
};

export const caseUserActions = [
{
actionField: ['comment'],
action: 'create',
actionAt: '2020-03-20T17:10:09.814Z',
actionBy: {
fullName: 'Steph Milovic',
username: 'smilovic',
email: '[email protected]',
},
newValue: 'Solve this fast!',
oldValue: null,
actionId: '3c4ddcc0-4e99-11ea-9290-35d05cb55c15',
caseId: '9b833a50-6acd-11ea-8fad-af86b1071bd9',
commentId: 'a357c6a0-5435-11ea-b427-fb51a1fcb7b8',
},
];
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,18 @@ import { mount } from 'enzyme';
import routeData from 'react-router';
/* eslint-enable @kbn/eslint/module_migration */
import { CaseComponent } from './';
import { caseProps, caseClosedProps, data, dataClosed } from './__mock__';
import { caseProps, caseClosedProps, data, dataClosed, caseUserActions } from './__mock__';
import { TestProviders } from '../../../../mock';
import { useUpdateCase } from '../../../../containers/case/use_update_case';
import { useGetCaseUserActions } from '../../../../containers/case/use_get_case_user_actions';
import { wait } from '../../../../lib/helpers';
import { usePushToService } from './push_to_service';
jest.mock('../../../../containers/case/use_update_case');
jest.mock('../../../../containers/case/use_get_case_user_actions');
jest.mock('./push_to_service');
const useUpdateCaseMock = useUpdateCase as jest.Mock;
const useGetCaseUserActionsMock = useGetCaseUserActions as jest.Mock;
const usePushToServiceMock = usePushToService as jest.Mock;
type Action = 'PUSH' | 'POP' | 'REPLACE';
const pop: Action = 'POP';
const location = {
Expand Down Expand Up @@ -70,27 +75,37 @@ describe('CaseView ', () => {
};

const defaultUseGetCaseUserActions = {
caseUserActions: [],
caseUserActions,
fetchCaseUserActions,
firstIndexPushToService: -1,
hasDataToPush: false,
isLoading: false,
isError: false,
fetchCaseUserActions,
lastIndexPushToService: -1,
};

const defaultUsePushToServiceMock = {
pushButton: <>{'Hello Button'}</>,
pushCallouts: null,
};

beforeEach(() => {
jest.resetAllMocks();
useUpdateCaseMock.mockImplementation(() => defaultUpdateCaseState);
jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation);
useGetCaseUserActionsMock.mockImplementation(() => defaultUseGetCaseUserActions);
usePushToServiceMock.mockImplementation(() => defaultUsePushToServiceMock);
});

it('should render CaseComponent', () => {
it('should render CaseComponent', async () => {
const wrapper = mount(
<TestProviders>
<Router history={mockHistory}>
<CaseComponent {...caseProps} />
</Router>
</TestProviders>
);
await wait();
expect(
wrapper
.find(`[data-test-subj="case-view-title"]`)
Expand Down Expand Up @@ -130,7 +145,7 @@ describe('CaseView ', () => {
).toEqual(data.description);
});

it('should show closed indicators in header when case is closed', () => {
it('should show closed indicators in header when case is closed', async () => {
useUpdateCaseMock.mockImplementation(() => ({
...defaultUpdateCaseState,
caseData: dataClosed,
Expand All @@ -142,6 +157,7 @@ describe('CaseView ', () => {
</Router>
</TestProviders>
);
await wait();
expect(wrapper.contains(`[data-test-subj="case-view-createdAt"]`)).toBe(false);
expect(
wrapper
Expand All @@ -157,33 +173,35 @@ describe('CaseView ', () => {
).toEqual(dataClosed.status);
});

it('should dispatch update state when button is toggled', () => {
it('should dispatch update state when button is toggled', async () => {
const wrapper = mount(
<TestProviders>
<Router history={mockHistory}>
<CaseComponent {...caseProps} />
</Router>
</TestProviders>
);

await wait();
wrapper
.find('input[data-test-subj="toggle-case-status"]')
.simulate('change', { target: { checked: true } });

expect(updateCaseProperty).toBeCalledWith({
fetchCaseUserActions,
updateKey: 'status',
updateValue: 'closed',
});
});

it('should render comments', () => {
it('should render comments', async () => {
const wrapper = mount(
<TestProviders>
<Router history={mockHistory}>
<CaseComponent {...caseProps} />
</Router>
</TestProviders>
);
await wait();
expect(
wrapper
.find(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import styled from 'styled-components';

import { uniqBy } from 'lodash/fp';
import * as i18n from './translations';
import { Case } from '../../../../containers/case/types';
import { getCaseUrl } from '../../../../components/link_to';
Expand Down Expand Up @@ -64,8 +65,11 @@ export const CaseComponent = React.memo<CaseProps>(({ caseId, initialData }) =>
const [initLoadingData, setInitLoadingData] = useState(true);
const {
caseUserActions,
isLoading: isLoadingUserActions,
fetchCaseUserActions,
firstIndexPushToService,
hasDataToPush,
isLoading: isLoadingUserActions,
lastIndexPushToService,
} = useGetCaseUserActions(caseId);
const { caseData, isLoading, updateKey, updateCase, updateCaseProperty } = useUpdateCase(
caseId,
Expand Down Expand Up @@ -119,14 +123,14 @@ export const CaseComponent = React.memo<CaseProps>(({ caseId, initialData }) =>
},
[fetchCaseUserActions, updateCaseProperty, caseData.status]
);

const handleUpdateCase = useCallback(
(newCase: Case) => {
updateCase(newCase);
fetchCaseUserActions(newCase.id);
},
[updateCase, fetchCaseUserActions]
);

const { pushButton, pushCallouts } = usePushToService({
caseData,
isNew: caseUserActions.filter(cua => cua.action === 'push-to-service').length === 0,
Expand All @@ -135,16 +139,15 @@ export const CaseComponent = React.memo<CaseProps>(({ caseId, initialData }) =>

const onSubmitTags = useCallback(newTags => onUpdateField('tags', newTags), [onUpdateField]);
const onSubmitTitle = useCallback(newTitle => onUpdateField('title', newTitle), [onUpdateField]);
const toggleStatusCase = useCallback(status => onUpdateField('status', status), [onUpdateField]);

const toggleStatusCase = useCallback(
e => onUpdateField('status', e.target.checked ? 'closed' : 'open'),
[onUpdateField]
);
const spyState = useMemo(() => ({ caseTitle: caseData.title }), [caseData.title]);
const hasDataToPush = useMemo(() => {
const indexPushToService = caseUserActions.findIndex(cua => cua.action === 'push-to-service');
if (indexPushToService === -1 || indexPushToService < caseUserActions.length - 1) {
return true;
}
return false;
}, [caseUserActions]);
const participants = useMemo(
() => uniqBy('actionBy.username', caseUserActions).map(cau => cau.actionBy),
[caseUserActions]
);
const caseStatusData = useMemo(
() =>
caseData.status === 'open'
Expand Down Expand Up @@ -175,13 +178,9 @@ export const CaseComponent = React.memo<CaseProps>(({ caseId, initialData }) =>
subject: i18n.EMAIL_SUBJECT(caseData.title),
body: i18n.EMAIL_BODY(caseLink),
}),
[caseData.title]
[caseLink, caseData.title]
);

const onChangeStatus = useCallback(e => toggleStatusCase(e.target.checked ? 'closed' : 'open'), [
toggleStatusCase,
]);

useEffect(() => {
if (initLoadingData && !isLoadingUserActions) {
setInitLoadingData(false);
Expand Down Expand Up @@ -210,7 +209,7 @@ export const CaseComponent = React.memo<CaseProps>(({ caseId, initialData }) =>
caseId={caseData.id}
caseTitle={caseData.title}
isLoading={isLoading && updateKey === 'status'}
toggleStatusCase={onChangeStatus}
toggleStatusCase={toggleStatusCase}
{...caseStatusData}
/>
</HeaderPage>
Expand All @@ -227,8 +226,10 @@ export const CaseComponent = React.memo<CaseProps>(({ caseId, initialData }) =>
caseUserActions={caseUserActions}
data={caseData}
fetchUserActions={fetchCaseUserActions.bind(null, caseData.id)}
firstIndexPushToService={firstIndexPushToService}
isLoadingDescription={isLoading && updateKey === 'description'}
isLoadingUserActions={isLoadingUserActions}
lastIndexPushToService={lastIndexPushToService}
onUpdateField={onUpdateField}
/>
<MyEuiHorizontalRule margin="s" />
Expand All @@ -240,7 +241,7 @@ export const CaseComponent = React.memo<CaseProps>(({ caseId, initialData }) =>
isSelected={caseStatusData.isSelected}
isLoading={isLoading && updateKey === 'status'}
label={caseStatusData.buttonLabel}
onChange={onChangeStatus}
onChange={toggleStatusCase}
/>
</EuiFlexItem>
{hasDataToPush && <EuiFlexItem grow={false}>{pushButton}</EuiFlexItem>}
Expand All @@ -250,11 +251,17 @@ export const CaseComponent = React.memo<CaseProps>(({ caseId, initialData }) =>
</EuiFlexItem>
<EuiFlexItem grow={2}>
<UserList
data-test-subj="case-view-user-list"
data-test-subj="case-view-user-list-reporter"
email={emailContent}
headline={i18n.REPORTER}
users={[caseData.createdBy]}
/>
<UserList
data-test-subj="case-view-user-list-particpants"
email={emailContent}
headline={i18n.PARTICIPANTS}
users={participants}
/>
<TagList
data-test-subj="case-view-tag-list"
tags={caseData.tags}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export const usePushToService = ({
() => (
<EuiButton
fill
iconType="importAction"
onClick={handlePushToService}
disabled={isLoading || loadingLicense || loadingCaseConfigure || errorsMsg.length > 0}
isLoading={isLoading}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,20 @@ export const REMOVED_FIELD = i18n.translate('xpack.siem.case.caseView.actionLabe
defaultMessage: 'removed',
});

export const PUSHED_NEW_INCIDENT = i18n.translate(
'xpack.siem.case.caseView.actionLabel.pushedNewIncident',
{
defaultMessage: 'pushed as new incident',
}
);

export const UPDATE_INCIDENT = i18n.translate(
'xpack.siem.case.caseView.actionLabel.updateIncident',
{
defaultMessage: 'updated incident',
}
);

export const ADDED_DESCRIPTION = i18n.translate(
'xpack.siem.case.caseView.actionLabel.addDescription',
{
Expand Down
Loading

0 comments on commit af7488a

Please sign in to comment.