mirror of
https://github.com/ansible/awx.git
synced 2026-03-21 10:57:36 -02:30
Merge pull request #8070 from nixocio/ui_add_edit_container_groups
Add/Edit Container Groups Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
@@ -111,7 +111,7 @@ function ContainerGroup({ i18n, setBreadcrumb }) {
|
|||||||
{instanceGroup && (
|
{instanceGroup && (
|
||||||
<>
|
<>
|
||||||
<Route path="/instance_groups/container_group/:id/edit">
|
<Route path="/instance_groups/container_group/:id/edit">
|
||||||
<ContainerGroupEdit />
|
<ContainerGroupEdit instanceGroup={instanceGroup} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/instance_groups/container_group/:id/details">
|
<Route path="/instance_groups/container_group/:id/details">
|
||||||
<ContainerGroupDetails />
|
<ContainerGroupDetails />
|
||||||
|
|||||||
@@ -1,11 +1,103 @@
|
|||||||
import React from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import { Card, PageSection } from '@patternfly/react-core';
|
import { Card, PageSection } from '@patternfly/react-core';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { CardBody } from '../../../components/Card';
|
||||||
|
import { InstanceGroupsAPI } from '../../../api';
|
||||||
|
import useRequest from '../../../util/useRequest';
|
||||||
|
import ContentError from '../../../components/ContentError';
|
||||||
|
import ContentLoading from '../../../components/ContentLoading';
|
||||||
|
import { jsonToYaml, isJsonString } from '../../../util/yaml';
|
||||||
|
|
||||||
|
import ContainerGroupForm from '../shared/ContainerGroupForm';
|
||||||
|
|
||||||
function ContainerGroupAdd() {
|
function ContainerGroupAdd() {
|
||||||
|
const history = useHistory();
|
||||||
|
const [submitError, setSubmitError] = useState(null);
|
||||||
|
|
||||||
|
const getPodSpecValue = value => {
|
||||||
|
if (isJsonString(value)) {
|
||||||
|
value = jsonToYaml(value);
|
||||||
|
}
|
||||||
|
if (value !== jsonToYaml(JSON.stringify(initialPodSpec))) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async values => {
|
||||||
|
try {
|
||||||
|
const { data: response } = await InstanceGroupsAPI.create({
|
||||||
|
name: values.name,
|
||||||
|
credential: values?.credential?.id,
|
||||||
|
pod_spec_override: values.override
|
||||||
|
? getPodSpecValue(values.pod_spec_override)
|
||||||
|
: null,
|
||||||
|
});
|
||||||
|
history.push(`/instance_groups/container_group/${response.id}/details`);
|
||||||
|
} catch (error) {
|
||||||
|
setSubmitError(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
history.push(`/instance_groups`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
error: fetchError,
|
||||||
|
isLoading,
|
||||||
|
request: fetchInitialPodSpec,
|
||||||
|
result: initialPodSpec,
|
||||||
|
} = useRequest(
|
||||||
|
useCallback(async () => {
|
||||||
|
const { data } = await InstanceGroupsAPI.readOptions();
|
||||||
|
return data.actions.POST.pod_spec_override.default;
|
||||||
|
}, []),
|
||||||
|
{
|
||||||
|
initialPodSpec: {},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchInitialPodSpec();
|
||||||
|
}, [fetchInitialPodSpec]);
|
||||||
|
|
||||||
|
if (fetchError) {
|
||||||
|
return (
|
||||||
|
<PageSection>
|
||||||
|
<Card>
|
||||||
|
<CardBody>
|
||||||
|
<ContentError error={fetchError} />
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
</PageSection>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<PageSection>
|
||||||
|
<Card>
|
||||||
|
<CardBody>
|
||||||
|
<ContentLoading />
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
</PageSection>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageSection>
|
<PageSection>
|
||||||
<Card>
|
<Card>
|
||||||
<div>Add container group</div>
|
<CardBody>
|
||||||
|
<ContainerGroupForm
|
||||||
|
initialPodSpec={initialPodSpec}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
submitError={submitError}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
/>
|
||||||
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
</PageSection>
|
</PageSection>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
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 ContainerGroupAdd from './ContainerGroupAdd';
|
||||||
|
|
||||||
|
jest.mock('../../../api');
|
||||||
|
|
||||||
|
const initialPodSpec = {
|
||||||
|
default: {
|
||||||
|
apiVersion: 'v1',
|
||||||
|
kind: 'Pod',
|
||||||
|
metadata: {
|
||||||
|
namespace: 'default',
|
||||||
|
},
|
||||||
|
spec: {
|
||||||
|
containers: [
|
||||||
|
{
|
||||||
|
image: 'ansible/ansible-runner',
|
||||||
|
tty: true,
|
||||||
|
stdin: true,
|
||||||
|
imagePullPolicy: 'Always',
|
||||||
|
args: ['sleep', 'infinity'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const instanceGroupCreateData = {
|
||||||
|
name: 'Fuz',
|
||||||
|
credential: { id: 71, name: 'CG' },
|
||||||
|
pod_spec_override:
|
||||||
|
'apiVersion: v1\nkind: Pod\nmetadata:\n namespace: default\nspec:\n containers:\n - image: ansible/ansible-runner\n tty: true\n stdin: true\n imagePullPolicy: Always\n args:\n - sleep\n - infinity\n - test',
|
||||||
|
};
|
||||||
|
|
||||||
|
InstanceGroupsAPI.create.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
id: 123,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
InstanceGroupsAPI.readOptions.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
results: initialPodSpec,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('<ContainerGroupAdd/>', () => {
|
||||||
|
let wrapper;
|
||||||
|
let history;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
history = createMemoryHistory({
|
||||||
|
initialEntries: ['/instance_groups'],
|
||||||
|
});
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(<ContainerGroupAdd />, {
|
||||||
|
context: { router: { history } },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handleSubmit should call the api and redirect to details page', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('ContainerGroupForm').prop('onSubmit')({
|
||||||
|
...instanceGroupCreateData,
|
||||||
|
override: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
expect(InstanceGroupsAPI.create).toHaveBeenCalledWith({
|
||||||
|
...instanceGroupCreateData,
|
||||||
|
credential: 71,
|
||||||
|
});
|
||||||
|
expect(wrapper.find('FormSubmitError').length).toBe(0);
|
||||||
|
expect(history.location.pathname).toBe(
|
||||||
|
'/instance_groups/container_group/123/details'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handleCancel should return the user back to the instance group list', async () => {
|
||||||
|
wrapper.find('Button[aria-label="Cancel"]').simulate('click');
|
||||||
|
expect(history.location.pathname).toEqual('/instance_groups');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,13 +1,88 @@
|
|||||||
import React from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
import { Card, PageSection } from '@patternfly/react-core';
|
import { Card, PageSection } from '@patternfly/react-core';
|
||||||
|
|
||||||
function ContainerGroupEdit() {
|
import { CardBody } from '../../../components/Card';
|
||||||
|
import { InstanceGroupsAPI } from '../../../api';
|
||||||
|
import ContainerGroupForm from '../shared/ContainerGroupForm';
|
||||||
|
import useRequest from '../../../util/useRequest';
|
||||||
|
import ContentError from '../../../components/ContentError';
|
||||||
|
import ContentLoading from '../../../components/ContentLoading';
|
||||||
|
|
||||||
|
function ContainerGroupEdit({ instanceGroup }) {
|
||||||
|
const history = useHistory();
|
||||||
|
const [submitError, setSubmitError] = useState(null);
|
||||||
|
const detailsIUrl = `/instance_groups/container_group/${instanceGroup.id}/details`;
|
||||||
|
|
||||||
|
const {
|
||||||
|
error: fetchError,
|
||||||
|
isLoading,
|
||||||
|
request: fetchInitialPodSpec,
|
||||||
|
result: initialPodSpec,
|
||||||
|
} = useRequest(
|
||||||
|
useCallback(async () => {
|
||||||
|
const { data } = await InstanceGroupsAPI.readOptions();
|
||||||
|
return data.actions.POST.pod_spec_override.default;
|
||||||
|
}, []),
|
||||||
|
{
|
||||||
|
initialPodSpec: {},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchInitialPodSpec();
|
||||||
|
}, [fetchInitialPodSpec]);
|
||||||
|
|
||||||
|
const handleSubmit = async values => {
|
||||||
|
try {
|
||||||
|
await InstanceGroupsAPI.update(instanceGroup.id, {
|
||||||
|
name: values.name,
|
||||||
|
credential: values.credential.id,
|
||||||
|
pod_spec_override: values.override ? values.pod_spec_override : null,
|
||||||
|
});
|
||||||
|
history.push(detailsIUrl);
|
||||||
|
} catch (error) {
|
||||||
|
setSubmitError(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleCancel = () => {
|
||||||
|
history.push(detailsIUrl);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (fetchError) {
|
||||||
|
return (
|
||||||
|
<PageSection>
|
||||||
|
<Card>
|
||||||
|
<CardBody>
|
||||||
|
<ContentError error={fetchError} />
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
</PageSection>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<PageSection>
|
||||||
|
<Card>
|
||||||
|
<CardBody>
|
||||||
|
<ContentLoading />
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
</PageSection>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageSection>
|
<CardBody>
|
||||||
<Card>
|
<ContainerGroupForm
|
||||||
<div>Edit container group</div>
|
instanceGroup={instanceGroup}
|
||||||
</Card>
|
initialPodSpec={initialPodSpec}
|
||||||
</PageSection>
|
onSubmit={handleSubmit}
|
||||||
|
submitError={submitError}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
/>
|
||||||
|
</CardBody>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,155 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { act } from 'react-dom/test-utils';
|
||||||
|
import { createMemoryHistory } from 'history';
|
||||||
|
|
||||||
|
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
|
||||||
|
import { InstanceGroupsAPI, CredentialsAPI } from '../../../api';
|
||||||
|
import ContainerGroupEdit from './ContainerGroupEdit';
|
||||||
|
|
||||||
|
jest.mock('../../../api');
|
||||||
|
|
||||||
|
const instanceGroup = {
|
||||||
|
id: 123,
|
||||||
|
type: 'instance_group',
|
||||||
|
url: '/api/v2/instance_groups/123/',
|
||||||
|
related: {
|
||||||
|
named_url: '/api/v2/instance_groups/Foo/',
|
||||||
|
jobs: '/api/v2/instance_groups/123/jobs/',
|
||||||
|
instances: '/api/v2/instance_groups/123/instances/',
|
||||||
|
credential: '/api/v2/credentials/71/',
|
||||||
|
},
|
||||||
|
name: 'Foo',
|
||||||
|
created: '2020-09-02T17:20:01.214170Z',
|
||||||
|
modified: '2020-09-02T17:20:01.214236Z',
|
||||||
|
capacity: 0,
|
||||||
|
committed_capacity: 0,
|
||||||
|
consumed_capacity: 0,
|
||||||
|
percent_capacity_remaining: 0.0,
|
||||||
|
jobs_running: 0,
|
||||||
|
jobs_total: 0,
|
||||||
|
instances: 0,
|
||||||
|
controller: null,
|
||||||
|
is_controller: false,
|
||||||
|
is_isolated: false,
|
||||||
|
is_containerized: true,
|
||||||
|
credential: 71,
|
||||||
|
policy_instance_percentage: 0,
|
||||||
|
policy_instance_minimum: 0,
|
||||||
|
policy_instance_list: [],
|
||||||
|
pod_spec_override: '',
|
||||||
|
summary_fields: {
|
||||||
|
credential: {
|
||||||
|
id: 71,
|
||||||
|
name: 'CG',
|
||||||
|
description: 'a',
|
||||||
|
kind: 'kubernetes_bearer_token',
|
||||||
|
cloud: false,
|
||||||
|
kubernetes: true,
|
||||||
|
credential_type_id: 17,
|
||||||
|
},
|
||||||
|
user_capabilities: {
|
||||||
|
edit: true,
|
||||||
|
delete: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const updatedInstanceGroup = {
|
||||||
|
name: 'Bar',
|
||||||
|
credential: { id: 12, name: 'CGX' },
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialPodSpec = {
|
||||||
|
default: {
|
||||||
|
apiVersion: 'v1',
|
||||||
|
kind: 'Pod',
|
||||||
|
metadata: {
|
||||||
|
namespace: 'default',
|
||||||
|
},
|
||||||
|
spec: {
|
||||||
|
containers: [
|
||||||
|
{
|
||||||
|
image: 'ansible/ansible-runner',
|
||||||
|
tty: true,
|
||||||
|
stdin: true,
|
||||||
|
imagePullPolicy: 'Always',
|
||||||
|
args: ['sleep', 'infinity'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
InstanceGroupsAPI.readOptions.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
results: initialPodSpec,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
CredentialsAPI.read.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
id: 71,
|
||||||
|
name: 'Test',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('<ContainerGroupEdit/>', () => {
|
||||||
|
let wrapper;
|
||||||
|
let history;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
history = createMemoryHistory({ initialEntries: ['/instance_groups'] });
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<ContainerGroupEdit instanceGroup={instanceGroup} />,
|
||||||
|
{
|
||||||
|
context: { router: { history } },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('initially renders successfully', async () => {
|
||||||
|
expect(wrapper.find('ContainerGroupEdit').length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('called InstanceGroupsAPI.readOptions', async () => {
|
||||||
|
expect(InstanceGroupsAPI.readOptions).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handleCancel returns the user to container group detail', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('Button[aria-label="Cancel"]').simulate('click');
|
||||||
|
});
|
||||||
|
expect(history.location.pathname).toEqual(
|
||||||
|
'/instance_groups/container_group/123/details'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handleSubmit should call the api and redirect to details page', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('ContainerGroupForm').prop('onSubmit')({
|
||||||
|
...updatedInstanceGroup,
|
||||||
|
override: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
expect(InstanceGroupsAPI.update).toHaveBeenCalledWith(123, {
|
||||||
|
...updatedInstanceGroup,
|
||||||
|
credential: 12,
|
||||||
|
pod_spec_override: null,
|
||||||
|
});
|
||||||
|
expect(history.location.pathname).toEqual(
|
||||||
|
'/instance_groups/container_group/123/details'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { func, shape } from 'prop-types';
|
||||||
|
import { Formik, useField } from 'formik';
|
||||||
|
import { withI18n } from '@lingui/react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import { Form, FormGroup } from '@patternfly/react-core';
|
||||||
|
import { jsonToYaml } from '../../../util/yaml';
|
||||||
|
|
||||||
|
import FormField, {
|
||||||
|
FormSubmitError,
|
||||||
|
CheckboxField,
|
||||||
|
} from '../../../components/FormField';
|
||||||
|
import FormActionGroup from '../../../components/FormActionGroup';
|
||||||
|
import { required } from '../../../util/validators';
|
||||||
|
import {
|
||||||
|
FormColumnLayout,
|
||||||
|
FormFullWidthLayout,
|
||||||
|
FormCheckboxLayout,
|
||||||
|
SubFormLayout,
|
||||||
|
} from '../../../components/FormLayout';
|
||||||
|
import CredentialLookup from '../../../components/Lookup/CredentialLookup';
|
||||||
|
import { VariablesField } from '../../../components/CodeMirrorInput';
|
||||||
|
|
||||||
|
function ContainerGroupFormFields({ i18n }) {
|
||||||
|
const [credentialField, credentialMeta, credentialHelpers] = useField(
|
||||||
|
'credential'
|
||||||
|
);
|
||||||
|
const [overrideField] = useField('override');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FormField
|
||||||
|
name="name"
|
||||||
|
id="container-group-name"
|
||||||
|
label={i18n._(t`Name`)}
|
||||||
|
type="text"
|
||||||
|
validate={required(null, i18n)}
|
||||||
|
isRequired
|
||||||
|
/>
|
||||||
|
<CredentialLookup
|
||||||
|
label={i18n._(t`Credential`)}
|
||||||
|
credentialTypeKind="kubernetes"
|
||||||
|
helperTextInvalid={credentialMeta.error}
|
||||||
|
isValid={!credentialMeta.touched || !credentialMeta.error}
|
||||||
|
onBlur={() => credentialHelpers.setTouched()}
|
||||||
|
onChange={value => {
|
||||||
|
credentialHelpers.setValue(value);
|
||||||
|
}}
|
||||||
|
value={credentialField.value}
|
||||||
|
required
|
||||||
|
tooltip={i18n._(
|
||||||
|
t`Credential to authenticate with Kubernetes or OpenShift. Must be of type "Kubernetes/OpenShift API Bearer Token”.`
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormGroup
|
||||||
|
fieldId="container-groups-option-checkbox"
|
||||||
|
label={i18n._(t`Options`)}
|
||||||
|
>
|
||||||
|
<FormCheckboxLayout>
|
||||||
|
<CheckboxField
|
||||||
|
name="override"
|
||||||
|
aria-label={i18n._(t`Customize pod specification`)}
|
||||||
|
label={i18n._(t`Customize pod specification`)}
|
||||||
|
id="container-groups-override-pod-specification"
|
||||||
|
/>
|
||||||
|
</FormCheckboxLayout>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
{overrideField.value && (
|
||||||
|
<SubFormLayout>
|
||||||
|
<FormFullWidthLayout>
|
||||||
|
<VariablesField
|
||||||
|
tooltip={i18n._(
|
||||||
|
t`Field for passing a custom Kubernetes or OpenShift Pod specification.`
|
||||||
|
)}
|
||||||
|
id="custom-pod-spec"
|
||||||
|
name="pod_spec_override"
|
||||||
|
label={i18n._(t`Custom pod spec`)}
|
||||||
|
/>
|
||||||
|
</FormFullWidthLayout>
|
||||||
|
</SubFormLayout>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ContainerGroupForm({
|
||||||
|
initialPodSpec,
|
||||||
|
instanceGroup,
|
||||||
|
onSubmit,
|
||||||
|
onCancel,
|
||||||
|
submitError,
|
||||||
|
...rest
|
||||||
|
}) {
|
||||||
|
const isCheckboxChecked = Boolean(instanceGroup?.pod_spec_override) || false;
|
||||||
|
|
||||||
|
const initialValues = {
|
||||||
|
name: instanceGroup?.name || '',
|
||||||
|
credential: instanceGroup?.summary_fields?.credential,
|
||||||
|
pod_spec_override: isCheckboxChecked
|
||||||
|
? instanceGroup?.pod_spec_override
|
||||||
|
: jsonToYaml(JSON.stringify(initialPodSpec)),
|
||||||
|
override: isCheckboxChecked,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Formik
|
||||||
|
initialValues={initialValues}
|
||||||
|
onSubmit={values => {
|
||||||
|
onSubmit(values);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{formik => (
|
||||||
|
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
|
||||||
|
<FormColumnLayout>
|
||||||
|
<ContainerGroupFormFields {...rest} />
|
||||||
|
{submitError && <FormSubmitError error={submitError} />}
|
||||||
|
<FormActionGroup
|
||||||
|
onCancel={onCancel}
|
||||||
|
onSubmit={formik.handleSubmit}
|
||||||
|
/>
|
||||||
|
</FormColumnLayout>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ContainerGroupForm.propTypes = {
|
||||||
|
instanceGroup: shape({}),
|
||||||
|
onCancel: func.isRequired,
|
||||||
|
onSubmit: func.isRequired,
|
||||||
|
submitError: shape({}),
|
||||||
|
initialPodSpec: shape({}),
|
||||||
|
};
|
||||||
|
|
||||||
|
ContainerGroupForm.defaultProps = {
|
||||||
|
instanceGroup: {},
|
||||||
|
submitError: null,
|
||||||
|
initialPodSpec: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withI18n()(ContainerGroupForm);
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { act } from 'react-dom/test-utils';
|
||||||
|
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
|
||||||
|
|
||||||
|
import ContainerGroupForm from './ContainerGroupForm';
|
||||||
|
|
||||||
|
jest.mock('../../../api');
|
||||||
|
|
||||||
|
const instanceGroup = {
|
||||||
|
id: 7,
|
||||||
|
type: 'instance_group',
|
||||||
|
url: '/api/v2/instance_groups/7/',
|
||||||
|
related: {
|
||||||
|
jobs: '/api/v2/instance_groups/7/jobs/',
|
||||||
|
instances: '/api/v2/instance_groups/7/instances/',
|
||||||
|
},
|
||||||
|
name: 'Bar',
|
||||||
|
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 initialPodSpec = {
|
||||||
|
default: {
|
||||||
|
apiVersion: 'v1',
|
||||||
|
kind: 'Pod',
|
||||||
|
metadata: {
|
||||||
|
namespace: 'default',
|
||||||
|
},
|
||||||
|
spec: {
|
||||||
|
containers: [
|
||||||
|
{
|
||||||
|
image: 'ansible/ansible-runner',
|
||||||
|
tty: true,
|
||||||
|
stdin: true,
|
||||||
|
imagePullPolicy: 'Always',
|
||||||
|
args: ['sleep', 'infinity'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('<ContainerGroupForm/>', () => {
|
||||||
|
let wrapper;
|
||||||
|
let onCancel;
|
||||||
|
let onSubmit;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
onCancel = jest.fn();
|
||||||
|
onSubmit = jest.fn();
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<ContainerGroupForm
|
||||||
|
onCancel={onCancel}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
instanceGroup={instanceGroup}
|
||||||
|
initialPodSpec={initialPodSpec}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Initially renders successfully', () => {
|
||||||
|
expect(wrapper.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should display form fields properly', () => {
|
||||||
|
expect(wrapper.find('FormGroup[label="Name"]').length).toBe(1);
|
||||||
|
expect(wrapper.find('VariablesField[label="Custom pod spec"]').length).toBe(
|
||||||
|
0
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
wrapper
|
||||||
|
.find('Checkbox[aria-label="Customize pod specification"]')
|
||||||
|
.prop('isChecked')
|
||||||
|
).toBeFalsy();
|
||||||
|
expect(wrapper.find('CredentialLookup').prop('value')).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should update form values', () => {
|
||||||
|
act(() => {
|
||||||
|
wrapper.find('CredentialLookup').invoke('onBlur')();
|
||||||
|
wrapper.find('CredentialLookup').invoke('onChange')({
|
||||||
|
id: 99,
|
||||||
|
name: 'credential',
|
||||||
|
});
|
||||||
|
wrapper.find('TextInputBase#container-group-name').simulate('change', {
|
||||||
|
target: { value: 'new Foo', name: 'name' },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
expect(wrapper.find('CredentialLookup').prop('value')).toEqual({
|
||||||
|
id: 99,
|
||||||
|
name: 'credential',
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
wrapper.find('TextInputBase#container-group-name').prop('value')
|
||||||
|
).toEqual('new Foo');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should call onSubmit when form submitted', async () => {
|
||||||
|
expect(onSubmit).not.toHaveBeenCalled();
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('button[aria-label="Save"]').simulate('click');
|
||||||
|
});
|
||||||
|
expect(onSubmit).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should call handleCancel when Cancel button is clicked', async () => {
|
||||||
|
expect(onCancel).not.toHaveBeenCalled();
|
||||||
|
wrapper.find('button[aria-label="Cancel"]').invoke('onClick')();
|
||||||
|
expect(onCancel).toBeCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user