diff --git a/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx b/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx
index 91818e84c7..46a502df8c 100644
--- a/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx
+++ b/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx
@@ -65,3 +65,106 @@ VariablesField.defaultProps = {
};
export default VariablesField;
+
+
+/*
+import React, { useState } from 'react';
+import { string, bool } from 'prop-types';
+import { withI18n } from '@lingui/react';
+import { t } from '@lingui/macro';
+import { Field, useFormikContext } from 'formik';
+import { Split, SplitItem } from '@patternfly/react-core';
+import { yamlToJson, jsonToYaml, isJson } from '@util/yaml';
+import { CheckboxField } from '@components/FormField';
+import styled from 'styled-components';
+import CodeMirrorInput from './CodeMirrorInput';
+import YamlJsonToggle from './YamlJsonToggle';
+import { JSON_MODE, YAML_MODE } from './constants';
+
+const FieldHeader = styled.div`
+ display: flex;
+ justify-content: space-between;
+`;
+
+const StyledCheckboxField = styled(CheckboxField)`
+ --pf-c-check__label--FontSize: var(--pf-c-form__label--FontSize);
+`;
+
+function VariablesField({ i18n, id, name, label, readOnly, promptId }) {
+ const { values, setFieldError, setFieldValue } = useFormikContext();
+ const value = values[name];
+ const [mode, setMode] = useState(isJson(value) ? JSON_MODE : YAML_MODE);
+
+ return (
+
+
+
+
+
+
+
+ {
+ try {
+ const newVal =
+ newMode === YAML_MODE
+ ? jsonToYaml(value)
+ : yamlToJson(value);
+ setFieldValue(name, newVal);
+ setMode(newMode);
+ } catch (err) {
+ setFieldError(name, err.message);
+ }
+ }}
+ />
+
+
+ {promptId && (
+
+ )}
+
+
+ {({ field, form }) => (
+ <>
+ {
+ form.setFieldValue(name, newVal);
+ }}
+ hasErrors={!!form.errors[field.name]}
+ />
+ {form.errors[field.name] ? (
+
+ {form.errors[field.name]}
+
+ ) : null}
+ >
+ )}
+
+
+ );
+}
+VariablesField.propTypes = {
+ id: string.isRequired,
+ name: string.isRequired,
+ label: string.isRequired,
+ readOnly: bool,
+};
+VariablesField.defaultProps = {
+ readOnly: false,
+};
+
+export default withI18n()(VariablesField);
+*/
diff --git a/awx/ui_next/src/components/Lookup/InventoryLookup.jsx b/awx/ui_next/src/components/Lookup/InventoryLookup.jsx
index 938ab80273..eacb269dd9 100644
--- a/awx/ui_next/src/components/Lookup/InventoryLookup.jsx
+++ b/awx/ui_next/src/components/Lookup/InventoryLookup.jsx
@@ -1,13 +1,11 @@
import React, { useState, useEffect } from 'react';
-import { string, func, bool } from 'prop-types';
+import { func, bool } from 'prop-types';
import { withRouter } from 'react-router-dom';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
-import { FormGroup } from '@patternfly/react-core';
import { InventoriesAPI } from '@api';
import { Inventory } from '@types';
import Lookup from '@components/Lookup';
-import { FieldTooltip } from '@components/FormField';
import { getQSConfig, parseQueryString } from '@util/qs';
import OptionsList from './shared/OptionsList';
import LookupErrorMessage from './shared/LookupErrorMessage';
@@ -18,17 +16,9 @@ const QS_CONFIG = getQSConfig('inventory', {
order_by: 'name',
});
-function InventoryLookup({
- value,
- tooltip,
- onChange,
- onBlur,
- required,
- isValid,
- helperTextInvalid,
- i18n,
- history,
-}) {
+function InventoryLookup({ value, onChange, onBlur, required, i18n, history }) {
+ // some stuff was stripped out of this component - need to propagate those changes
+ // out to other forms that use this lookup
const [inventories, setInventories] = useState([]);
const [count, setCount] = useState(0);
const [error, setError] = useState(null);
@@ -47,14 +37,7 @@ function InventoryLookup({
}, [history.location]);
return (
-
- {tooltip && }
+ <>
-
+ >
);
}
InventoryLookup.propTypes = {
value: Inventory,
- tooltip: string,
onChange: func.isRequired,
required: bool,
};
InventoryLookup.defaultProps = {
value: null,
- tooltip: '',
required: false,
};
diff --git a/awx/ui_next/src/components/Lookup/MultiCredentialsLookup.jsx b/awx/ui_next/src/components/Lookup/MultiCredentialsLookup.jsx
index cf05f48e89..12d660ffef 100644
--- a/awx/ui_next/src/components/Lookup/MultiCredentialsLookup.jsx
+++ b/awx/ui_next/src/components/Lookup/MultiCredentialsLookup.jsx
@@ -194,3 +194,197 @@ MultiCredentialsLookup.defaultProps = {
export { MultiCredentialsLookup as _MultiCredentialsLookup };
export default withI18n()(withRouter(MultiCredentialsLookup));
+
+
+/*
+import React, { Fragment, useState, useEffect } from 'react';
+import { withRouter } from 'react-router-dom';
+import PropTypes from 'prop-types';
+import { withI18n } from '@lingui/react';
+import { t } from '@lingui/macro';
+import { ToolbarItem } from '@patternfly/react-core';
+import { CredentialsAPI, CredentialTypesAPI } from '@api';
+import AnsibleSelect from '@components/AnsibleSelect';
+import CredentialChip from '@components/CredentialChip';
+import VerticalSeperator from '@components/VerticalSeparator';
+import { getQSConfig, parseQueryString } from '@util/qs';
+import Lookup from './Lookup';
+import OptionsList from './shared/OptionsList';
+
+const QS_CONFIG = getQSConfig('credentials', {
+ page: 1,
+ page_size: 5,
+ order_by: 'name',
+});
+
+async function loadCredentialTypes() {
+ const { data } = await CredentialTypesAPI.read();
+ const acceptableTypes = ['machine', 'cloud', 'net', 'ssh', 'vault'];
+ return data.results.filter(type => acceptableTypes.includes(type.kind));
+}
+
+async function loadCredentials(params, selectedCredentialTypeId) {
+ params.credential_type = selectedCredentialTypeId || 1;
+ const { data } = await CredentialsAPI.read(params);
+ return data;
+}
+
+function MultiCredentialsLookup(props) {
+ const { value, onChange, onError, history, i18n } = props;
+ const [credentialTypes, setCredentialTypes] = useState([]);
+ const [selectedType, setSelectedType] = useState(null);
+ const [credentials, setCredentials] = useState([]);
+ const [credentialsCount, setCredentialsCount] = useState(0);
+
+ useEffect(() => {
+ (async () => {
+ try {
+ const types = await loadCredentialTypes();
+ setCredentialTypes(types);
+ const match = types.find(type => type.kind === 'ssh') || types[0];
+ setSelectedType(match);
+ } catch (err) {
+ onError(err);
+ }
+ })();
+ }, [onError]);
+
+ useEffect(() => {
+ (async () => {
+ if (!selectedType) {
+ return;
+ }
+ try {
+ const params = parseQueryString(QS_CONFIG, history.location.search);
+ const { results, count } = await loadCredentials(
+ params,
+ selectedType.id
+ );
+ setCredentials(results);
+ setCredentialsCount(count);
+ } catch (err) {
+ onError(err);
+ }
+ })();
+ }, [selectedType, history.location.search, onError]);
+
+ const renderChip = ({ item, removeItem, canDelete }) => (
+ removeItem(item)}
+ isReadOnly={!canDelete}
+ credential={item}
+ />
+ );
+
+ const isMultiple = selectedType && selectedType.kind === 'vault';
+
+ return (
+ {
+ return (
+
+ {credentialTypes && credentialTypes.length > 0 && (
+
+ {i18n._(t`Selected Category`)}
+
+ ({
+ key: type.id,
+ value: type.id,
+ label: type.name,
+ isDisabled: false,
+ }))}
+ value={selectedType && selectedType.id}
+ onChange={(e, id) => {
+ setSelectedType(
+ credentialTypes.find(o => o.id === parseInt(id, 10))
+ );
+ }}
+ />
+
+ )}
+ {
+ if (isMultiple) {
+ return dispatch({ type: 'SELECT_ITEM', item });
+ }
+ const selectedItems = state.selectedItems.filter(
+ i => i.kind !== item.kind
+ );
+ selectedItems.push(item);
+ return dispatch({
+ type: 'SET_SELECTED_ITEMS',
+ selectedItems,
+ });
+ }}
+ deselectItem={item => dispatch({ type: 'DESELECT_ITEM', item })}
+ renderItemChip={renderChip}
+ />
+
+ );
+ }}
+ />
+ );
+}
+
+MultiCredentialsLookup.propTypes = {
+ value: PropTypes.arrayOf(
+ PropTypes.shape({
+ id: PropTypes.number,
+ name: PropTypes.string,
+ description: PropTypes.string,
+ kind: PropTypes.string,
+ clound: PropTypes.bool,
+ })
+ ),
+ onChange: PropTypes.func.isRequired,
+ onError: PropTypes.func.isRequired,
+};
+
+MultiCredentialsLookup.defaultProps = {
+ value: [],
+};
+
+export { MultiCredentialsLookup as _MultiCredentialsLookup };
+export default withI18n()(withRouter(MultiCredentialsLookup));
+*/
diff --git a/awx/ui_next/src/screens/Host/shared/HostForm.jsx b/awx/ui_next/src/screens/Host/shared/HostForm.jsx
index af5fb0a2ec..de7b22101d 100644
--- a/awx/ui_next/src/screens/Host/shared/HostForm.jsx
+++ b/awx/ui_next/src/screens/Host/shared/HostForm.jsx
@@ -122,3 +122,139 @@ HostForm.defaultProps = {
export { HostForm as _HostForm };
export default withI18n()(HostForm);
+
+
+
+/*
+import React, { useState } from 'react';
+import { func, shape } from 'prop-types';
+
+import { useRouteMatch } from 'react-router-dom';
+import { Formik, Field } from 'formik';
+import { withI18n } from '@lingui/react';
+import { t } from '@lingui/macro';
+
+import { Form, FormGroup } from '@patternfly/react-core';
+
+import FormRow from '@components/FormRow';
+import FormField, {
+ FormSubmitError,
+ FieldTooltip,
+} from '@components/FormField';
+import FormActionGroup from '@components/FormActionGroup/FormActionGroup';
+import { VariablesField } from '@components/CodeMirrorInput';
+import { required } from '@util/validators';
+import { InventoryLookup } from '@components/Lookup';
+
+function HostForm({ handleSubmit, handleCancel, host, submitError, i18n }) {
+ const [inventory, setInventory] = useState(
+ host ? host.summary_fields.inventory : ''
+ );
+
+ const hostAddMatch = useRouteMatch('/hosts/add');
+
+ return (
+
+ {formik => (
+
+ )}
+
+ );
+}
+
+HostForm.propTypes = {
+ handleSubmit: func.isRequired,
+ handleCancel: func.isRequired,
+ host: shape({}),
+ submitError: shape({}),
+};
+
+HostForm.defaultProps = {
+ host: {
+ name: '',
+ description: '',
+ inventory: undefined,
+ variables: '---\n',
+ summary_fields: {
+ inventory: null,
+ },
+ },
+ submitError: null,
+};
+
+export { HostForm as _HostForm };
+export default withI18n()(HostForm);
+*/
diff --git a/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.test.jsx b/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.test.jsx
index 748b71dbcf..84847af506 100644
--- a/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.test.jsx
+++ b/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.test.jsx
@@ -10,9 +10,20 @@ jest.mock('@api');
const jobTemplateData = {
allow_callbacks: false,
allow_simultaneous: false,
+ ask_credential_on_launch: false,
+ ask_diff_mode_on_launch: false,
+ ask_inventory_on_launch: false,
ask_job_type_on_launch: false,
- description: 'Baz',
+ ask_limit_on_launch: false,
+ ask_scm_branch_on_launch: false,
+ ask_skip_tags_on_launch: false,
+ ask_tags_on_launch: false,
+ ask_variables_on_launch: false,
+ ask_verbosity_on_launch: false,
+ become_enabled: false,
+ description: '',
diff_mode: false,
+ extra_vars: '---\n',
forks: 0,
host_config_key: '',
inventory: 1,
@@ -20,9 +31,9 @@ const jobTemplateData = {
job_tags: '',
job_type: 'run',
limit: '',
- name: 'Foo',
- playbook: 'Bar',
- project: 2,
+ name: '',
+ playbook: '',
+ project: 1,
scm_branch: '',
skip_tags: '',
timeout: 0,
@@ -103,13 +114,12 @@ describe('', () => {
await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0);
act(() => {
wrapper.find('input#template-name').simulate('change', {
- target: { value: 'Foo', name: 'name' },
- });
- wrapper.find('AnsibleSelect#template-job-type').invoke('onChange')('run');
- wrapper.find('InventoryLookup').invoke('onChange')({
- id: 1,
- organization: 1,
+ target: { value: 'Bar', name: 'name' },
});
+ wrapper.find('AnsibleSelect#template-job-type').prop('onChange')(
+ null,
+ 'check'
+ );
wrapper.find('ProjectLookup').invoke('onChange')({
id: 2,
name: 'project',
@@ -119,18 +129,28 @@ describe('', () => {
.find('PlaybookSelect')
.prop('field')
.onChange({
- target: { value: 'Bar', name: 'playbook' },
+ target: { value: 'Baz', name: 'playbook' },
});
});
wrapper.update();
+ act(() => {
+ wrapper.find('InventoryLookup').invoke('onChange')({
+ id: 2,
+ organization: 1,
+ });
+ });
+ wrapper.update();
await act(async () => {
wrapper.find('form').simulate('submit');
});
wrapper.update();
expect(JobTemplatesAPI.create).toHaveBeenCalledWith({
...jobTemplateData,
- description: '',
- become_enabled: false,
+ name: 'Bar',
+ job_type: 'check',
+ project: 2,
+ playbook: 'Baz',
+ inventory: 2,
});
});
@@ -154,11 +174,10 @@ describe('', () => {
wrapper.find('input#template-name').simulate('change', {
target: { value: 'Foo', name: 'name' },
});
- wrapper.find('AnsibleSelect#template-job-type').invoke('onChange')('run');
- wrapper.find('InventoryLookup').invoke('onChange')({
- id: 1,
- organization: 1,
- });
+ wrapper.find('AnsibleSelect#template-job-type').prop('onChange')(
+ null,
+ 'check'
+ );
wrapper.find('ProjectLookup').invoke('onChange')({
id: 2,
name: 'project',
@@ -172,6 +191,13 @@ describe('', () => {
});
});
wrapper.update();
+ act(() => {
+ wrapper.find('InventoryLookup').invoke('onChange')({
+ id: 1,
+ organization: 1,
+ });
+ });
+ wrapper.update();
await act(async () => {
wrapper.find('form').simulate('submit');
});
diff --git a/awx/ui_next/src/screens/Template/JobTemplateDetail/JobTemplateDetail.jsx b/awx/ui_next/src/screens/Template/JobTemplateDetail/JobTemplateDetail.jsx
index 999af636e2..0b8a118f4a 100644
--- a/awx/ui_next/src/screens/Template/JobTemplateDetail/JobTemplateDetail.jsx
+++ b/awx/ui_next/src/screens/Template/JobTemplateDetail/JobTemplateDetail.jsx
@@ -22,6 +22,7 @@ import { DetailList, Detail, UserDateDetail } from '@components/DetailList';
import DeleteButton from '@components/DeleteButton';
import ErrorDetail from '@components/ErrorDetail';
import LaunchButton from '@components/LaunchButton';
+import { VariablesDetail } from '@components/CodeMirrorInput';
import { JobTemplatesAPI } from '@api';
const MissingDetail = styled(Detail)`
@@ -38,6 +39,7 @@ function JobTemplateDetail({ i18n, template }) {
created,
description,
diff_mode,
+ extra_vars,
forks,
host_config_key,
job_slice_count,
@@ -302,6 +304,11 @@ function JobTemplateDetail({ i18n, template }) {
}
/>
)}
+
{summary_fields.user_capabilities &&
diff --git a/awx/ui_next/src/screens/Template/JobTemplateEdit/JobTemplateEdit.test.jsx b/awx/ui_next/src/screens/Template/JobTemplateEdit/JobTemplateEdit.test.jsx
index 8798249169..92c47ecf05 100644
--- a/awx/ui_next/src/screens/Template/JobTemplateEdit/JobTemplateEdit.test.jsx
+++ b/awx/ui_next/src/screens/Template/JobTemplateEdit/JobTemplateEdit.test.jsx
@@ -11,9 +11,20 @@ jest.mock('@api');
const mockJobTemplate = {
allow_callbacks: false,
allow_simultaneous: false,
+ ask_scm_branch_on_launch: false,
+ ask_diff_mode_on_launch: false,
+ ask_variables_on_launch: false,
+ ask_limit_on_launch: false,
+ ask_tags_on_launch: false,
+ ask_skip_tags_on_launch: false,
ask_job_type_on_launch: false,
+ ask_verbosity_on_launch: false,
+ ask_inventory_on_launch: false,
+ ask_credential_on_launch: false,
+ become_enabled: false,
description: 'Bar',
diff_mode: false,
+ extra_vars: '---',
forks: 0,
host_config_key: '',
id: 1,
@@ -192,6 +203,7 @@ describe('', () => {
);
});
const updatedTemplateData = {
+ job_type: 'check',
name: 'new name',
inventory: 1,
};
@@ -206,14 +218,18 @@ describe('', () => {
wrapper.find('input#template-name').simulate('change', {
target: { value: 'new name', name: 'name' },
});
- wrapper.find('AnsibleSelect#template-job-type').invoke('onChange')(
+ wrapper.find('AnsibleSelect#template-job-type').prop('onChange')(
+ null,
'check'
);
+ wrapper.find('LabelSelect').invoke('onChange')(labels);
+ });
+ wrapper.update();
+ act(() => {
wrapper.find('InventoryLookup').invoke('onChange')({
id: 1,
organization: 1,
});
- wrapper.find('LabelSelect').invoke('onChange')(labels);
});
wrapper.update();
await act(async () => {
@@ -224,7 +240,6 @@ describe('', () => {
const expected = {
...mockJobTemplate,
...updatedTemplateData,
- become_enabled: false,
};
delete expected.summary_fields;
delete expected.id;
diff --git a/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx b/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx
index 82ceeb3313..fd8a81d4ba 100644
--- a/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx
+++ b/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx
@@ -642,3 +642,749 @@ const FormikApp = withFormik({
export { JobTemplateForm as _JobTemplateForm };
export default withI18n()(withRouter(FormikApp));
+
+
+
+
+
+/*
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import { withRouter } from 'react-router-dom';
+import { withI18n } from '@lingui/react';
+import { t } from '@lingui/macro';
+import { withFormik, Field } from 'formik';
+import {
+ Form,
+ FormGroup,
+ Switch,
+ Checkbox,
+ TextInput,
+} from '@patternfly/react-core';
+import ContentError from '@components/ContentError';
+import ContentLoading from '@components/ContentLoading';
+import AnsibleSelect from '@components/AnsibleSelect';
+import { TagMultiSelect } from '@components/MultiSelect';
+import FormActionGroup from '@components/FormActionGroup';
+import FormField, {
+ CheckboxField,
+ FieldTooltip,
+ FormSubmitError,
+} from '@components/FormField';
+import FieldWithPrompt from '@components/FieldWithPrompt';
+import FormRow from '@components/FormRow';
+import { required } from '@util/validators';
+import styled from 'styled-components';
+import { JobTemplate } from '@types';
+import {
+ InventoryLookup,
+ InstanceGroupsLookup,
+ ProjectLookup,
+ MultiCredentialsLookup,
+} from '@components/Lookup';
+import { VariablesField } from '@components/CodeMirrorInput';
+import { JobTemplatesAPI, ProjectsAPI } from '@api';
+import LabelSelect from './LabelSelect';
+import PlaybookSelect from './PlaybookSelect';
+
+const GridFormGroup = styled(FormGroup)`
+ & > label {
+ grid-column: 1 / -1;
+ }
+
+ && {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
+ }
+`;
+
+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');
+ }
+ );
+ }
+
+ 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 });
+ }
+ }
+ }
+
+ 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 });
+ }
+ }
+
+ 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;
+ };
+ }
+
+ handleProjectUpdate(project) {
+ const { setFieldValue } = this.props;
+ setFieldValue('project', project.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 = [
+ {
+ 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 ;
+ }
+
+ if (contentError) {
+ return ;
+ }
+
+ return (
+
+ );
+ }
+}
+
+const FormikApp = withFormik({
+ mapPropsToValues(props) {
+ const { template = {} } = props;
+ const {
+ summary_fields = {
+ labels: { results: [] },
+ inventory: { organization: null },
+ },
+ } = template;
+ const hasInventory = summary_fields.inventory
+ ? summary_fields.inventory.organization_id
+ : null;
+ return {
+ ask_credential_on_launch: template.ask_credential_on_launch || false,
+ ask_diff_mode_on_launch: template.ask_diff_mode_on_launch || false,
+ ask_inventory_on_launch: template.ask_inventory_on_launch || false,
+ ask_job_type_on_launch: template.ask_job_type_on_launch || false,
+ ask_limit_on_launch: template.ask_limit_on_launch || false,
+ ask_scm_branch_on_launch: template.ask_scm_branch_on_launch || false,
+ ask_skip_tags_on_launch: template.ask_skip_tags_on_launch || false,
+ ask_tags_on_launch: template.ask_tags_on_launch || false,
+ ask_variables_on_launch: template.ask_variables_on_launch || false,
+ ask_verbosity_on_launch: template.ask_verbosity_on_launch || false,
+ name: template.name || '',
+ description: template.description || '',
+ job_type: template.job_type || 'run',
+ inventory: template.inventory || '',
+ project: template.project || '',
+ scm_branch: template.scm_branch || '',
+ playbook: template.playbook || '',
+ labels: summary_fields.labels.results || [],
+ forks: template.forks || 0,
+ limit: template.limit || '',
+ verbosity: template.verbosity || '0',
+ job_slice_count: template.job_slice_count || 1,
+ timeout: template.timeout || 0,
+ diff_mode: template.diff_mode || false,
+ job_tags: template.job_tags || '',
+ skip_tags: template.skip_tags || '',
+ become_enabled: template.become_enabled || false,
+ allow_callbacks: template.allow_callbacks || false,
+ allow_simultaneous: template.allow_simultaneous || false,
+ use_fact_cache: template.use_fact_cache || false,
+ host_config_key: template.host_config_key || '',
+ organizationId: hasInventory,
+ initialInstanceGroups: [],
+ instanceGroups: [],
+ credentials: summary_fields.credentials || [],
+ extra_vars: template.extra_vars || '---\n',
+ };
+ },
+ handleSubmit: async (values, { props, setErrors }) => {
+ try {
+ await props.handleSubmit(values);
+ } catch (errors) {
+ setErrors(errors);
+ }
+ },
+ validate: values => {
+ const errors = {};
+
+ if (
+ (!values.inventory || values.inventory === '') &&
+ !values.ask_inventory_on_launch
+ ) {
+ errors.inventory =
+ 'Please select an Inventory or check the Prompt on Launch option.';
+ }
+
+ return errors;
+ },
+})(JobTemplateForm);
+
+export { JobTemplateForm as _JobTemplateForm };
+export default withI18n()(withRouter(FormikApp));
+*/
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 95a02c7edb..338af6f714 100644
--- a/awx/ui_next/src/screens/Template/shared/JobTemplateForm.test.jsx
+++ b/awx/ui_next/src/screens/Template/shared/JobTemplateForm.test.jsx
@@ -140,13 +140,10 @@ describe('', () => {
wrapper.find('input#template-description').simulate('change', {
target: { value: 'new bar', name: 'description' },
});
- wrapper.find('AnsibleSelect[name="job_type"]').simulate('change', {
- target: { value: 'new job type', name: 'job_type' },
- });
- wrapper.find('InventoryLookup').invoke('onChange')({
- id: 3,
- name: 'inventory',
- });
+ wrapper.find('AnsibleSelect#template-job-type').prop('onChange')(
+ null,
+ 'check'
+ );
wrapper.find('ProjectLookup').invoke('onChange')({
id: 4,
name: 'project',
@@ -155,7 +152,14 @@ describe('', () => {
});
wrapper.update();
await act(async () => {
- wrapper.find('input#scm_branch').simulate('change', {
+ wrapper.find('InventoryLookup').invoke('onChange')({
+ id: 3,
+ name: 'inventory',
+ });
+ });
+ wrapper.update();
+ await act(async () => {
+ wrapper.find('input#template-scm-branch').simulate('change', {
target: { value: 'devel', name: 'scm_branch' },
});
wrapper.find('AnsibleSelect[name="playbook"]').simulate('change', {
@@ -179,7 +183,7 @@ describe('', () => {
);
expect(
wrapper.find('AnsibleSelect[name="job_type"]').prop('value')
- ).toEqual('new job type');
+ ).toEqual('check');
expect(wrapper.find('InventoryLookup').prop('value')).toEqual({
id: 3,
name: 'inventory',
@@ -189,7 +193,9 @@ describe('', () => {
name: 'project',
allow_override: true,
});
- expect(wrapper.find('input#scm_branch').prop('value')).toEqual('devel');
+ expect(wrapper.find('input#template-scm-branch').prop('value')).toEqual(
+ 'devel'
+ );
expect(
wrapper.find('AnsibleSelect[name="playbook"]').prop('value')
).toEqual('new baz type');