Skip to content

Commit

Permalink
[Security Solution][Case] Fix connector's dropdown with conflicting r…
Browse files Browse the repository at this point in the history
…equests (#72037)
  • Loading branch information
cnasikas authored Jul 17, 2020
1 parent 19def17 commit b4f07de
Show file tree
Hide file tree
Showing 13 changed files with 154 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -409,4 +409,32 @@ describe('CaseView ', () => {
wrapper.find('button[data-test-subj="push-to-external-service"]').first().prop('disabled')
).toBeTruthy();
});

it('should revert to the initial connector in case of failure', async () => {
updateCaseProperty.mockImplementation(({ onError }) => {
onError();
});
const wrapper = mount(
<TestProviders>
<Router history={mockHistory}>
<CaseComponent
{...caseProps}
caseData={{ ...caseProps.caseData, connectorId: 'servicenow-1' }}
/>
</Router>
</TestProviders>
);
await wait();
wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click');
wrapper.update();
wrapper.find('button[data-test-subj="dropdown-connector-servicenow-2"]').simulate('click');
wrapper.update();
wrapper.find(`[data-test-subj="edit-connectors-submit"]`).last().simulate('click');
wrapper.update();
await wait();
wrapper.update();
expect(
wrapper.find('[data-test-subj="dropdown-connectors"]').at(0).prop('valueOfSelected')
).toBe('servicenow-1');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ interface Props {
userCanCrud: boolean;
}

export interface OnUpdateFields {
key: keyof Case;
value: Case[keyof Case];
onSuccess?: () => void;
onError?: () => void;
}

const MyWrapper = styled.div`
padding: ${({
theme,
Expand Down Expand Up @@ -88,65 +95,75 @@ export const CaseComponent = React.memo<CaseProps>(

// Update Fields
const onUpdateField = useCallback(
(newUpdateKey: keyof Case, updateValue: Case[keyof Case]) => {
({ key, value, onSuccess, onError }: OnUpdateFields) => {
const handleUpdateNewCase = (newCase: Case) =>
updateCase({ ...newCase, comments: caseData.comments });
switch (newUpdateKey) {
switch (key) {
case 'title':
const titleUpdate = getTypedPayload<string>(updateValue);
const titleUpdate = getTypedPayload<string>(value);
if (titleUpdate.length > 0) {
updateCaseProperty({
fetchCaseUserActions,
updateKey: 'title',
updateValue: titleUpdate,
updateCase: handleUpdateNewCase,
version: caseData.version,
onSuccess,
onError,
});
}
break;
case 'connectorId':
const connectorId = getTypedPayload<string>(updateValue);
const connectorId = getTypedPayload<string>(value);
if (connectorId.length > 0) {
updateCaseProperty({
fetchCaseUserActions,
updateKey: 'connector_id',
updateValue: connectorId,
updateCase: handleUpdateNewCase,
version: caseData.version,
onSuccess,
onError,
});
}
break;
case 'description':
const descriptionUpdate = getTypedPayload<string>(updateValue);
const descriptionUpdate = getTypedPayload<string>(value);
if (descriptionUpdate.length > 0) {
updateCaseProperty({
fetchCaseUserActions,
updateKey: 'description',
updateValue: descriptionUpdate,
updateCase: handleUpdateNewCase,
version: caseData.version,
onSuccess,
onError,
});
}
break;
case 'tags':
const tagsUpdate = getTypedPayload<string[]>(updateValue);
const tagsUpdate = getTypedPayload<string[]>(value);
updateCaseProperty({
fetchCaseUserActions,
updateKey: 'tags',
updateValue: tagsUpdate,
updateCase: handleUpdateNewCase,
version: caseData.version,
onSuccess,
onError,
});
break;
case 'status':
const statusUpdate = getTypedPayload<string>(updateValue);
if (caseData.status !== updateValue) {
const statusUpdate = getTypedPayload<string>(value);
if (caseData.status !== value) {
updateCaseProperty({
fetchCaseUserActions,
updateKey: 'status',
updateValue: statusUpdate,
updateCase: handleUpdateNewCase,
version: caseData.version,
onSuccess,
onError,
});
}
default:
Expand Down Expand Up @@ -191,15 +208,28 @@ export const CaseComponent = React.memo<CaseProps>(
});

const onSubmitConnector = useCallback(
(connectorId) => onUpdateField('connectorId', connectorId),
(connectorId, onSuccess, onError) =>
onUpdateField({
key: 'connectorId',
value: connectorId,
onSuccess,
onError,
}),
[onUpdateField]
);
const onSubmitTags = useCallback((newTags) => onUpdateField('tags', newTags), [onUpdateField]);
const onSubmitTitle = useCallback((newTitle) => onUpdateField('title', newTitle), [
const onSubmitTags = useCallback((newTags) => onUpdateField({ key: 'tags', value: newTags }), [
onUpdateField,
]);
const onSubmitTitle = useCallback(
(newTitle) => onUpdateField({ key: 'title', value: newTitle }),
[onUpdateField]
);
const toggleStatusCase = useCallback(
(e) => onUpdateField('status', e.target.checked ? 'closed' : 'open'),
(e) =>
onUpdateField({
key: 'status',
value: e.target.checked ? 'closed' : 'open',
}),
[onUpdateField]
);
const handleRefresh = useCallback(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,35 @@ describe('EditConnector ', () => {
await act(async () => {
wrapper.find(`[data-test-subj="edit-connectors-submit"]`).last().simulate('click');
await wait();
expect(onSubmit).toBeCalledWith(sampleConnector);
expect(onSubmit.mock.calls[0][0]).toBe(sampleConnector);
});
});

it('Revert to initial external service on error', async () => {
onSubmit.mockImplementation((connector, onSuccess, onError) => {
onError(new Error('An error has occurred'));
});
const wrapper = mount(
<TestProviders>
<EditConnector {...defaultProps} />
</TestProviders>
);

wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click');
wrapper.update();
wrapper.find('button[data-test-subj="dropdown-connector-servicenow-2"]').simulate('click');
wrapper.update();

expect(wrapper.find(`[data-test-subj="edit-connectors-submit"]`).last().exists()).toBeTruthy();

await act(async () => {
wrapper.find(`[data-test-subj="edit-connectors-submit"]`).last().simulate('click');
await wait();
wrapper.update();
});
expect(formHookMock.setFieldValue).toHaveBeenCalledWith('connector', 'none');
});

it('Resets selector on cancel', async () => {
const props = {
...defaultProps,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {
EuiLoadingSpinner,
} from '@elastic/eui';
import styled, { css } from 'styled-components';
import { noop } from 'lodash/fp';

import * as i18n from '../../translations';
import { Form, UseField, useForm } from '../../../shared_imports';
import { schema } from './schema';
Expand All @@ -25,7 +27,7 @@ interface EditConnectorProps {
connectors: Connector[];
disabled?: boolean;
isLoading: boolean;
onSubmit: (a: string[]) => void;
onSubmit: (a: string[], onSuccess: () => void, onError: () => void) => void;
selectedConnector: string;
}

Expand Down Expand Up @@ -61,6 +63,11 @@ export const EditConnector = React.memo(
[selectedConnector]
);

const onError = useCallback(() => {
setFieldValue('connector', selectedConnector);
setConnectorHasChanged(false);
}, [setFieldValue, selectedConnector]);

const onCancelConnector = useCallback(() => {
setFieldValue('connector', selectedConnector);
setConnectorHasChanged(false);
Expand All @@ -69,10 +76,10 @@ export const EditConnector = React.memo(
const onSubmitConnector = useCallback(async () => {
const { isValid, data: newData } = await submit();
if (isValid && newData.connector) {
onSubmit(newData.connector);
onSubmit(newData.connector, noop, onError);
setConnectorHasChanged(false);
}
}, [submit, onSubmit]);
}, [submit, onSubmit, onError]);

return (
<EuiText>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ describe('UserActionTree ', () => {
)
.exists()
).toEqual(false);
expect(onUpdateField).toBeCalledWith('description', sampleData.content);
expect(onUpdateField).toBeCalledWith({ key: 'description', value: sampleData.content });
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { UserActionMarkdown } from './user_action_markdown';
import { Connector } from '../../../../../case/common/api/cases';
import { CaseServices } from '../../containers/use_get_case_user_actions';
import { parseString } from '../../containers/utils';
import { OnUpdateFields } from '../case_view';

export interface UserActionTreeProps {
caseServices: CaseServices;
Expand All @@ -30,7 +31,7 @@ export interface UserActionTreeProps {
fetchUserActions: () => void;
isLoadingDescription: boolean;
isLoadingUserActions: boolean;
onUpdateField: (updateKey: keyof Case, updateValue: string | string[]) => void;
onUpdateField: ({ key, value, onSuccess, onError }: OnUpdateFields) => void;
updateCase: (newCase: Case) => void;
userCanCrud: boolean;
}
Expand Down Expand Up @@ -138,7 +139,7 @@ export const UserActionTree = React.memo(
content={caseData.description}
isEditable={manageMarkdownEditIds.includes(DESCRIPTION_ID)}
onSaveContent={(content: string) => {
onUpdateField(DESCRIPTION_ID, content);
onUpdateField({ key: DESCRIPTION_ID, value: content });
}}
onChangeEditable={handleManageMarkdownEditId}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,17 @@ describe('useUpdateCase', () => {
const fetchCaseUserActions = jest.fn();
const updateCase = jest.fn();
const updateKey: UpdateKey = 'description';
const onSuccess = jest.fn();
const onError = jest.fn();

const sampleUpdate = {
fetchCaseUserActions,
updateKey,
updateValue: 'updated description',
updateCase,
version: basicCase.version,
onSuccess,
onError,
};
beforeEach(() => {
jest.clearAllMocks();
Expand Down Expand Up @@ -79,6 +84,7 @@ describe('useUpdateCase', () => {
});
expect(fetchCaseUserActions).toBeCalledWith(basicCase.id);
expect(updateCase).toBeCalledWith(basicCase);
expect(onSuccess).toHaveBeenCalled();
});
});

Expand Down Expand Up @@ -114,6 +120,7 @@ describe('useUpdateCase', () => {
isError: true,
updateCaseProperty: result.current.updateCaseProperty,
});
expect(onError).toHaveBeenCalled();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

import { useReducer, useCallback } from 'react';

import {
displaySuccessToast,
errorToToaster,
Expand Down Expand Up @@ -33,6 +34,8 @@ export interface UpdateByKey {
fetchCaseUserActions?: (caseId: string) => void;
updateCase?: (newCase: Case) => void;
version: string;
onSuccess?: () => void;
onError?: () => void;
}

type Action =
Expand Down Expand Up @@ -81,7 +84,15 @@ export const useUpdateCase = ({ caseId }: { caseId: string }): UseUpdateCase =>
const [, dispatchToaster] = useStateToaster();

const dispatchUpdateCaseProperty = useCallback(
async ({ fetchCaseUserActions, updateKey, updateValue, updateCase, version }: UpdateByKey) => {
async ({
fetchCaseUserActions,
updateKey,
updateValue,
updateCase,
version,
onSuccess,
onError,
}: UpdateByKey) => {
let cancel = false;
const abortCtrl = new AbortController();

Expand All @@ -102,6 +113,9 @@ export const useUpdateCase = ({ caseId }: { caseId: string }): UseUpdateCase =>
}
dispatch({ type: 'FETCH_SUCCESS' });
displaySuccessToast(i18n.UPDATED_CASE(response[0].title), dispatchToaster);
if (onSuccess) {
onSuccess();
}
}
} catch (error) {
if (!cancel) {
Expand All @@ -111,6 +125,9 @@ export const useUpdateCase = ({ caseId }: { caseId: string }): UseUpdateCase =>
dispatchToaster,
});
dispatch({ type: 'FETCH_FAILURE' });
if (onError) {
onError();
}
}
}
return () => {
Expand Down
Loading

0 comments on commit b4f07de

Please sign in to comment.