diff options
author | softwarefactory-project-zuul[bot] <33884098+softwarefactory-project-zuul[bot]@users.noreply.github.com> | 2020-03-18 16:58:13 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-03-18 16:58:13 +0100 |
commit | 3219b9b4ac3d81af8eef47e8dd276f5bc1cdccff (patch) | |
tree | 18d5b5734e7dde2cb72ee6ade73ebf9bb29262cb | |
parent | Merge pull request #6319 from squidboylan/collection_test_refactor (diff) | |
parent | Moves JT Form to using react hooks and custom hooks (diff) | |
download | awx-3219b9b4ac3d81af8eef47e8dd276f5bc1cdccff.tar.xz awx-3219b9b4ac3d81af8eef47e8dd276f5bc1cdccff.zip |
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
-rw-r--r-- | awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx | 1037 | ||||
-rw-r--r-- | awx/ui_next/src/screens/Template/shared/JobTemplateForm.test.jsx | 1 |
2 files changed, 503 insertions, 535 deletions
diff --git a/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx b/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx index 19ca4d3467..0640dab906 100644 --- a/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx +++ b/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx @@ -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,287 +42,362 @@ 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, - }; +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) + ); - 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 }); + 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 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 }); + }, [template, validateField]) + ); + const { + request: loadRelatedInstanceGroups, + error: instanceGroupError, + contentLoading: instanceGroupLoading, + } = useRequest( + useCallback(async () => { + if (!template?.id) { + return; } - } - } - - async loadRelatedInstanceGroups() { - const { setFieldValue, template } = this.props; - 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 () => { - if (!project && touched.project) { - return i18n._(t`Select a value for this field`); - } - if (project && project.status === 'never updated') { - return i18n._(t`This project needs to be updated`); - } - return undefined; - }; - } + useEffect(() => { + fetchProject(); + }, [fetchProject]); + + useEffect(() => { + loadRelatedInstanceGroups(); + }, [loadRelatedInstanceGroups]); + + const handleProjectValidation = () => { + if (!project && touched.project) { + return i18n._(t`Select a value for this field`); + } + if (project && project.status === 'never updated') { + return i18n._(t`This project needs to be updated`); + } + 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 }); + const jobTypeOptions = [ + { + value: '', + key: '', + label: i18n._(t`Choose a job type`), + isDisabled: true, + }, + { value: 'run', key: 'run', label: i18n._(t`Run`), isDisabled: false }, + { + value: 'check', + key: 'check', + label: i18n._(t`Check`), + isDisabled: false, + }, + ]; + const verbosityOptions = [ + { value: '0', key: '0', label: i18n._(t`0 (Normal)`) }, + { value: '1', key: '1', label: i18n._(t`1 (Verbose)`) }, + { value: '2', key: '2', label: i18n._(t`2 (More Verbose)`) }, + { value: '3', key: '3', label: i18n._(t`3 (Debug)`) }, + { value: '4', key: '4', label: i18n._(t`4 (Connection Debug)`) }, + ]; + let callbackUrl; + if (template?.related) { + const { origin } = document.location; + const path = template.related.callback || `${template.url}callback`; + callbackUrl = `${origin}${path}`; } - render() { - const { - contentError, - hasContentLoading, - inventory, - project, - allowCallbacks, - } = this.state; - const { - handleCancel, - handleSubmit, - handleBlur, - setFieldValue, - template, - submitError, - i18n, - } = this.props; - - const jobTypeOptions = [ - { - value: '', - key: '', - label: i18n._(t`Choose a job type`), - isDisabled: true, - }, - { value: 'run', key: 'run', label: i18n._(t`Run`), isDisabled: false }, - { - value: 'check', - key: 'check', - label: i18n._(t`Check`), - isDisabled: false, - }, - ]; - const verbosityOptions = [ - { value: '0', key: '0', label: i18n._(t`0 (Normal)`) }, - { value: '1', key: '1', label: i18n._(t`1 (Verbose)`) }, - { value: '2', key: '2', label: i18n._(t`2 (More Verbose)`) }, - { value: '3', key: '3', label: i18n._(t`3 (Debug)`) }, - { value: '4', key: '4', label: i18n._(t`4 (Connection Debug)`) }, - ]; - let callbackUrl; - if (template && template.related) { - const { origin } = document.location; - const path = template.related.callback || `${template.url}callback`; - callbackUrl = `${origin}${path}`; - } - - if (hasContentLoading) { - return <ContentLoading />; - } + if (instanceGroupLoading || hasProjectLoading) { + return <ContentLoading />; + } - if (contentError) { - return <ContentError error={contentError} />; - } + if (instanceGroupError || projectContentError) { + return <ContentError error={contentError} />; + } - return ( - <Form autoComplete="off" onSubmit={handleSubmit}> - <FormColumnLayout> - <FormField - id="template-name" - name="name" - type="text" - label={i18n._(t`Name`)} - validate={required(null, i18n)} - isRequired - /> - <FormField - id="template-description" - name="description" - type="text" - label={i18n._(t`Description`)} - /> - <FieldWithPrompt - fieldId="template-job-type" - isRequired - label={i18n._(t`Job Type`)} - promptId="template-ask-job-type-on-launch" - promptName="ask_job_type_on_launch" - tooltip={i18n._(t`For job templates, select run to execute + return ( + <Form autoComplete="off" onSubmit={handleSubmit}> + <FormColumnLayout> + <FormField + id="template-name" + name="name" + type="text" + label={i18n._(t`Name`)} + validate={required(null, i18n)} + isRequired + /> + <FormField + id="template-description" + name="description" + type="text" + label={i18n._(t`Description`)} + /> + <FieldWithPrompt + fieldId="template-job-type" + isRequired + label={i18n._(t`Job Type`)} + promptId="template-ask-job-type-on-launch" + promptName="ask_job_type_on_launch" + tooltip={i18n._(t`For job templates, select run to execute the playbook. Select check to only check playbook syntax, test environment setup, and report problems without executing the playbook.`)} + > + <Field + name="job_type" + validate={required(null, i18n)} + onBlur={handleBlur} > - <Field - name="job_type" - validate={required(null, i18n)} - onBlur={handleBlur} - > - {({ form, field }) => { - const isValid = !form.touched.job_type || !form.errors.job_type; + {({ form, field }) => { + const isValid = !form.touched.job_type || !form.errors.job_type; + return ( + <AnsibleSelect + isValid={isValid} + id="template-job-type" + data={jobTypeOptions} + {...field} + onChange={(event, value) => { + form.setFieldValue('job_type', value); + }} + /> + ); + }} + </Field> + </FieldWithPrompt> + <FieldWithPrompt + fieldId="template-inventory" + isRequired + label={i18n._(t`Inventory`)} + promptId="template-ask-inventory-on-launch" + promptName="ask_inventory_on_launch" + tooltip={i18n._(t`Select the inventory containing the hosts + you want this job to manage.`)} + > + <Field name="inventory"> + {({ form }) => ( + <> + <InventoryLookup + value={inventory} + onBlur={() => { + form.setFieldTouched('inventory'); + }} + onChange={value => { + form.setValues({ + ...form.values, + inventory: value.id, + organizationId: value.organization, + }); + setInventory(value); + }} + required + touched={form.touched.inventory} + error={form.errors.inventory} + /> + {(form.touched.inventory || + form.touched.ask_inventory_on_launch) && + form.errors.inventory && ( + <div + className="pf-c-form__helper-text pf-m-error" + aria-live="polite" + > + {form.errors.inventory} + </div> + )} + </> + )} + </Field> + </FieldWithPrompt> + <Field name="project" validate={() => handleProjectValidation()}> + {({ form }) => ( + <ProjectLookup + value={project} + onBlur={() => form.setFieldTouched('project')} + tooltip={i18n._(t`Select the project containing the playbook + you want this job to execute.`)} + isValid={!form.touched.project || !form.errors.project} + helperTextInvalid={form.errors.project} + onChange={handleProjectUpdate} + required + /> + )} + </Field> + {project && project.allow_override && ( + <FieldWithPrompt + fieldId="template-scm-branch" + label={i18n._(t`SCM Branch`)} + promptId="template-ask-scm-branch-on-launch" + promptName="ask_scm_branch_on_launch" + > + <Field name="scm_branch"> + {({ field }) => { return ( - <AnsibleSelect - isValid={isValid} - id="template-job-type" - data={jobTypeOptions} + <TextInput + id="template-scm-branch" {...field} - onChange={(event, value) => { - form.setFieldValue('job_type', value); + onChange={(value, event) => { + field.onChange(event); }} /> ); }} </Field> </FieldWithPrompt> + )} + <Field + name="playbook" + validate={required(i18n._(t`Select a value for this field`), i18n)} + onBlur={handleBlur} + > + {({ field, form }) => { + const isValid = !form.touched.playbook || !form.errors.playbook; + return ( + <FormGroup + fieldId="template-playbook" + helperTextInvalid={form.errors.playbook} + isRequired + isValid={isValid} + label={i18n._(t`Playbook`)} + > + <FieldTooltip + content={i18n._( + t`Select the playbook to be executed by this job.` + )} + /> + <PlaybookSelect + projectId={form.values.project} + isValid={isValid} + form={form} + field={field} + onBlur={() => form.setFieldTouched('playbook')} + onError={setContentError} + /> + </FormGroup> + ); + }} + </Field> + <FormFullWidthLayout> <FieldWithPrompt - fieldId="template-inventory" - isRequired - label={i18n._(t`Inventory`)} - promptId="template-ask-inventory-on-launch" - promptName="ask_inventory_on_launch" - tooltip={i18n._(t`Select the inventory containing the hosts - you want this job to manage.`)} + fieldId="template-credentials" + label={i18n._(t`Credentials`)} + promptId="template-ask-credential-on-launch" + promptName="ask_credential_on_launch" + tooltip={i18n._(t`Select credentials that allow Tower to access the nodes this job will be ran + against. You can only select one credential of each type. For machine credentials (SSH), + checking "Prompt on launch" without selecting credentials will require you to select a machine + credential at run time. If you select credentials and check "Prompt on launch", the selected + credential(s) become the defaults that can be updated at run time.`)} > - <Field name="inventory"> - {({ form }) => ( - <> - <InventoryLookup - value={inventory} - onBlur={() => { - form.setFieldTouched('inventory'); - }} - onChange={value => { - form.setValues({ - ...form.values, - inventory: value.id, - organizationId: value.organization, - }); - this.setState({ inventory: value }); - }} - required - touched={form.touched.inventory} - error={form.errors.inventory} + <Field name="credentials" fieldId="template-credentials"> + {({ field }) => { + return ( + <MultiCredentialsLookup + value={field.value} + onChange={newCredentials => + setFieldValue('credentials', newCredentials) + } + onError={setContentError} /> - {(form.touched.inventory || - form.touched.ask_inventory_on_launch) && - form.errors.inventory && ( - <div - className="pf-c-form__helper-text pf-m-error" - aria-live="polite" - > - {form.errors.inventory} - </div> - )} - </> - )} + ); + }} </Field> </FieldWithPrompt> - <Field name="project" validate={this.handleProjectValidation()}> - {({ form }) => ( - <ProjectLookup - value={project} - onBlur={() => form.setFieldTouched('project')} - tooltip={i18n._(t`Select the project containing the playbook - you want this job to execute.`)} - isValid={!form.touched.project || !form.errors.project} - helperTextInvalid={form.errors.project} - onChange={this.handleProjectUpdate} - required - /> + <Field name="labels"> + {({ field }) => ( + <FormGroup label={i18n._(t`Labels`)} fieldId="template-labels"> + <FieldTooltip + content={i18n._(t`Optional labels that describe this job template, + such as 'dev' or 'test'. Labels can be used to group and filter + job templates and completed jobs.`)} + /> + <LabelSelect + value={field.value} + onChange={labels => setFieldValue('labels', labels)} + onError={setContentError} + /> + </FormGroup> )} </Field> - {project && project.allow_override && ( + <VariablesField + id="template-variables" + name="extra_vars" + label={i18n._(t`Variables`)} + promptId="template-ask-variables-on-launch" + /> + <FormColumnLayout> + <FormField + id="template-forks" + name="forks" + type="number" + min="0" + label={i18n._(t`Forks`)} + tooltip={ + <span> + {i18n._(t`The number of parallel or simultaneous + processes to use while executing the playbook. An empty value, + or a value less than 1 will use the Ansible default which is + usually 5. The default number of forks can be overwritten + with a change to`)}{' '} + <code>ansible.cfg</code>.{' '} + {i18n._(t`Refer to the Ansible documentation for details + about the configuration file.`)} + </span> + } + /> <FieldWithPrompt - fieldId="template-scm-branch" - label={i18n._(t`SCM Branch`)} - promptId="template-ask-scm-branch-on-launch" - promptName="ask_scm_branch_on_launch" + fieldId="template-limit" + label={i18n._(t`Limit`)} + promptId="template-ask-limit-on-launch" + promptName="ask_limit_on_launch" + tooltip={i18n._(t`Provide a host pattern to further constrain + the list of hosts that will be managed or affected by the + playbook. Multiple patterns are allowed. Refer to Ansible + documentation for more information and examples on patterns.`)} > - <Field name="scm_branch"> - {({ field }) => { + <Field name="limit"> + {({ form, field }) => { return ( <TextInput - id="template-scm-branch" + id="template-limit" {...field} + isValid={!form.touched.job_type || !form.errors.job_type} onChange={(value, event) => { field.onChange(event); }} @@ -329,335 +406,225 @@ class JobTemplateForm extends Component { }} </Field> </FieldWithPrompt> - )} - <Field - name="playbook" - validate={required(i18n._(t`Select a value for this field`), i18n)} - onBlur={handleBlur} - > - {({ field, form }) => { - const isValid = !form.touched.playbook || !form.errors.playbook; - return ( - <FormGroup - fieldId="template-playbook" - helperTextInvalid={form.errors.playbook} - isRequired - isValid={isValid} - label={i18n._(t`Playbook`)} - > - <FieldTooltip - content={i18n._( - t`Select the playbook to be executed by this job.` - )} - /> - <PlaybookSelect - projectId={form.values.project} - isValid={isValid} - form={form} - field={field} - onBlur={() => form.setFieldTouched('playbook')} - onError={this.setContentError} + <FieldWithPrompt + fieldId="template-verbosity" + label={i18n._(t`Verbosity`)} + promptId="template-ask-verbosity-on-launch" + promptName="ask_verbosity_on_launch" + tooltip={i18n._(t`Control the level of output ansible will + produce as the playbook executes.`)} + > + <Field name="verbosity"> + {({ field }) => ( + <AnsibleSelect + id="template-verbosity" + data={verbosityOptions} + {...field} /> - </FormGroup> - ); - }} - </Field> - <FormFullWidthLayout> + )} + </Field> + </FieldWithPrompt> + <FormField + id="template-job-slicing" + name="job_slice_count" + type="number" + min="1" + label={i18n._(t`Job Slicing`)} + tooltip={i18n._(t`Divide the work done by this job template + into the specified number of job slices, each running the + same tasks against a portion of the inventory.`)} + /> + <FormField + id="template-timeout" + name="timeout" + type="number" + min="0" + label={i18n._(t`Timeout`)} + tooltip={i18n._(t`The amount of time (in seconds) to run + before the task is canceled. Defaults to 0 for no job + timeout.`)} + /> <FieldWithPrompt - fieldId="template-credentials" - label={i18n._(t`Credentials`)} - promptId="template-ask-credential-on-launch" - promptName="ask_credential_on_launch" - tooltip={i18n._(t`Select credentials that allow Tower to access the nodes this job will be ran - against. You can only select one credential of each type. For machine credentials (SSH), - checking "Prompt on launch" without selecting credentials will require you to select a machine - credential at run time. If you select credentials and check "Prompt on launch", the selected - credential(s) become the defaults that can be updated at run time.`)} + fieldId="template-diff-mode" + label={i18n._(t`Show Changes`)} + promptId="template-ask-diff-mode-on-launch" + promptName="ask_diff_mode_on_launch" + tooltip={i18n._(t`If enabled, show the changes made by + Ansible tasks, where supported. This is equivalent + to Ansible’s --diff mode.`)} > - <Field name="credentials" fieldId="template-credentials"> - {({ field }) => { + <Field name="diff_mode"> + {({ form, field }) => { return ( - <MultiCredentialsLookup - value={field.value} - onChange={newCredentials => - setFieldValue('credentials', newCredentials) + <Switch + id="template-show-changes" + label={field.value ? i18n._(t`On`) : i18n._(t`Off`)} + isChecked={field.value} + onChange={checked => + form.setFieldValue(field.name, checked) } - onError={this.setContentError} /> ); }} </Field> </FieldWithPrompt> - <Field name="labels"> - {({ field }) => ( - <FormGroup label={i18n._(t`Labels`)} fieldId="template-labels"> - <FieldTooltip - content={i18n._(t`Optional labels that describe this job template, - such as 'dev' or 'test'. Labels can be used to group and filter - job templates and completed jobs.`)} - /> - <LabelSelect + <FormFullWidthLayout> + <Field name="instanceGroups"> + {({ field, form }) => ( + <InstanceGroupsLookup value={field.value} - onChange={labels => setFieldValue('labels', labels)} - onError={this.setContentError} + onChange={value => form.setFieldValue(field.name, value)} + tooltip={i18n._(t`Select the Instance Groups for this Organization + to run on.`)} /> - </FormGroup> - )} - </Field> - <VariablesField - id="template-variables" - name="extra_vars" - label={i18n._(t`Variables`)} - promptId="template-ask-variables-on-launch" - /> - <FormColumnLayout> - <FormField - id="template-forks" - name="forks" - type="number" - min="0" - label={i18n._(t`Forks`)} - tooltip={ - <span> - {i18n._(t`The number of parallel or simultaneous - processes to use while executing the playbook. An empty value, - or a value less than 1 will use the Ansible default which is - usually 5. The default number of forks can be overwritten - with a change to`)}{' '} - <code>ansible.cfg</code>.{' '} - {i18n._(t`Refer to the Ansible documentation for details - about the configuration file.`)} - </span> - } - /> - <FieldWithPrompt - fieldId="template-limit" - label={i18n._(t`Limit`)} - promptId="template-ask-limit-on-launch" - promptName="ask_limit_on_launch" - tooltip={i18n._(t`Provide a host pattern to further constrain - the list of hosts that will be managed or affected by the - playbook. Multiple patterns are allowed. Refer to Ansible - documentation for more information and examples on patterns.`)} - > - <Field name="limit"> - {({ form, field }) => { - return ( - <TextInput - id="template-limit" - {...field} - isValid={ - !form.touched.job_type || !form.errors.job_type - } - onChange={(value, event) => { - field.onChange(event); - }} - /> - ); - }} - </Field> - </FieldWithPrompt> + )} + </Field> <FieldWithPrompt - fieldId="template-verbosity" - label={i18n._(t`Verbosity`)} - promptId="template-ask-verbosity-on-launch" - promptName="ask_verbosity_on_launch" - tooltip={i18n._(t`Control the level of output ansible will - produce as the playbook executes.`)} + fieldId="template-tags" + label={i18n._(t`Job Tags`)} + promptId="template-ask-tags-on-launch" + promptName="ask_tags_on_launch" + tooltip={i18n._(t`Tags are useful when you have a large + playbook, and you want to run a specific part of a + play or task. Use commas to separate multiple tags. + Refer to Ansible Tower documentation for details on + the usage of tags.`)} > - <Field name="verbosity"> - {({ field }) => ( - <AnsibleSelect - id="template-verbosity" - data={verbosityOptions} - {...field} + <Field name="job_tags"> + {({ field, form }) => ( + <TagMultiSelect + value={field.value} + onChange={value => form.setFieldValue(field.name, value)} /> )} </Field> </FieldWithPrompt> - <FormField - id="template-job-slicing" - name="job_slice_count" - type="number" - min="1" - label={i18n._(t`Job Slicing`)} - tooltip={i18n._(t`Divide the work done by this job template - into the specified number of job slices, each running the - same tasks against a portion of the inventory.`)} - /> - <FormField - id="template-timeout" - name="timeout" - type="number" - min="0" - label={i18n._(t`Timeout`)} - tooltip={i18n._(t`The amount of time (in seconds) to run - before the task is canceled. Defaults to 0 for no job - timeout.`)} - /> <FieldWithPrompt - fieldId="template-diff-mode" - label={i18n._(t`Show Changes`)} - promptId="template-ask-diff-mode-on-launch" - promptName="ask_diff_mode_on_launch" - tooltip={i18n._(t`If enabled, show the changes made by - Ansible tasks, where supported. This is equivalent - to Ansible’s --diff mode.`)} + fieldId="template-skip-tags" + label={i18n._(t`Skip Tags`)} + promptId="template-ask-skip-tags-on-launch" + promptName="ask_skip_tags_on_launch" + tooltip={i18n._(t`Skip tags are useful when you have a + large playbook, and you want to skip specific parts of a + play or task. Use commas to separate multiple tags. Refer + to Ansible Tower documentation for details on the usage + of tags.`)} > - <Field name="diff_mode"> - {({ form, field }) => { - return ( - <Switch - id="template-show-changes" - label={field.value ? i18n._(t`On`) : i18n._(t`Off`)} - isChecked={field.value} - onChange={checked => - form.setFieldValue(field.name, checked) - } - /> - ); - }} - </Field> - </FieldWithPrompt> - <FormFullWidthLayout> - <Field name="instanceGroups"> + <Field name="skip_tags"> {({ field, form }) => ( - <InstanceGroupsLookup + <TagMultiSelect value={field.value} onChange={value => form.setFieldValue(field.name, value)} - tooltip={i18n._(t`Select the Instance Groups for this Organization - to run on.`)} /> )} </Field> - <FieldWithPrompt - fieldId="template-tags" - label={i18n._(t`Job Tags`)} - promptId="template-ask-tags-on-launch" - promptName="ask_tags_on_launch" - tooltip={i18n._(t`Tags are useful when you have a large - playbook, and you want to run a specific part of a - play or task. Use commas to separate multiple tags. - Refer to Ansible Tower documentation for details on - the usage of tags.`)} - > - <Field name="job_tags"> - {({ field, form }) => ( - <TagMultiSelect - value={field.value} - onChange={value => - form.setFieldValue(field.name, value) - } - /> - )} - </Field> - </FieldWithPrompt> - <FieldWithPrompt - fieldId="template-skip-tags" - label={i18n._(t`Skip Tags`)} - promptId="template-ask-skip-tags-on-launch" - promptName="ask_skip_tags_on_launch" - tooltip={i18n._(t`Skip tags are useful when you have a - large playbook, and you want to skip specific parts of a - play or task. Use commas to separate multiple tags. Refer - to Ansible Tower documentation for details on the usage - of tags.`)} - > - <Field name="skip_tags"> - {({ field, form }) => ( - <TagMultiSelect - value={field.value} - onChange={value => - form.setFieldValue(field.name, value) - } - /> - )} - </Field> - </FieldWithPrompt> - <FormGroup - fieldId="template-option-checkboxes" - label={i18n._(t`Options`)} - > - <FormCheckboxLayout> - <CheckboxField - id="option-privilege-escalation" - name="become_enabled" - label={i18n._(t`Privilege Escalation`)} - tooltip={i18n._(t`If enabled, run this playbook as an + </FieldWithPrompt> + <FormGroup + fieldId="template-option-checkboxes" + label={i18n._(t`Options`)} + > + <FormCheckboxLayout> + <CheckboxField + id="option-privilege-escalation" + name="become_enabled" + label={i18n._(t`Privilege Escalation`)} + tooltip={i18n._(t`If enabled, run this playbook as an administrator.`)} - /> - <Checkbox - aria-label={i18n._(t`Provisioning Callbacks`)} - label={ - <span> - {i18n._(t`Provisioning Callbacks`)} - - <FieldTooltip - content={i18n._(t`Enables creation of a provisioning + /> + <Checkbox + aria-label={i18n._(t`Provisioning Callbacks`)} + label={ + <span> + {i18n._(t`Provisioning Callbacks`)} + + <FieldTooltip + content={i18n._(t`Enables creation of a provisioning callback URL. Using the URL a host can contact BRAND_NAME and request a configuration update using this job template.`)} - /> - </span> - } - id="option-callbacks" - isChecked={allowCallbacks} - onChange={checked => { - this.setState({ allowCallbacks: checked }); - }} - /> - <CheckboxField - id="option-concurrent" - name="allow_simultaneous" - label={i18n._(t`Concurrent Jobs`)} - tooltip={i18n._(t`If enabled, simultaneous runs of this job + /> + </span> + } + id="option-callbacks" + isChecked={allowCallbacks} + onChange={checked => { + setAllowCallbacks(checked); + }} + /> + <CheckboxField + id="option-concurrent" + name="allow_simultaneous" + label={i18n._(t`Concurrent Jobs`)} + tooltip={i18n._(t`If enabled, simultaneous runs of this job template will be allowed.`)} - /> - <CheckboxField - id="option-fact-cache" - name="use_fact_cache" - label={i18n._(t`Fact Cache`)} - tooltip={i18n._(t`If enabled, use cached facts if available + /> + <CheckboxField + id="option-fact-cache" + name="use_fact_cache" + label={i18n._(t`Fact Cache`)} + tooltip={i18n._(t`If enabled, use cached facts if available and store discovered facts in the cache.`)} - /> - </FormCheckboxLayout> - </FormGroup> - </FormFullWidthLayout> - {allowCallbacks && ( - <> - {callbackUrl && ( - <FormGroup - label={i18n._(t`Provisioning Callback URL`)} - fieldId="template-callback-url" - > - <TextInput - id="template-callback-url" - isDisabled - value={callbackUrl} - /> - </FormGroup> - )} - <FormField - id="template-host-config-key" - name="host_config_key" - label={i18n._(t`Host Config Key`)} - validate={allowCallbacks ? required(null, i18n) : null} /> - </> - )} - </FormColumnLayout> - </FormFullWidthLayout> - <FormSubmitError error={submitError} /> - <FormActionGroup onCancel={handleCancel} onSubmit={handleSubmit} /> - </FormColumnLayout> - </Form> - ); - } + </FormCheckboxLayout> + </FormGroup> + </FormFullWidthLayout> + {allowCallbacks && ( + <> + {callbackUrl && ( + <FormGroup + label={i18n._(t`Provisioning Callback URL`)} + fieldId="template-callback-url" + > + <TextInput + id="template-callback-url" + isDisabled + value={callbackUrl} + /> + </FormGroup> + )} + <FormField + id="template-host-config-key" + name="host_config_key" + label={i18n._(t`Host Config Key`)} + validate={allowCallbacks ? required(null, i18n) : null} + /> + </> + )} + </FormColumnLayout> + </FormFullWidthLayout> + <FormSubmitError error={submitError} /> + <FormActionGroup onCancel={handleCancel} onSubmit={handleSubmit} /> + </FormColumnLayout> + </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: [] }, diff --git a/awx/ui_next/src/screens/Template/shared/JobTemplateForm.test.jsx b/awx/ui_next/src/screens/Template/shared/JobTemplateForm.test.jsx index 338af6f714..383bef39f0 100644 --- a/awx/ui_next/src/screens/Template/shared/JobTemplateForm.test.jsx +++ b/awx/ui_next/src/screens/Template/shared/JobTemplateForm.test.jsx @@ -157,6 +157,7 @@ describe('<JobTemplateForm />', () => { name: 'inventory', }); }); + wrapper.update(); await act(async () => { wrapper.find('input#template-scm-branch').simulate('change', { |