summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsoftwarefactory-project-zuul[bot] <33884098+softwarefactory-project-zuul[bot]@users.noreply.github.com>2020-03-18 16:58:13 +0100
committerGitHub <noreply@github.com>2020-03-18 16:58:13 +0100
commit3219b9b4ac3d81af8eef47e8dd276f5bc1cdccff (patch)
tree18d5b5734e7dde2cb72ee6ade73ebf9bb29262cb
parentMerge pull request #6319 from squidboylan/collection_test_refactor (diff)
parentMoves JT Form to using react hooks and custom hooks (diff)
downloadawx-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.jsx1037
-rw-r--r--awx/ui_next/src/screens/Template/shared/JobTemplateForm.test.jsx1
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&#x2019s --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&#x2019s --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`)}
- &nbsp;
- <FieldTooltip
- content={i18n._(t`Enables creation of a provisioning
+ />
+ <Checkbox
+ aria-label={i18n._(t`Provisioning Callbacks`)}
+ label={
+ <span>
+ {i18n._(t`Provisioning Callbacks`)}
+ &nbsp;
+ <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', {