diff --git a/awx/ui_next/src/components/MultiSelect/MultiSelect.jsx b/awx/ui_next/src/components/MultiSelect/MultiSelect.jsx
index ea5a701ccd..cb386b67b6 100644
--- a/awx/ui_next/src/components/MultiSelect/MultiSelect.jsx
+++ b/awx/ui_next/src/components/MultiSelect/MultiSelect.jsx
@@ -86,7 +86,7 @@ class MultiSelect extends Component {
this.handleSelection(e, option);
}
} else {
- this.setState({ isExpanded: false });
+ this.setState({ input: '', isExpanded: false });
}
}
@@ -105,17 +105,30 @@ class MultiSelect extends Component {
handleAddItem(event) {
const { input, chipItems } = this.state;
const { onAddNewItem } = this.props;
- const newChip = { name: input, id: Math.random() };
- if (event.key !== 'Tab') {
+ const isIncluded = chipItems.some(chipItem => chipItem.name === input);
+
+ if (!input) {
return;
}
- this.setState({
- chipItems: chipItems.concat(newChip),
- isExpanded: false,
- input: '',
- });
- onAddNewItem(input);
+ if (isIncluded) {
+ // This event.preventDefault prevents the form from submitting
+ // if the user tries to create 2 chips of the same name
+ event.preventDefault();
+ this.setState({ input: '', isExpanded: false });
+ return;
+ }
+ if (event.key === 'Enter') {
+ event.preventDefault();
+ this.setState({
+ chipItems: chipItems.concat({ name: input, id: input }),
+ isExpanded: false,
+ input: '',
+ });
+ onAddNewItem(input);
+ } else if (event.key === 'Tab') {
+ this.setState({ input: '' });
+ }
}
handleInputChange(e) {
diff --git a/awx/ui_next/src/components/MultiSelect/MultiSelect.test.jsx b/awx/ui_next/src/components/MultiSelect/MultiSelect.test.jsx
index 422baacb84..66996fcdb7 100644
--- a/awx/ui_next/src/components/MultiSelect/MultiSelect.test.jsx
+++ b/awx/ui_next/src/components/MultiSelect/MultiSelect.test.jsx
@@ -40,10 +40,10 @@ describe('', () => {
const component = wrapper.find('MultiSelect');
component
.find('input[aria-label="labels"]')
- .simulate('keydown', { key: 'Tab' });
+ .simulate('keydown', { key: 'Enter' });
component.update();
await sleep(1);
- expect(component.state().chipItems.length).toBe(3);
+ expect(component.state().chipItems.length).toBe(2);
});
test('handleAddItem adds a chip only when Tab is pressed', () => {
const onAddNewItem = jest.fn();
@@ -57,7 +57,7 @@ describe('', () => {
);
const event = {
preventDefault: () => {},
- key: 'Tab',
+ key: 'Enter',
};
const component = wrapper.find('MultiSelect');
diff --git a/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.jsx b/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.jsx
index 5c738a942d..b3b4d12f92 100644
--- a/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.jsx
+++ b/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.jsx
@@ -14,21 +14,47 @@ import JobTemplateForm from '../shared/JobTemplateForm';
import { JobTemplatesAPI } from '@api';
function JobTemplateAdd({ history, i18n }) {
- const [error, setError] = useState(null);
+ const [formSubmitError, setFormSubmitError] = useState(null);
- const handleSubmit = async values => {
- setError(null);
+ async function handleSubmit(values) {
+ const { newLabels, removedLabels } = values;
+ delete values.newLabels;
+ delete values.removedLabels;
+
+ setFormSubmitError(null);
try {
- const { data } = await JobTemplatesAPI.create(values);
- history.push(`/templates/${data.type}/${data.id}/details`);
- } catch (err) {
- setError(err);
+ const {
+ data: { id, type },
+ } = await JobTemplatesAPI.create(values);
+ await Promise.all([submitLabels(id, newLabels, removedLabels)]);
+ history.push(`/templates/${type}/${id}/details`);
+ } catch (error) {
+ setFormSubmitError(error);
}
- };
+ }
- const handleCancel = () => {
+ async function submitLabels(id, newLabels = [], removedLabels = []) {
+ const disassociationPromises = removedLabels.map(label =>
+ JobTemplatesAPI.disassociateLabel(id, label)
+ );
+ const associationPromises = newLabels
+ .filter(label => !label.organization)
+ .map(label => JobTemplatesAPI.associateLabel(id, label));
+ const creationPromises = newLabels
+ .filter(label => label.organization)
+ .map(label => JobTemplatesAPI.generateLabel(id, label));
+
+ const results = await Promise.all([
+ ...disassociationPromises,
+ ...associationPromises,
+ ...creationPromises,
+ ]);
+ return results;
+ }
+
+ function handleCancel() {
history.push(`/templates`);
- };
+ }
return (
@@ -44,7 +70,7 @@ function JobTemplateAdd({ history, i18n }) {
handleSubmit={handleSubmit}
/>
- {error ? error
: ''}
+ {formSubmitError ? formSubmitError
: ''}
);
diff --git a/awx/ui_next/src/screens/Template/shared/JobTemplateForm.test.jsx b/awx/ui_next/src/screens/Template/shared/JobTemplateForm.test.jsx
index d9be14e148..1d6a46adb2 100644
--- a/awx/ui_next/src/screens/Template/shared/JobTemplateForm.test.jsx
+++ b/awx/ui_next/src/screens/Template/shared/JobTemplateForm.test.jsx
@@ -158,7 +158,7 @@ describe('', () => {
_JobTemplateForm.prototype,
'handleNewLabel'
);
- const event = { key: 'Tab' };
+ const event = { key: 'Enter', preventDefault: () => {} };
const wrapper = mountWithContexts(