Merge pull request #6318 from AlexSCorey/6100-ConvertWFJTandJTtoHooks

Moves JT Form to using react hooks and custom hooks

Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
softwarefactory-project-zuul[bot]
2020-03-18 15:58:13 +00:00
committed by GitHub
2 changed files with 512 additions and 544 deletions

View File

@@ -1,4 +1,4 @@
import React, { Component } from 'react';
import React, { useState, useEffect, useCallback } from 'react';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router-dom';
import { withI18n } from '@lingui/react';
@@ -15,6 +15,8 @@ import ContentError from '@components/ContentError';
import ContentLoading from '@components/ContentLoading';
import AnsibleSelect from '@components/AnsibleSelect';
import { TagMultiSelect } from '@components/MultiSelect';
import useRequest from '@util/useRequest';
import FormActionGroup from '@components/FormActionGroup';
import FormField, {
CheckboxField,
@@ -40,91 +42,64 @@ import { JobTemplatesAPI, ProjectsAPI } from '@api';
import LabelSelect from './LabelSelect';
import PlaybookSelect from './PlaybookSelect';
class JobTemplateForm extends Component {
static propTypes = {
template: JobTemplate,
handleCancel: PropTypes.func.isRequired,
handleSubmit: PropTypes.func.isRequired,
submitError: PropTypes.shape({}),
};
static defaultProps = {
template: {
name: '',
description: '',
job_type: 'run',
inventory: undefined,
project: undefined,
playbook: '',
summary_fields: {
inventory: null,
labels: { results: [] },
project: null,
credentials: [],
},
isNew: true,
},
submitError: null,
};
constructor(props) {
super(props);
this.state = {
hasContentLoading: true,
contentError: false,
project: props.template.summary_fields.project,
inventory: props.template.summary_fields.inventory,
allowCallbacks: !!props.template.host_config_key,
};
this.handleProjectValidation = this.handleProjectValidation.bind(this);
this.loadRelatedInstanceGroups = this.loadRelatedInstanceGroups.bind(this);
this.handleProjectUpdate = this.handleProjectUpdate.bind(this);
this.setContentError = this.setContentError.bind(this);
this.fetchProject = this.fetchProject.bind(this);
}
componentDidMount() {
const { validateField } = this.props;
this.setState({ contentError: null, hasContentLoading: true });
// TODO: determine when LabelSelect has finished loading labels
Promise.all([this.loadRelatedInstanceGroups(), this.fetchProject()]).then(
() => {
this.setState({ hasContentLoading: false });
validateField('project');
}
function JobTemplateForm({
template,
validateField,
handleCancel,
handleSubmit,
handleBlur,
setFieldValue,
submitError,
i18n,
touched,
}) {
const [contentError, setContentError] = useState(false);
const [project, setProject] = useState(null);
const [inventory, setInventory] = useState(
template?.summary_fields?.inventory
);
const [allowCallbacks, setAllowCallbacks] = useState(
Boolean(template?.host_config_key)
);
}
async fetchProject() {
const { project } = this.state;
if (project && project.id) {
try {
const { data: projectData } = await ProjectsAPI.readDetail(project.id);
this.setState({ project: projectData });
} catch (err) {
this.setState({ contentError: err });
const {
request: fetchProject,
error: projectContentError,
contentLoading: hasProjectLoading,
} = useRequest(
useCallback(async () => {
let projectData;
if (template?.project) {
projectData = await ProjectsAPI.readDetail(template?.project);
validateField('project');
setProject(projectData.data);
}
}
}
async loadRelatedInstanceGroups() {
const { setFieldValue, template } = this.props;
if (!template.id) {
}, [template, validateField])
);
const {
request: loadRelatedInstanceGroups,
error: instanceGroupError,
contentLoading: instanceGroupLoading,
} = useRequest(
useCallback(async () => {
if (!template?.id) {
return;
}
try {
const { data } = await JobTemplatesAPI.readInstanceGroups(template.id);
setFieldValue('initialInstanceGroups', data.results);
setFieldValue('instanceGroups', [...data.results]);
} catch (err) {
this.setState({ contentError: err });
}
}
}, [setFieldValue, template])
);
handleProjectValidation() {
const { i18n, touched } = this.props;
const { project } = this.state;
return () => {
useEffect(() => {
fetchProject();
}, [fetchProject]);
useEffect(() => {
loadRelatedInstanceGroups();
}, [loadRelatedInstanceGroups]);
const handleProjectValidation = () => {
if (!project && touched.project) {
return i18n._(t`Select a value for this field`);
}
@@ -133,37 +108,13 @@ class JobTemplateForm extends Component {
}
return undefined;
};
}
handleProjectUpdate(project) {
const { setFieldValue } = this.props;
setFieldValue('project', project.id);
const handleProjectUpdate = newProject => {
setProject(newProject);
setFieldValue('project', newProject.id);
setFieldValue('playbook', 0);
setFieldValue('scm_branch', '');
this.setState({ project });
}
setContentError(contentError) {
this.setState({ contentError });
}
render() {
const {
contentError,
hasContentLoading,
inventory,
project,
allowCallbacks,
} = this.state;
const {
handleCancel,
handleSubmit,
handleBlur,
setFieldValue,
template,
submitError,
i18n,
} = this.props;
};
const jobTypeOptions = [
{
@@ -188,17 +139,17 @@ class JobTemplateForm extends Component {
{ value: '4', key: '4', label: i18n._(t`4 (Connection Debug)`) },
];
let callbackUrl;
if (template && template.related) {
if (template?.related) {
const { origin } = document.location;
const path = template.related.callback || `${template.url}callback`;
callbackUrl = `${origin}${path}`;
}
if (hasContentLoading) {
if (instanceGroupLoading || hasProjectLoading) {
return <ContentLoading />;
}
if (contentError) {
if (instanceGroupError || projectContentError) {
return <ContentError error={contentError} />;
}
@@ -274,7 +225,7 @@ class JobTemplateForm extends Component {
inventory: value.id,
organizationId: value.organization,
});
this.setState({ inventory: value });
setInventory(value);
}}
required
touched={form.touched.inventory}
@@ -294,7 +245,7 @@ class JobTemplateForm extends Component {
)}
</Field>
</FieldWithPrompt>
<Field name="project" validate={this.handleProjectValidation()}>
<Field name="project" validate={() => handleProjectValidation()}>
{({ form }) => (
<ProjectLookup
value={project}
@@ -303,7 +254,7 @@ class JobTemplateForm extends Component {
you want this job to execute.`)}
isValid={!form.touched.project || !form.errors.project}
helperTextInvalid={form.errors.project}
onChange={this.handleProjectUpdate}
onChange={handleProjectUpdate}
required
/>
)}
@@ -356,7 +307,7 @@ class JobTemplateForm extends Component {
form={form}
field={field}
onBlur={() => form.setFieldTouched('playbook')}
onError={this.setContentError}
onError={setContentError}
/>
</FormGroup>
);
@@ -382,7 +333,7 @@ class JobTemplateForm extends Component {
onChange={newCredentials =>
setFieldValue('credentials', newCredentials)
}
onError={this.setContentError}
onError={setContentError}
/>
);
}}
@@ -399,7 +350,7 @@ class JobTemplateForm extends Component {
<LabelSelect
value={field.value}
onChange={labels => setFieldValue('labels', labels)}
onError={this.setContentError}
onError={setContentError}
/>
</FormGroup>
)}
@@ -446,9 +397,7 @@ class JobTemplateForm extends Component {
<TextInput
id="template-limit"
{...field}
isValid={
!form.touched.job_type || !form.errors.job_type
}
isValid={!form.touched.job_type || !form.errors.job_type}
onChange={(value, event) => {
field.onChange(event);
}}
@@ -545,9 +494,7 @@ class JobTemplateForm extends Component {
{({ field, form }) => (
<TagMultiSelect
value={field.value}
onChange={value =>
form.setFieldValue(field.name, value)
}
onChange={value => form.setFieldValue(field.name, value)}
/>
)}
</Field>
@@ -567,9 +514,7 @@ class JobTemplateForm extends Component {
{({ field, form }) => (
<TagMultiSelect
value={field.value}
onChange={value =>
form.setFieldValue(field.name, value)
}
onChange={value => form.setFieldValue(field.name, value)}
/>
)}
</Field>
@@ -603,7 +548,7 @@ class JobTemplateForm extends Component {
id="option-callbacks"
isChecked={allowCallbacks}
onChange={checked => {
this.setState({ allowCallbacks: checked });
setAllowCallbacks(checked);
}}
/>
<CheckboxField
@@ -653,11 +598,33 @@ class JobTemplateForm extends Component {
</Form>
);
}
}
JobTemplateForm.propTypes = {
template: JobTemplate,
handleCancel: PropTypes.func.isRequired,
handleSubmit: PropTypes.func.isRequired,
submitError: PropTypes.shape({}),
};
JobTemplateForm.defaultProps = {
template: {
name: '',
description: '',
job_type: 'run',
inventory: undefined,
project: undefined,
playbook: '',
summary_fields: {
inventory: null,
labels: { results: [] },
project: null,
credentials: [],
},
isNew: true,
},
submitError: null,
};
const FormikApp = withFormik({
mapPropsToValues(props) {
const { template = {} } = props;
mapPropsToValues({ template = {} }) {
const {
summary_fields = {
labels: { results: [] },

View File

@@ -157,6 +157,7 @@ describe('<JobTemplateForm />', () => {
name: 'inventory',
});
});
wrapper.update();
await act(async () => {
wrapper.find('input#template-scm-branch').simulate('change', {