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

View File

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