diff --git a/awx/ui_next/src/screens/InstanceGroup/InstanceGroupEdit/InstanceGroupEdit.jsx b/awx/ui_next/src/screens/InstanceGroup/InstanceGroupEdit/InstanceGroupEdit.jsx
index 2f724479ee30..b2f9bbaa9a8b 100644
--- a/awx/ui_next/src/screens/InstanceGroup/InstanceGroupEdit/InstanceGroupEdit.jsx
+++ b/awx/ui_next/src/screens/InstanceGroup/InstanceGroupEdit/InstanceGroupEdit.jsx
@@ -1,13 +1,37 @@
-import React from 'react';
-import { Card, PageSection } from '@patternfly/react-core';
+import React, { useState } from 'react';
+import { useHistory } from 'react-router-dom';
+
+import { CardBody } from '../../../components/Card';
+import { InstanceGroupsAPI } from '../../../api';
+import InstanceGroupForm from '../shared/InstanceGroupForm';
+
+function InstanceGroupEdit({ instanceGroup }) {
+ const history = useHistory();
+ const [submitError, setSubmitError] = useState(null);
+ const detailsUrl = `/instance_groups/${instanceGroup.id}/details`;
+
+ const handleSubmit = async values => {
+ try {
+ await InstanceGroupsAPI.update(instanceGroup.id, values);
+ history.push(detailsUrl);
+ } catch (error) {
+ setSubmitError(error);
+ }
+ };
+
+ const handleCancel = () => {
+ history.push(detailsUrl);
+ };
-function InstanceGroupEdit() {
return (
-
-
- Edit instance group
-
-
+
+
+
);
}
diff --git a/awx/ui_next/src/screens/InstanceGroup/InstanceGroupEdit/InstanceGroupEdit.test.jsx b/awx/ui_next/src/screens/InstanceGroup/InstanceGroupEdit/InstanceGroupEdit.test.jsx
new file mode 100644
index 000000000000..45b94bd17dde
--- /dev/null
+++ b/awx/ui_next/src/screens/InstanceGroup/InstanceGroupEdit/InstanceGroupEdit.test.jsx
@@ -0,0 +1,140 @@
+import React from 'react';
+import { act } from 'react-dom/test-utils';
+import { createMemoryHistory } from 'history';
+
+import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
+import { InstanceGroupsAPI } from '../../../api';
+
+import InstanceGroupEdit from './InstanceGroupEdit';
+
+jest.mock('../../../api');
+
+const instanceGroupData = {
+ id: 42,
+ type: 'instance_group',
+ url: '/api/v2/instance_groups/42/',
+ related: {
+ jobs: '/api/v2/instance_groups/42/jobs/',
+ instances: '/api/v2/instance_groups/7/instances/',
+ },
+ name: 'Foo',
+ created: '2020-07-21T18:41:02.818081Z',
+ modified: '2020-07-24T20:32:03.121079Z',
+ capacity: 24,
+ committed_capacity: 0,
+ consumed_capacity: 0,
+ percent_capacity_remaining: 100.0,
+ jobs_running: 0,
+ jobs_total: 0,
+ instances: 1,
+ controller: null,
+ is_controller: false,
+ is_isolated: false,
+ is_containerized: false,
+ credential: null,
+ policy_instance_percentage: 46,
+ policy_instance_minimum: 12,
+ policy_instance_list: [],
+ pod_spec_override: '',
+ summary_fields: {
+ user_capabilities: {
+ edit: true,
+ delete: true,
+ },
+ },
+};
+
+const updatedInstanceGroup = {
+ name: 'Bar',
+ policy_instance_percentage: 42,
+};
+
+describe('', () => {
+ let wrapper;
+ let history;
+
+ beforeAll(async () => {
+ history = createMemoryHistory();
+ await act(async () => {
+ wrapper = mountWithContexts(
+ ,
+ {
+ context: { router: { history } },
+ }
+ );
+ });
+ });
+
+ afterAll(() => {
+ jest.clearAllMocks();
+ wrapper.unmount();
+ });
+
+ test('tower instance group name can not be updated', async () => {
+ let towerWrapper;
+ await act(async () => {
+ towerWrapper = mountWithContexts(
+ ,
+ {
+ context: { router: { history } },
+ }
+ );
+ });
+ expect(
+ towerWrapper.find('input#instance-group-name').prop('disabled')
+ ).toBeTruthy();
+ expect(
+ towerWrapper.find('input#instance-group-name').prop('value')
+ ).toEqual('tower');
+ });
+
+ test('handleSubmit should call the api and redirect to details page', async () => {
+ await act(async () => {
+ wrapper.find('InstanceGroupForm').invoke('onSubmit')(
+ updatedInstanceGroup
+ );
+ });
+ expect(InstanceGroupsAPI.update).toHaveBeenCalledWith(
+ 42,
+ updatedInstanceGroup
+ );
+ });
+
+ test('should navigate to instance group details when cancel is clicked', async () => {
+ await act(async () => {
+ wrapper.find('button[aria-label="Cancel"]').prop('onClick')();
+ });
+ expect(history.location.pathname).toEqual('/instance_groups/42/details');
+ });
+
+ test('should navigate to instance group details after successful submission', async () => {
+ await act(async () => {
+ wrapper.find('InstanceGroupForm').invoke('onSubmit')(
+ updatedInstanceGroup
+ );
+ });
+ wrapper.update();
+ expect(wrapper.find('FormSubmitError').length).toBe(0);
+ expect(history.location.pathname).toEqual('/instance_groups/42/details');
+ });
+
+ test('failed form submission should show an error message', async () => {
+ const error = {
+ response: {
+ data: { detail: 'An error occurred' },
+ },
+ };
+ InstanceGroupsAPI.update.mockImplementationOnce(() =>
+ Promise.reject(error)
+ );
+ await act(async () => {
+ wrapper.find('InstanceGroupForm').invoke('onSubmit')(
+ updatedInstanceGroup
+ );
+ });
+ wrapper.update();
+ expect(wrapper.find('FormSubmitError').length).toBe(1);
+ });
+});
diff --git a/awx/ui_next/src/screens/InstanceGroup/shared/InstanceGroupForm.jsx b/awx/ui_next/src/screens/InstanceGroup/shared/InstanceGroupForm.jsx
index a2477d2f5381..2f5509239492 100644
--- a/awx/ui_next/src/screens/InstanceGroup/shared/InstanceGroupForm.jsx
+++ b/awx/ui_next/src/screens/InstanceGroup/shared/InstanceGroupForm.jsx
@@ -1,6 +1,6 @@
import React from 'react';
import { func, shape } from 'prop-types';
-import { Formik } from 'formik';
+import { Formik, useField } from 'formik';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { Form } from '@patternfly/react-core';
@@ -11,21 +11,24 @@ import { required, minMaxValue } from '../../../util/validators';
import { FormColumnLayout } from '../../../components/FormLayout';
function InstanceGroupFormFields({ i18n }) {
+ const [instanceGroupNameField, ,] = useField('name');
return (
<>