mirror of
https://github.com/ansible/awx.git
synced 2026-05-14 04:47:44 -02:30
Merge pull request #5842 from keithjgrant/4240-form-error-handling
Form error handling Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
@@ -53,8 +53,13 @@ class ErrorDetail extends Component {
|
|||||||
const { error } = this.props;
|
const { error } = this.props;
|
||||||
const { response } = error;
|
const { response } = error;
|
||||||
|
|
||||||
const message =
|
let message = '';
|
||||||
typeof response.data === 'string' ? response.data : response.data.detail;
|
if (response.data) {
|
||||||
|
message =
|
||||||
|
typeof response.data === 'string'
|
||||||
|
? response.data
|
||||||
|
: response.data?.detail;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import styled from 'styled-components';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { ActionGroup as PFActionGroup, Button } from '@patternfly/react-core';
|
import { ActionGroup as PFActionGroup, Button } from '@patternfly/react-core';
|
||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
const ActionGroup = styled(PFActionGroup)`
|
const ActionGroup = styled(PFActionGroup)`
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -11,14 +11,13 @@ const ActionGroup = styled(PFActionGroup)`
|
|||||||
--pf-c-form__group--m-action--MarginTop: 0;
|
--pf-c-form__group--m-action--MarginTop: 0;
|
||||||
|
|
||||||
.pf-c-form__actions {
|
.pf-c-form__actions {
|
||||||
display: grid;
|
|
||||||
gap: 24px;
|
|
||||||
grid-template-columns: auto auto;
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
& > button {
|
& > button {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
& > :not(:first-child) {
|
||||||
|
margin-left: 24px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
|||||||
import FormActionGroup from './FormActionGroup';
|
import FormActionGroup from './FormActionGroup';
|
||||||
|
|
||||||
describe('FormActionGroup', () => {
|
describe('FormActionGroup', () => {
|
||||||
test('renders the expected content', () => {
|
test('should render the expected content', () => {
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<FormActionGroup onSubmit={() => {}} onCancel={() => {}} />
|
<FormActionGroup onSubmit={() => {}} onCancel={() => {}} />
|
||||||
);
|
);
|
||||||
|
|||||||
35
awx/ui_next/src/components/FormField/FormSubmitError.jsx
Normal file
35
awx/ui_next/src/components/FormField/FormSubmitError.jsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useFormikContext } from 'formik';
|
||||||
|
import { Alert } from '@patternfly/react-core';
|
||||||
|
|
||||||
|
function FormSubmitError({ error }) {
|
||||||
|
const [errorMessage, setErrorMessage] = useState(null);
|
||||||
|
const { setErrors } = useFormikContext();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!error) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (error?.response?.data && typeof error.response.data === 'object') {
|
||||||
|
const errorMessages = error.response.data;
|
||||||
|
setErrors(errorMessages);
|
||||||
|
if (errorMessages.__all__) {
|
||||||
|
setErrorMessage(errorMessages.__all__);
|
||||||
|
} else {
|
||||||
|
setErrorMessage(null);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* eslint-disable-next-line no-console */
|
||||||
|
console.error(error);
|
||||||
|
setErrorMessage(error.message);
|
||||||
|
}
|
||||||
|
}, [error, setErrors]);
|
||||||
|
|
||||||
|
if (!errorMessage) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Alert variant="danger" isInline title={errorMessage} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FormSubmitError;
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { act } from 'react-dom/test-utils';
|
||||||
|
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
import FormSubmitError from './FormSubmitError';
|
||||||
|
|
||||||
|
describe('<FormSubmitError>', () => {
|
||||||
|
test('should render null when no error present', () => {
|
||||||
|
const wrapper = mountWithContexts(
|
||||||
|
<Formik>{() => <FormSubmitError error={null} />}</Formik>
|
||||||
|
);
|
||||||
|
expect(wrapper.find('FormSubmitError').text()).toEqual('');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should pass field errors to Formik', () => {
|
||||||
|
const error = {
|
||||||
|
response: {
|
||||||
|
data: {
|
||||||
|
name: 'invalid',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const wrapper = mountWithContexts(
|
||||||
|
<Formik>
|
||||||
|
{({ errors }) => (
|
||||||
|
<div>
|
||||||
|
<p>{errors.name}</p>
|
||||||
|
<FormSubmitError error={error} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
);
|
||||||
|
expect(wrapper.find('p').text()).toEqual('invalid');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should display error message if field errors not provided', async () => {
|
||||||
|
const realConsole = global.console;
|
||||||
|
global.console = {
|
||||||
|
error: jest.fn(),
|
||||||
|
};
|
||||||
|
const error = {
|
||||||
|
message: 'There was an error',
|
||||||
|
};
|
||||||
|
let wrapper;
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<Formik>{() => <FormSubmitError error={error} />}</Formik>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
expect(wrapper.find('Alert').prop('title')).toEqual('There was an error');
|
||||||
|
expect(global.console.error).toHaveBeenCalledWith(error);
|
||||||
|
global.console = realConsole;
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -2,3 +2,4 @@ export { default } from './FormField';
|
|||||||
export { default as CheckboxField } from './CheckboxField';
|
export { default as CheckboxField } from './CheckboxField';
|
||||||
export { default as FieldTooltip } from './FieldTooltip';
|
export { default as FieldTooltip } from './FieldTooltip';
|
||||||
export { default as PasswordField } from './PasswordField';
|
export { default as PasswordField } from './PasswordField';
|
||||||
|
export { default as FormSubmitError } from './FormSubmitError';
|
||||||
|
|||||||
@@ -33,8 +33,11 @@ function HostAdd() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<HostForm handleSubmit={handleSubmit} handleCancel={handleCancel} />
|
<HostForm
|
||||||
{formError ? <div>error</div> : ''}
|
handleSubmit={handleSubmit}
|
||||||
|
handleCancel={handleCancel}
|
||||||
|
submitError={formError}
|
||||||
|
/>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,12 @@ describe('<HostAdd />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('handleSubmit should post to api', async () => {
|
test('handleSubmit should post to api', async () => {
|
||||||
|
HostsAPI.create.mockResolvedValueOnce({
|
||||||
|
data: {
|
||||||
|
...hostData,
|
||||||
|
id: 5,
|
||||||
|
},
|
||||||
|
});
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper.find('HostForm').prop('handleSubmit')(hostData);
|
wrapper.find('HostForm').prop('handleSubmit')(hostData);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ function HostEdit({ host }) {
|
|||||||
host={host}
|
host={host}
|
||||||
handleSubmit={handleSubmit}
|
handleSubmit={handleSubmit}
|
||||||
handleCancel={handleCancel}
|
handleCancel={handleCancel}
|
||||||
|
submitError={formError}
|
||||||
/>
|
/>
|
||||||
{formError ? <div>error</div> : null}
|
|
||||||
</CardBody>
|
</CardBody>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -55,5 +55,4 @@ HostEdit.propTypes = {
|
|||||||
host: PropTypes.shape().isRequired,
|
host: PropTypes.shape().isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export { HostEdit as _HostEdit };
|
|
||||||
export default HostEdit;
|
export default HostEdit;
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ import { t } from '@lingui/macro';
|
|||||||
import { Form } from '@patternfly/react-core';
|
import { Form } from '@patternfly/react-core';
|
||||||
|
|
||||||
import FormRow from '@components/FormRow';
|
import FormRow from '@components/FormRow';
|
||||||
import FormField from '@components/FormField';
|
import FormField, { FormSubmitError } from '@components/FormField';
|
||||||
import FormActionGroup from '@components/FormActionGroup/FormActionGroup';
|
import FormActionGroup from '@components/FormActionGroup/FormActionGroup';
|
||||||
import { VariablesField } from '@components/CodeMirrorInput';
|
import { VariablesField } from '@components/CodeMirrorInput';
|
||||||
import { required } from '@util/validators';
|
import { required } from '@util/validators';
|
||||||
import { InventoryLookup } from '@components/Lookup';
|
import { InventoryLookup } from '@components/Lookup';
|
||||||
|
|
||||||
function HostForm({ handleSubmit, handleCancel, host, i18n }) {
|
function HostForm({ handleSubmit, handleCancel, host, submitError, i18n }) {
|
||||||
const [inventory, setInventory] = useState(
|
const [inventory, setInventory] = useState(
|
||||||
host ? host.summary_fields.inventory : ''
|
host ? host.summary_fields.inventory : ''
|
||||||
);
|
);
|
||||||
@@ -85,6 +85,7 @@ function HostForm({ handleSubmit, handleCancel, host, i18n }) {
|
|||||||
label={i18n._(t`Variables`)}
|
label={i18n._(t`Variables`)}
|
||||||
/>
|
/>
|
||||||
</FormRow>
|
</FormRow>
|
||||||
|
<FormSubmitError error={submitError} />
|
||||||
<FormActionGroup
|
<FormActionGroup
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
onSubmit={formik.handleSubmit}
|
onSubmit={formik.handleSubmit}
|
||||||
@@ -99,6 +100,7 @@ HostForm.propTypes = {
|
|||||||
handleSubmit: func.isRequired,
|
handleSubmit: func.isRequired,
|
||||||
handleCancel: func.isRequired,
|
handleCancel: func.isRequired,
|
||||||
host: shape({}),
|
host: shape({}),
|
||||||
|
submitError: shape({}),
|
||||||
};
|
};
|
||||||
|
|
||||||
HostForm.defaultProps = {
|
HostForm.defaultProps = {
|
||||||
@@ -111,6 +113,7 @@ HostForm.defaultProps = {
|
|||||||
inventory: null,
|
inventory: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
submitError: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export { HostForm as _HostForm };
|
export { HostForm as _HostForm };
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { PageSection, Card } from '@patternfly/react-core';
|
import { PageSection, Card } from '@patternfly/react-core';
|
||||||
import { CardBody } from '@components/Card';
|
import { CardBody } from '@components/Card';
|
||||||
import ContentError from '@components/ContentError';
|
|
||||||
import ContentLoading from '@components/ContentLoading';
|
import ContentLoading from '@components/ContentLoading';
|
||||||
|
|
||||||
import { InventoriesAPI, CredentialTypesAPI } from '@api';
|
import { InventoriesAPI, CredentialTypesAPI } from '@api';
|
||||||
@@ -69,17 +68,6 @@ function InventoryAdd() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return (
|
|
||||||
<PageSection>
|
|
||||||
<Card>
|
|
||||||
<CardBody>
|
|
||||||
<ContentError error={error} />
|
|
||||||
</CardBody>
|
|
||||||
</Card>
|
|
||||||
</PageSection>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <ContentLoading />;
|
return <ContentLoading />;
|
||||||
}
|
}
|
||||||
@@ -91,6 +79,7 @@ function InventoryAdd() {
|
|||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
credentialTypeId={credentialTypeId}
|
credentialTypeId={credentialTypeId}
|
||||||
|
submitError={error}
|
||||||
/>
|
/>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { object } from 'prop-types';
|
|||||||
import { CardBody } from '@components/Card';
|
import { CardBody } from '@components/Card';
|
||||||
import { InventoriesAPI, CredentialTypesAPI } from '@api';
|
import { InventoriesAPI, CredentialTypesAPI } from '@api';
|
||||||
import ContentLoading from '@components/ContentLoading';
|
import ContentLoading from '@components/ContentLoading';
|
||||||
import ContentError from '@components/ContentError';
|
|
||||||
import InventoryForm from '../shared/InventoryForm';
|
import InventoryForm from '../shared/InventoryForm';
|
||||||
import { getAddedAndRemoved } from '../../../util/lists';
|
import { getAddedAndRemoved } from '../../../util/lists';
|
||||||
|
|
||||||
@@ -105,10 +104,6 @@ function InventoryEdit({ inventory }) {
|
|||||||
return <ContentLoading />;
|
return <ContentLoading />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return <ContentError />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<InventoryForm
|
<InventoryForm
|
||||||
@@ -117,6 +112,7 @@ function InventoryEdit({ inventory }) {
|
|||||||
inventory={inventory}
|
inventory={inventory}
|
||||||
instanceGroups={associatedInstanceGroups}
|
instanceGroups={associatedInstanceGroups}
|
||||||
credentialTypeId={credentialTypeId}
|
credentialTypeId={credentialTypeId}
|
||||||
|
submitError={error}
|
||||||
/>
|
/>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { func, number, shape } from 'prop-types';
|
|||||||
|
|
||||||
import { VariablesField } from '@components/CodeMirrorInput';
|
import { VariablesField } from '@components/CodeMirrorInput';
|
||||||
import { Form } from '@patternfly/react-core';
|
import { Form } from '@patternfly/react-core';
|
||||||
import FormField from '@components/FormField';
|
import FormField, { FormSubmitError } from '@components/FormField';
|
||||||
import FormActionGroup from '@components/FormActionGroup/FormActionGroup';
|
import FormActionGroup from '@components/FormActionGroup/FormActionGroup';
|
||||||
import FormRow from '@components/FormRow';
|
import FormRow from '@components/FormRow';
|
||||||
import { required } from '@util/validators';
|
import { required } from '@util/validators';
|
||||||
@@ -21,6 +21,7 @@ function InventoryForm({
|
|||||||
onSubmit,
|
onSubmit,
|
||||||
instanceGroups,
|
instanceGroups,
|
||||||
credentialTypeId,
|
credentialTypeId,
|
||||||
|
submitError,
|
||||||
}) {
|
}) {
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
name: inventory.name || '',
|
name: inventory.name || '',
|
||||||
@@ -129,6 +130,7 @@ function InventoryForm({
|
|||||||
/>
|
/>
|
||||||
</FormRow>
|
</FormRow>
|
||||||
<FormRow>
|
<FormRow>
|
||||||
|
<FormSubmitError error={submitError} />
|
||||||
<FormActionGroup
|
<FormActionGroup
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
onSubmit={formik.handleSubmit}
|
onSubmit={formik.handleSubmit}
|
||||||
@@ -146,11 +148,13 @@ InventoryForm.proptype = {
|
|||||||
instanceGroups: shape(),
|
instanceGroups: shape(),
|
||||||
inventory: shape(),
|
inventory: shape(),
|
||||||
credentialTypeId: number.isRequired,
|
credentialTypeId: number.isRequired,
|
||||||
|
submitError: shape(),
|
||||||
};
|
};
|
||||||
|
|
||||||
InventoryForm.defaultProps = {
|
InventoryForm.defaultProps = {
|
||||||
inventory: {},
|
inventory: {},
|
||||||
instanceGroups: [],
|
instanceGroups: [],
|
||||||
|
submitError: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withI18n()(InventoryForm);
|
export default withI18n()(InventoryForm);
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { func, shape } from 'prop-types';
|
|
||||||
import { Formik } from 'formik';
|
|
||||||
import { withI18n } from '@lingui/react';
|
|
||||||
import { t } from '@lingui/macro';
|
|
||||||
import { Form } from '@patternfly/react-core';
|
|
||||||
import FormRow from '@components/FormRow';
|
|
||||||
import FormField from '@components/FormField';
|
|
||||||
import FormActionGroup from '@components/FormActionGroup/FormActionGroup';
|
|
||||||
import { VariablesField } from '@components/CodeMirrorInput';
|
|
||||||
import { required } from '@util/validators';
|
|
||||||
|
|
||||||
function InventoryHostForm({ handleSubmit, handleCancel, host, i18n }) {
|
|
||||||
return (
|
|
||||||
<Formik
|
|
||||||
initialValues={{
|
|
||||||
name: host.name,
|
|
||||||
description: host.description,
|
|
||||||
variables: host.variables,
|
|
||||||
}}
|
|
||||||
onSubmit={handleSubmit}
|
|
||||||
>
|
|
||||||
{formik => (
|
|
||||||
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
|
|
||||||
<FormRow>
|
|
||||||
<FormField
|
|
||||||
id="host-name"
|
|
||||||
name="name"
|
|
||||||
type="text"
|
|
||||||
label={i18n._(t`Name`)}
|
|
||||||
validate={required(null, i18n)}
|
|
||||||
isRequired
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
id="host-description"
|
|
||||||
name="description"
|
|
||||||
type="text"
|
|
||||||
label={i18n._(t`Description`)}
|
|
||||||
/>
|
|
||||||
</FormRow>
|
|
||||||
<FormRow>
|
|
||||||
<VariablesField
|
|
||||||
id="host-variables"
|
|
||||||
name="variables"
|
|
||||||
label={i18n._(t`Variables`)}
|
|
||||||
/>
|
|
||||||
</FormRow>
|
|
||||||
<FormActionGroup
|
|
||||||
onCancel={handleCancel}
|
|
||||||
onSubmit={() => {
|
|
||||||
formik.handleSubmit();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Form>
|
|
||||||
)}
|
|
||||||
</Formik>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
InventoryHostForm.propTypes = {
|
|
||||||
handleSubmit: func.isRequired,
|
|
||||||
handleCancel: func.isRequired,
|
|
||||||
host: shape({}),
|
|
||||||
};
|
|
||||||
|
|
||||||
InventoryHostForm.defaultProps = {
|
|
||||||
host: {
|
|
||||||
name: '',
|
|
||||||
description: '',
|
|
||||||
variables: '---\n',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default withI18n()(InventoryHostForm);
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { act } from 'react-dom/test-utils';
|
|
||||||
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
|
||||||
import { sleep } from '@testUtils/testUtils';
|
|
||||||
import InventoryHostForm from './InventoryHostForm';
|
|
||||||
|
|
||||||
jest.mock('@api');
|
|
||||||
|
|
||||||
describe('<InventoryHostform />', () => {
|
|
||||||
let wrapper;
|
|
||||||
|
|
||||||
const handleSubmit = jest.fn();
|
|
||||||
const handleCancel = jest.fn();
|
|
||||||
|
|
||||||
const mockHostData = {
|
|
||||||
name: 'foo',
|
|
||||||
description: 'bar',
|
|
||||||
inventory: 1,
|
|
||||||
variables: '---\nfoo: bar',
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await act(async () => {
|
|
||||||
wrapper = mountWithContexts(
|
|
||||||
<InventoryHostForm
|
|
||||||
handleCancel={handleCancel}
|
|
||||||
handleSubmit={handleSubmit}
|
|
||||||
host={mockHostData}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
wrapper.unmount();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should display form fields', () => {
|
|
||||||
expect(wrapper.find('FormGroup[label="Name"]').length).toBe(1);
|
|
||||||
expect(wrapper.find('FormGroup[label="Description"]').length).toBe(1);
|
|
||||||
expect(wrapper.find('VariablesField').length).toBe(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should call handleSubmit when Submit button is clicked', async () => {
|
|
||||||
expect(handleSubmit).not.toHaveBeenCalled();
|
|
||||||
await act(async () => {
|
|
||||||
wrapper.find('button[aria-label="Save"]').simulate('click');
|
|
||||||
});
|
|
||||||
expect(handleSubmit).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should call handleCancel when Cancel button is clicked', async () => {
|
|
||||||
expect(handleCancel).not.toHaveBeenCalled();
|
|
||||||
wrapper.find('button[aria-label="Cancel"]').simulate('click');
|
|
||||||
await sleep(1);
|
|
||||||
expect(handleCancel).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -40,10 +40,10 @@ function OrganizationAdd() {
|
|||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
me={me || {}}
|
me={me || {}}
|
||||||
|
submitError={formError}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Config>
|
</Config>
|
||||||
{formError ? <div>error</div> : ''}
|
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
</PageSection>
|
</PageSection>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ describe('<OrganizationAdd />', () => {
|
|||||||
description: 'new description',
|
description: 'new description',
|
||||||
custom_virtualenv: 'Buzz',
|
custom_virtualenv: 'Buzz',
|
||||||
};
|
};
|
||||||
|
OrganizationsAPI.create.mockResolvedValueOnce({ data: {} });
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
const wrapper = mountWithContexts(<OrganizationAdd />);
|
const wrapper = mountWithContexts(<OrganizationAdd />);
|
||||||
wrapper.find('OrganizationForm').prop('onSubmit')(updatedOrgData, [], []);
|
wrapper.find('OrganizationForm').prop('onSubmit')(updatedOrgData, [], []);
|
||||||
|
|||||||
@@ -48,10 +48,10 @@ function OrganizationEdit({ organization }) {
|
|||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
me={me || {}}
|
me={me || {}}
|
||||||
|
submitError={formError}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Config>
|
</Config>
|
||||||
{formError ? <div>error</div> : null}
|
|
||||||
</CardBody>
|
</CardBody>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,13 +13,20 @@ import AnsibleSelect from '@components/AnsibleSelect';
|
|||||||
import ContentError from '@components/ContentError';
|
import ContentError from '@components/ContentError';
|
||||||
import ContentLoading from '@components/ContentLoading';
|
import ContentLoading from '@components/ContentLoading';
|
||||||
import FormRow from '@components/FormRow';
|
import FormRow from '@components/FormRow';
|
||||||
import FormField from '@components/FormField';
|
import FormField, { FormSubmitError } from '@components/FormField';
|
||||||
import FormActionGroup from '@components/FormActionGroup/FormActionGroup';
|
import FormActionGroup from '@components/FormActionGroup/FormActionGroup';
|
||||||
import { InstanceGroupsLookup } from '@components/Lookup/';
|
import { InstanceGroupsLookup } from '@components/Lookup/';
|
||||||
import { getAddedAndRemoved } from '@util/lists';
|
import { getAddedAndRemoved } from '@util/lists';
|
||||||
import { required, minMaxValue } from '@util/validators';
|
import { required, minMaxValue } from '@util/validators';
|
||||||
|
|
||||||
function OrganizationForm({ organization, i18n, me, onCancel, onSubmit }) {
|
function OrganizationForm({
|
||||||
|
organization,
|
||||||
|
i18n,
|
||||||
|
me,
|
||||||
|
onCancel,
|
||||||
|
onSubmit,
|
||||||
|
submitError,
|
||||||
|
}) {
|
||||||
const defaultVenv = {
|
const defaultVenv = {
|
||||||
label: i18n._(t`Use Default Ansible Environment`),
|
label: i18n._(t`Use Default Ansible Environment`),
|
||||||
value: '/venv/ansible/',
|
value: '/venv/ansible/',
|
||||||
@@ -161,6 +168,7 @@ function OrganizationForm({ organization, i18n, me, onCancel, onSubmit }) {
|
|||||||
t`Select the Instance Groups for this Organization to run on.`
|
t`Select the Instance Groups for this Organization to run on.`
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<FormSubmitError error={submitError} />
|
||||||
<FormActionGroup
|
<FormActionGroup
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
onSubmit={formik.handleSubmit}
|
onSubmit={formik.handleSubmit}
|
||||||
@@ -179,6 +187,7 @@ OrganizationForm.propTypes = {
|
|||||||
organization: PropTypes.shape(),
|
organization: PropTypes.shape(),
|
||||||
onSubmit: PropTypes.func.isRequired,
|
onSubmit: PropTypes.func.isRequired,
|
||||||
onCancel: PropTypes.func.isRequired,
|
onCancel: PropTypes.func.isRequired,
|
||||||
|
submitError: PropTypes.shape(),
|
||||||
};
|
};
|
||||||
|
|
||||||
OrganizationForm.defaultProps = {
|
OrganizationForm.defaultProps = {
|
||||||
@@ -188,6 +197,7 @@ OrganizationForm.defaultProps = {
|
|||||||
max_hosts: '0',
|
max_hosts: '0',
|
||||||
custom_virtualenv: '',
|
custom_virtualenv: '',
|
||||||
},
|
},
|
||||||
|
submitError: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
OrganizationForm.contextTypes = {
|
OrganizationForm.contextTypes = {
|
||||||
|
|||||||
@@ -41,13 +41,9 @@ function ProjectAdd() {
|
|||||||
<ProjectForm
|
<ProjectForm
|
||||||
handleCancel={handleCancel}
|
handleCancel={handleCancel}
|
||||||
handleSubmit={handleSubmit}
|
handleSubmit={handleSubmit}
|
||||||
|
submitError={formSubmitError}
|
||||||
/>
|
/>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
{formSubmitError ? (
|
|
||||||
<div className="formSubmitError">formSubmitError</div>
|
|
||||||
) : (
|
|
||||||
''
|
|
||||||
)}
|
|
||||||
</Card>
|
</Card>
|
||||||
</PageSection>
|
</PageSection>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -106,7 +106,8 @@ describe('<ProjectAdd />', () => {
|
|||||||
project_local_paths: ['foobar', 'qux'],
|
project_local_paths: ['foobar', 'qux'],
|
||||||
project_base_dir: 'dir/foo/bar',
|
project_base_dir: 'dir/foo/bar',
|
||||||
};
|
};
|
||||||
ProjectsAPI.create.mockImplementation(() => Promise.reject(new Error()));
|
const error = new Error('oops');
|
||||||
|
ProjectsAPI.create.mockImplementation(() => Promise.reject(error));
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(<ProjectAdd />, {
|
wrapper = mountWithContexts(<ProjectAdd />, {
|
||||||
context: { config },
|
context: { config },
|
||||||
@@ -121,7 +122,7 @@ describe('<ProjectAdd />', () => {
|
|||||||
});
|
});
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
expect(ProjectsAPI.create).toHaveBeenCalledTimes(1);
|
expect(ProjectsAPI.create).toHaveBeenCalledTimes(1);
|
||||||
expect(wrapper.find('ProjectAdd .formSubmitError').length).toBe(1);
|
expect(wrapper.find('ProjectForm').prop('submitError')).toEqual(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('CardBody cancel button should navigate to projects list', async () => {
|
test('CardBody cancel button should navigate to projects list', async () => {
|
||||||
|
|||||||
@@ -42,13 +42,9 @@ function ProjectEdit({ project }) {
|
|||||||
project={project}
|
project={project}
|
||||||
handleCancel={handleCancel}
|
handleCancel={handleCancel}
|
||||||
handleSubmit={handleSubmit}
|
handleSubmit={handleSubmit}
|
||||||
|
submitError={formSubmitError}
|
||||||
/>
|
/>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
{formSubmitError ? (
|
|
||||||
<div className="formSubmitError">formSubmitError</div>
|
|
||||||
) : (
|
|
||||||
''
|
|
||||||
)}
|
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,7 +120,10 @@ describe('<ProjectEdit />', () => {
|
|||||||
project_local_paths: [],
|
project_local_paths: [],
|
||||||
project_base_dir: 'foo/bar',
|
project_base_dir: 'foo/bar',
|
||||||
};
|
};
|
||||||
ProjectsAPI.update.mockImplementation(() => Promise.reject(new Error()));
|
const error = new Error('oops');
|
||||||
|
const realConsoleError = global.console.error;
|
||||||
|
global.console.error = jest.fn();
|
||||||
|
ProjectsAPI.update.mockImplementation(() => Promise.reject(error));
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(
|
wrapper = mountWithContexts(
|
||||||
<ProjectEdit project={{ ...projectData, scm_type: 'manual' }} />,
|
<ProjectEdit project={{ ...projectData, scm_type: 'manual' }} />,
|
||||||
@@ -135,7 +138,8 @@ describe('<ProjectEdit />', () => {
|
|||||||
});
|
});
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
expect(ProjectsAPI.update).toHaveBeenCalledTimes(1);
|
expect(ProjectsAPI.update).toHaveBeenCalledTimes(1);
|
||||||
expect(wrapper.find('ProjectEdit .formSubmitError').length).toBe(1);
|
expect(wrapper.find('ProjectForm').prop('submitError')).toEqual(error);
|
||||||
|
global.console.error = realConsoleError;
|
||||||
});
|
});
|
||||||
|
|
||||||
test('CardBody cancel button should navigate to project details', async () => {
|
test('CardBody cancel button should navigate to project details', async () => {
|
||||||
|
|||||||
@@ -10,7 +10,10 @@ import AnsibleSelect from '@components/AnsibleSelect';
|
|||||||
import ContentError from '@components/ContentError';
|
import ContentError from '@components/ContentError';
|
||||||
import ContentLoading from '@components/ContentLoading';
|
import ContentLoading from '@components/ContentLoading';
|
||||||
import FormActionGroup from '@components/FormActionGroup/FormActionGroup';
|
import FormActionGroup from '@components/FormActionGroup/FormActionGroup';
|
||||||
import FormField, { FieldTooltip } from '@components/FormField';
|
import FormField, {
|
||||||
|
FieldTooltip,
|
||||||
|
FormSubmitError,
|
||||||
|
} from '@components/FormField';
|
||||||
import FormRow from '@components/FormRow';
|
import FormRow from '@components/FormRow';
|
||||||
import OrganizationLookup from '@components/Lookup/OrganizationLookup';
|
import OrganizationLookup from '@components/Lookup/OrganizationLookup';
|
||||||
import { CredentialTypesAPI, ProjectsAPI } from '@api';
|
import { CredentialTypesAPI, ProjectsAPI } from '@api';
|
||||||
@@ -70,7 +73,7 @@ const fetchCredentials = async credential => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
function ProjectForm({ project, ...props }) {
|
function ProjectForm({ project, submitError, ...props }) {
|
||||||
const { i18n, handleCancel, handleSubmit } = props;
|
const { i18n, handleCancel, handleSubmit } = props;
|
||||||
const { summary_fields = {} } = project;
|
const { summary_fields = {} } = project;
|
||||||
const [contentError, setContentError] = useState(null);
|
const [contentError, setContentError] = useState(null);
|
||||||
@@ -385,6 +388,7 @@ function ProjectForm({ project, ...props }) {
|
|||||||
}
|
}
|
||||||
</Config>
|
</Config>
|
||||||
</FormRow>
|
</FormRow>
|
||||||
|
<FormSubmitError error={submitError} />
|
||||||
<FormActionGroup
|
<FormActionGroup
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
onSubmit={formik.handleSubmit}
|
onSubmit={formik.handleSubmit}
|
||||||
@@ -401,10 +405,12 @@ ProjectForm.propTypes = {
|
|||||||
handleCancel: PropTypes.func.isRequired,
|
handleCancel: PropTypes.func.isRequired,
|
||||||
handleSubmit: PropTypes.func.isRequired,
|
handleSubmit: PropTypes.func.isRequired,
|
||||||
project: PropTypes.shape({}),
|
project: PropTypes.shape({}),
|
||||||
|
submitError: PropTypes.shape({}),
|
||||||
};
|
};
|
||||||
|
|
||||||
ProjectForm.defaultProps = {
|
ProjectForm.defaultProps = {
|
||||||
project: {},
|
project: {},
|
||||||
|
submitError: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withI18n()(ProjectForm);
|
export default withI18n()(ProjectForm);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class TeamAdd extends React.Component {
|
|||||||
super(props);
|
super(props);
|
||||||
this.handleSubmit = this.handleSubmit.bind(this);
|
this.handleSubmit = this.handleSubmit.bind(this);
|
||||||
this.handleCancel = this.handleCancel.bind(this);
|
this.handleCancel = this.handleCancel.bind(this);
|
||||||
this.state = { error: '' };
|
this.state = { error: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleSubmit(values) {
|
async handleSubmit(values) {
|
||||||
@@ -43,10 +43,10 @@ class TeamAdd extends React.Component {
|
|||||||
handleSubmit={this.handleSubmit}
|
handleSubmit={this.handleSubmit}
|
||||||
handleCancel={this.handleCancel}
|
handleCancel={this.handleCancel}
|
||||||
me={me || {}}
|
me={me || {}}
|
||||||
|
submitError={error}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Config>
|
</Config>
|
||||||
{error ? <div>error</div> : ''}
|
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
</PageSection>
|
</PageSection>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ jest.mock('@api');
|
|||||||
|
|
||||||
describe('<TeamAdd />', () => {
|
describe('<TeamAdd />', () => {
|
||||||
test('handleSubmit should post to api', async () => {
|
test('handleSubmit should post to api', async () => {
|
||||||
|
TeamsAPI.create.mockResolvedValueOnce({ data: {} });
|
||||||
const wrapper = mountWithContexts(<TeamAdd />);
|
const wrapper = mountWithContexts(<TeamAdd />);
|
||||||
const updatedTeamData = {
|
const updatedTeamData = {
|
||||||
name: 'new name',
|
name: 'new name',
|
||||||
|
|||||||
@@ -34,10 +34,10 @@ function TeamEdit({ team }) {
|
|||||||
handleSubmit={handleSubmit}
|
handleSubmit={handleSubmit}
|
||||||
handleCancel={handleCancel}
|
handleCancel={handleCancel}
|
||||||
me={me || {}}
|
me={me || {}}
|
||||||
|
submitError={error}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Config>
|
</Config>
|
||||||
{error ? <div>error</div> : null}
|
|
||||||
</CardBody>
|
</CardBody>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ import { t } from '@lingui/macro';
|
|||||||
import { Formik, Field } from 'formik';
|
import { Formik, Field } from 'formik';
|
||||||
import { Form } from '@patternfly/react-core';
|
import { Form } from '@patternfly/react-core';
|
||||||
import FormActionGroup from '@components/FormActionGroup/FormActionGroup';
|
import FormActionGroup from '@components/FormActionGroup/FormActionGroup';
|
||||||
import FormField from '@components/FormField';
|
import FormField, { FormSubmitError } from '@components/FormField';
|
||||||
import FormRow from '@components/FormRow';
|
import FormRow from '@components/FormRow';
|
||||||
import OrganizationLookup from '@components/Lookup/OrganizationLookup';
|
import OrganizationLookup from '@components/Lookup/OrganizationLookup';
|
||||||
import { required } from '@util/validators';
|
import { required } from '@util/validators';
|
||||||
|
|
||||||
function TeamForm(props) {
|
function TeamForm(props) {
|
||||||
const { team, handleCancel, handleSubmit, i18n } = props;
|
const { team, handleCancel, handleSubmit, submitError, i18n } = props;
|
||||||
const [organization, setOrganization] = useState(
|
const [organization, setOrganization] = useState(
|
||||||
team.summary_fields ? team.summary_fields.organization : null
|
team.summary_fields ? team.summary_fields.organization : null
|
||||||
);
|
);
|
||||||
@@ -70,6 +70,7 @@ function TeamForm(props) {
|
|||||||
)}
|
)}
|
||||||
</Field>
|
</Field>
|
||||||
</FormRow>
|
</FormRow>
|
||||||
|
<FormSubmitError error={submitError} />
|
||||||
<FormActionGroup
|
<FormActionGroup
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
onSubmit={formik.handleSubmit}
|
onSubmit={formik.handleSubmit}
|
||||||
@@ -84,10 +85,12 @@ TeamForm.propTypes = {
|
|||||||
handleCancel: PropTypes.func.isRequired,
|
handleCancel: PropTypes.func.isRequired,
|
||||||
handleSubmit: PropTypes.func.isRequired,
|
handleSubmit: PropTypes.func.isRequired,
|
||||||
team: PropTypes.shape({}),
|
team: PropTypes.shape({}),
|
||||||
|
submitError: PropTypes.shape(),
|
||||||
};
|
};
|
||||||
|
|
||||||
TeamForm.defaultProps = {
|
TeamForm.defaultProps = {
|
||||||
team: {},
|
team: {},
|
||||||
|
submitError: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withI18n()(TeamForm);
|
export default withI18n()(TeamForm);
|
||||||
|
|||||||
@@ -66,9 +66,9 @@ function JobTemplateAdd() {
|
|||||||
<JobTemplateForm
|
<JobTemplateForm
|
||||||
handleCancel={handleCancel}
|
handleCancel={handleCancel}
|
||||||
handleSubmit={handleSubmit}
|
handleSubmit={handleSubmit}
|
||||||
|
submitError={formSubmitError}
|
||||||
/>
|
/>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
{formSubmitError ? <div>formSubmitError</div> : ''}
|
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,8 +113,8 @@ class JobTemplateEdit extends Component {
|
|||||||
this.submitCredentials(credentials),
|
this.submitCredentials(credentials),
|
||||||
]);
|
]);
|
||||||
history.push(this.detailsUrl);
|
history.push(this.detailsUrl);
|
||||||
} catch (formSubmitError) {
|
} catch (error) {
|
||||||
this.setState({ formSubmitError });
|
this.setState({ formSubmitError: error });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,8 +209,8 @@ class JobTemplateEdit extends Component {
|
|||||||
handleCancel={this.handleCancel}
|
handleCancel={this.handleCancel}
|
||||||
handleSubmit={this.handleSubmit}
|
handleSubmit={this.handleSubmit}
|
||||||
relatedProjectPlaybooks={relatedProjectPlaybooks}
|
relatedProjectPlaybooks={relatedProjectPlaybooks}
|
||||||
|
submitError={formSubmitError}
|
||||||
/>
|
/>
|
||||||
{formSubmitError ? <div> error </div> : null}
|
|
||||||
</CardBody>
|
</CardBody>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,11 @@ import ContentLoading from '@components/ContentLoading';
|
|||||||
import AnsibleSelect from '@components/AnsibleSelect';
|
import AnsibleSelect from '@components/AnsibleSelect';
|
||||||
import { TagMultiSelect } from '@components/MultiSelect';
|
import { TagMultiSelect } from '@components/MultiSelect';
|
||||||
import FormActionGroup from '@components/FormActionGroup';
|
import FormActionGroup from '@components/FormActionGroup';
|
||||||
import FormField, { CheckboxField, FieldTooltip } from '@components/FormField';
|
import FormField, {
|
||||||
|
CheckboxField,
|
||||||
|
FieldTooltip,
|
||||||
|
FormSubmitError,
|
||||||
|
} from '@components/FormField';
|
||||||
import FormRow from '@components/FormRow';
|
import FormRow from '@components/FormRow';
|
||||||
import CollapsibleSection from '@components/CollapsibleSection';
|
import CollapsibleSection from '@components/CollapsibleSection';
|
||||||
import { required } from '@util/validators';
|
import { required } from '@util/validators';
|
||||||
@@ -48,6 +52,7 @@ class JobTemplateForm extends Component {
|
|||||||
template: JobTemplate,
|
template: JobTemplate,
|
||||||
handleCancel: PropTypes.func.isRequired,
|
handleCancel: PropTypes.func.isRequired,
|
||||||
handleSubmit: PropTypes.func.isRequired,
|
handleSubmit: PropTypes.func.isRequired,
|
||||||
|
submitError: PropTypes.shape({}),
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
@@ -66,6 +71,7 @@ class JobTemplateForm extends Component {
|
|||||||
},
|
},
|
||||||
isNew: true,
|
isNew: true,
|
||||||
},
|
},
|
||||||
|
submitError: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -161,8 +167,9 @@ class JobTemplateForm extends Component {
|
|||||||
handleSubmit,
|
handleSubmit,
|
||||||
handleBlur,
|
handleBlur,
|
||||||
setFieldValue,
|
setFieldValue,
|
||||||
i18n,
|
|
||||||
template,
|
template,
|
||||||
|
submitError,
|
||||||
|
i18n,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const jobTypeOptions = [
|
const jobTypeOptions = [
|
||||||
@@ -201,6 +208,7 @@ class JobTemplateForm extends Component {
|
|||||||
if (contentError) {
|
if (contentError) {
|
||||||
return <ContentError error={contentError} />;
|
return <ContentError error={contentError} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AdvancedFieldsWrapper = template.isNew ? CollapsibleSection : 'div';
|
const AdvancedFieldsWrapper = template.isNew ? CollapsibleSection : 'div';
|
||||||
return (
|
return (
|
||||||
<Form autoComplete="off" onSubmit={handleSubmit}>
|
<Form autoComplete="off" onSubmit={handleSubmit}>
|
||||||
@@ -585,6 +593,7 @@ class JobTemplateForm extends Component {
|
|||||||
</FormRow>
|
</FormRow>
|
||||||
</div>
|
</div>
|
||||||
</AdvancedFieldsWrapper>
|
</AdvancedFieldsWrapper>
|
||||||
|
<FormSubmitError error={submitError} />
|
||||||
<FormActionGroup onCancel={handleCancel} onSubmit={handleSubmit} />
|
<FormActionGroup onCancel={handleCancel} onSubmit={handleSubmit} />
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
@@ -631,7 +640,13 @@ const FormikApp = withFormik({
|
|||||||
credentials: summary_fields.credentials || [],
|
credentials: summary_fields.credentials || [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
handleSubmit: (values, { props }) => props.handleSubmit(values),
|
handleSubmit: async (values, { props, setErrors }) => {
|
||||||
|
try {
|
||||||
|
await props.handleSubmit(values);
|
||||||
|
} catch (errors) {
|
||||||
|
setErrors(errors);
|
||||||
|
}
|
||||||
|
},
|
||||||
})(JobTemplateForm);
|
})(JobTemplateForm);
|
||||||
|
|
||||||
export { JobTemplateForm as _JobTemplateForm };
|
export { JobTemplateForm as _JobTemplateForm };
|
||||||
|
|||||||
@@ -35,13 +35,12 @@ function UserAdd() {
|
|||||||
<PageSection>
|
<PageSection>
|
||||||
<Card>
|
<Card>
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<UserForm handleCancel={handleCancel} handleSubmit={handleSubmit} />
|
<UserForm
|
||||||
|
handleCancel={handleCancel}
|
||||||
|
handleSubmit={handleSubmit}
|
||||||
|
submitError={formSubmitError}
|
||||||
|
/>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
{formSubmitError ? (
|
|
||||||
<div className="formSubmitError">formSubmitError</div>
|
|
||||||
) : (
|
|
||||||
''
|
|
||||||
)}
|
|
||||||
</Card>
|
</Card>
|
||||||
</PageSection>
|
</PageSection>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ describe('<UserAdd />', () => {
|
|||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(<UserAdd />);
|
wrapper = mountWithContexts(<UserAdd />);
|
||||||
});
|
});
|
||||||
|
UsersAPI.create.mockResolvedValueOnce({ data: {} });
|
||||||
const updatedUserData = {
|
const updatedUserData = {
|
||||||
username: 'sysadmin',
|
username: 'sysadmin',
|
||||||
email: 'sysadmin@ansible.com',
|
email: 'sysadmin@ansible.com',
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ function UserEdit({ user, history }) {
|
|||||||
user={user}
|
user={user}
|
||||||
handleCancel={handleCancel}
|
handleCancel={handleCancel}
|
||||||
handleSubmit={handleSubmit}
|
handleSubmit={handleSubmit}
|
||||||
|
submitError={formSubmitError}
|
||||||
/>
|
/>
|
||||||
{formSubmitError ? <div> error </div> : null}
|
|
||||||
</CardBody>
|
</CardBody>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,15 @@ import { Formik, Field } from 'formik';
|
|||||||
import { Form, FormGroup } from '@patternfly/react-core';
|
import { Form, FormGroup } from '@patternfly/react-core';
|
||||||
import AnsibleSelect from '@components/AnsibleSelect';
|
import AnsibleSelect from '@components/AnsibleSelect';
|
||||||
import FormActionGroup from '@components/FormActionGroup/FormActionGroup';
|
import FormActionGroup from '@components/FormActionGroup/FormActionGroup';
|
||||||
import FormField, { PasswordField } from '@components/FormField';
|
import FormField, {
|
||||||
|
PasswordField,
|
||||||
|
FormSubmitError,
|
||||||
|
} from '@components/FormField';
|
||||||
import FormRow from '@components/FormRow';
|
import FormRow from '@components/FormRow';
|
||||||
import OrganizationLookup from '@components/Lookup/OrganizationLookup';
|
import OrganizationLookup from '@components/Lookup/OrganizationLookup';
|
||||||
import { required, requiredEmail } from '@util/validators';
|
import { required, requiredEmail } from '@util/validators';
|
||||||
|
|
||||||
function UserForm(props) {
|
function UserForm({ user, handleCancel, handleSubmit, submitError, i18n }) {
|
||||||
const { user, handleCancel, handleSubmit, i18n } = props;
|
|
||||||
const [organization, setOrganization] = useState(null);
|
const [organization, setOrganization] = useState(null);
|
||||||
|
|
||||||
const userTypeOptions = [
|
const userTypeOptions = [
|
||||||
@@ -183,6 +185,7 @@ function UserForm(props) {
|
|||||||
}}
|
}}
|
||||||
</Field>
|
</Field>
|
||||||
</FormRow>
|
</FormRow>
|
||||||
|
<FormSubmitError error={submitError} />
|
||||||
<FormActionGroup
|
<FormActionGroup
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
onSubmit={formik.handleSubmit}
|
onSubmit={formik.handleSubmit}
|
||||||
|
|||||||
Reference in New Issue
Block a user