Skip to content

Commit

Permalink
[Cases] Only enable save if changes were made to the connector form (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
academo authored Mar 15, 2022
1 parent 5533748 commit f4bd49b
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 16 deletions.
115 changes: 102 additions & 13 deletions x-pack/plugins/cases/public/components/edit_connector/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { TestProviders } from '../../common/mock';
import { connectorsMock } from '../../containers/configure/mock';
import { basicCase, basicPush, caseUserActions } from '../../containers/mock';
import { useKibana } from '../../common/lib/kibana';
import { CaseConnector } from '../../containers/configure/types';
import userEvent from '@testing-library/user-event';

jest.mock('../../common/lib/kibana');
const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>;
Expand All @@ -29,18 +31,20 @@ const caseServices = {
hasDataToPush: true,
},
};
const defaultProps: EditConnectorProps = {
caseData: basicCase,
caseServices,
connectorName: connectorsMock[0].name,
connectors: connectorsMock,
hasDataToPush: true,
isLoading: false,
isValidConnector: true,
onSubmit,
updateCase,
userActions: caseUserActions,
userCanCrud: true,
const getDefaultProps = (): EditConnectorProps => {
return {
caseData: basicCase,
caseServices,
connectorName: connectorsMock[0].name,
connectors: connectorsMock,
hasDataToPush: true,
isLoading: false,
isValidConnector: true,
onSubmit,
updateCase,
userActions: caseUserActions,
userCanCrud: true,
};
};

describe('EditConnector ', () => {
Expand All @@ -53,6 +57,7 @@ describe('EditConnector ', () => {
});

it('Renders servicenow connector from case initially', async () => {
const defaultProps = getDefaultProps();
const serviceNowProps = {
...defaultProps,
caseData: {
Expand All @@ -71,6 +76,7 @@ describe('EditConnector ', () => {
});

it('Renders no connector, and then edit', async () => {
const defaultProps = getDefaultProps();
const wrapper = mount(
<TestProviders>
<EditConnector {...defaultProps} />
Expand All @@ -92,6 +98,7 @@ describe('EditConnector ', () => {
});

it('Edit external service on submit', async () => {
const defaultProps = getDefaultProps();
const wrapper = mount(
<TestProviders>
<EditConnector {...defaultProps} />
Expand All @@ -111,6 +118,7 @@ describe('EditConnector ', () => {
});

it('Revert to initial external service on error', async () => {
const defaultProps = getDefaultProps();
onSubmit.mockImplementation((connector, onSuccess, onError) => {
onError(new Error('An error has occurred'));
});
Expand Down Expand Up @@ -155,11 +163,15 @@ describe('EditConnector ', () => {
});

it('Resets selector on cancel', async () => {
const defaultProps = getDefaultProps();
const props = {
...defaultProps,
caseData: {
...defaultProps.caseData,
connector: { ...defaultProps.caseData.connector, id: 'servicenow-1' },
connector: {
...defaultProps.caseData.connector,
id: 'servicenow-1',
},
},
};

Expand All @@ -185,6 +197,7 @@ describe('EditConnector ', () => {
});

it('Renders loading spinner', async () => {
const defaultProps = getDefaultProps();
const props = { ...defaultProps, isLoading: true };
const wrapper = mount(
<TestProviders>
Expand All @@ -197,6 +210,7 @@ describe('EditConnector ', () => {
});

it('does not allow the connector to be edited when the user does not have write permissions', async () => {
const defaultProps = getDefaultProps();
const props = { ...defaultProps, userCanCrud: false };
const wrapper = mount(
<TestProviders>
Expand All @@ -211,6 +225,7 @@ describe('EditConnector ', () => {
});

it('displays the permissions error message when one is provided', async () => {
const defaultProps = getDefaultProps();
const props = { ...defaultProps, permissionsError: 'error message' };
const wrapper = mount(
<TestProviders>
Expand All @@ -232,6 +247,7 @@ describe('EditConnector ', () => {
});

it('displays the callout message when none is selected', async () => {
const defaultProps = getDefaultProps();
const props = { ...defaultProps, connectors: [] };
const wrapper = mount(
<TestProviders>
Expand All @@ -247,4 +263,77 @@ describe('EditConnector ', () => {
expect(wrapper.find(`[data-test-subj="push-callouts"]`).exists()).toEqual(true);
});
});

it('disables the save button until changes are done ', async () => {
const defaultProps = getDefaultProps();
const serviceNowProps = {
...defaultProps,
caseData: {
...defaultProps.caseData,
connector: {
...defaultProps.caseData.connector,
id: 'servicenow-1',
fields: {
urgency: null,
severity: null,
impact: null,
category: null,
subcategory: null,
},
} as CaseConnector,
},
};
const result = render(
<TestProviders>
<EditConnector {...serviceNowProps} />
</TestProviders>
);

// the save button should be disabled
userEvent.click(result.getByTestId('connector-edit-button'));
expect(result.getByTestId('edit-connectors-submit')).toBeDisabled();

// simulate changing the connector
userEvent.click(result.getByTestId('dropdown-connectors'));
userEvent.click(result.getAllByTestId('dropdown-connector-no-connector')[0]);
expect(result.getByTestId('edit-connectors-submit')).toBeEnabled();

// this strange assertion is required because of existing race conditions inside the EditConnector component
await waitFor(() => {
expect(true).toBeTruthy();
});
});

it('disables the save button when no connector is the default', async () => {
const defaultProps = getDefaultProps();
const noneConnector = {
...defaultProps,
caseData: {
...defaultProps.caseData,
connector: {
id: 'none',
fields: null,
} as CaseConnector,
},
};
const result = render(
<TestProviders>
<EditConnector {...noneConnector} />
</TestProviders>
);

// the save button should be disabled
userEvent.click(result.getByTestId('connector-edit-button'));
expect(result.getByTestId('edit-connectors-submit')).toBeDisabled();

// simulate changing the connector
userEvent.click(result.getByTestId('dropdown-connectors'));
userEvent.click(result.getAllByTestId('dropdown-connector-resilient-2')[0]);
expect(result.getByTestId('edit-connectors-submit')).toBeEnabled();

// this strange assertion is required because of existing race conditions inside the EditConnector component
await waitFor(() => {
expect(true).toBeTruthy();
});
});
});
24 changes: 21 additions & 3 deletions x-pack/plugins/cases/public/components/edit_connector/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React, { useCallback, useEffect, useReducer } from 'react';
import React, { useCallback, useEffect, useReducer, useState } from 'react';
import deepEqual from 'fast-deep-equal';
import {
EuiText,
Expand All @@ -22,7 +21,7 @@ import { isEmpty, noop } from 'lodash/fp';

import { FieldConfig, Form, UseField, useForm } from '../../common/shared_imports';
import { Case } from '../../../common/ui/types';
import { ActionConnector, ConnectorTypeFields } from '../../../common/api';
import { ActionConnector, ConnectorTypeFields, NONE_CONNECTOR_ID } from '../../../common/api';
import { ConnectorSelector } from '../connector_selector/form';
import { ConnectorFieldsForm } from '../connectors/fields_form';
import { CaseUserActions } from '../../containers/types';
Expand Down Expand Up @@ -133,13 +132,31 @@ export const EditConnector = React.memo(
schema,
});

// by default save if disabled
const [enableSave, setEnableSave] = useState(false);

const { setFieldValue, submit } = form;

const [{ currentConnector, fields, editConnector }, dispatch] = useReducer(
editConnectorReducer,
{ ...initialState, fields: caseFields }
);

// only enable the save button if changes were made to the previous selected
// connector or its fields
useEffect(() => {
// null and none are equivalent to `no connector`.
// This makes sure we don't enable the button when the "no connector" option is selected
// by default. e.g. when a case is created without a selector
const isNoConnectorDeafultValue =
currentConnector === null && selectedConnector === NONE_CONNECTOR_ID;
const enable =
(!isNoConnectorDeafultValue && currentConnector?.id !== selectedConnector) ||
!deepEqual(fields, caseFields);

setEnableSave(enable);
}, [caseFields, currentConnector, fields, selectedConnector]);

useEffect(() => {
// Initialize the current connector with the connector information attached to the case if we can find that
// connector in the retrieved connectors from the API call
Expand Down Expand Up @@ -330,6 +347,7 @@ export const EditConnector = React.memo(
<EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}>
<EuiFlexItem grow={false}>
<EuiButton
disabled={!enableSave}
color="success"
data-test-subj="edit-connectors-submit"
fill
Expand Down

0 comments on commit f4bd49b

Please sign in to comment.