From e3fe680d14100e8bc9d54b7547b2ae9eac044609 Mon Sep 17 00:00:00 2001 From: nixocio Date: Mon, 10 Aug 2020 14:47:01 -0400 Subject: [PATCH] Add feature to edit instance group Add feature to edit instance group. See: https://github.com/ansible/awx/issues/7767 --- .../InstanceGroupEdit/InstanceGroupEdit.jsx | 40 ++++- .../InstanceGroupEdit.test.jsx | 140 ++++++++++++++++++ .../shared/InstanceGroupForm.jsx | 9 +- 3 files changed, 179 insertions(+), 10 deletions(-) create mode 100644 awx/ui_next/src/screens/InstanceGroup/InstanceGroupEdit/InstanceGroupEdit.test.jsx 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 ( <>