mirror of
https://github.com/ansible/awx.git
synced 2026-01-13 02:50:02 -03:30
Merge pull request #6001 from mabashian/4967-jt-prompt-on-launch
Adds prompt on launch support to the rest of the relevant jt fields Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
commit
e096ad18cb
@ -1,42 +1,64 @@
|
||||
import React, { useState } from 'react';
|
||||
import { string, bool } from 'prop-types';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { useField } from 'formik';
|
||||
import styled from 'styled-components';
|
||||
import { Split, SplitItem } from '@patternfly/react-core';
|
||||
import { CheckboxField } from '@components/FormField';
|
||||
import { yamlToJson, jsonToYaml, isJson } from '@util/yaml';
|
||||
import CodeMirrorInput from './CodeMirrorInput';
|
||||
import YamlJsonToggle from './YamlJsonToggle';
|
||||
import { JSON_MODE, YAML_MODE } from './constants';
|
||||
|
||||
function VariablesField({ id, name, label, readOnly }) {
|
||||
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 [field, meta, helpers] = useField(name);
|
||||
const [mode, setMode] = useState(isJson(field.value) ? JSON_MODE : YAML_MODE);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Split gutter="sm">
|
||||
<SplitItem>
|
||||
<label htmlFor={id} className="pf-c-form__label">
|
||||
<span className="pf-c-form__label-text">{label}</span>
|
||||
</label>
|
||||
</SplitItem>
|
||||
<SplitItem>
|
||||
<YamlJsonToggle
|
||||
mode={mode}
|
||||
onChange={newMode => {
|
||||
try {
|
||||
const newVal =
|
||||
newMode === YAML_MODE
|
||||
? jsonToYaml(field.value)
|
||||
: yamlToJson(field.value);
|
||||
helpers.setValue(newVal);
|
||||
setMode(newMode);
|
||||
} catch (err) {
|
||||
helpers.setError(err.message);
|
||||
}
|
||||
}}
|
||||
<div className="pf-c-form__group">
|
||||
<FieldHeader>
|
||||
<Split gutter="sm">
|
||||
<SplitItem>
|
||||
<label htmlFor={id} className="pf-c-form__label">
|
||||
<span className="pf-c-form__label-text">{label}</span>
|
||||
</label>
|
||||
</SplitItem>
|
||||
<SplitItem>
|
||||
<YamlJsonToggle
|
||||
mode={mode}
|
||||
onChange={newMode => {
|
||||
try {
|
||||
const newVal =
|
||||
newMode === YAML_MODE
|
||||
? jsonToYaml(field.value)
|
||||
: yamlToJson(field.value);
|
||||
helpers.setValue(newVal);
|
||||
setMode(newMode);
|
||||
} catch (err) {
|
||||
helpers.setError(err.message);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</SplitItem>
|
||||
</Split>
|
||||
{promptId && (
|
||||
<StyledCheckboxField
|
||||
id="template-ask-variables-on-launch"
|
||||
label={i18n._(t`Prompt On Launch`)}
|
||||
name="ask_variables_on_launch"
|
||||
/>
|
||||
</SplitItem>
|
||||
</Split>
|
||||
)}
|
||||
</FieldHeader>
|
||||
<CodeMirrorInput
|
||||
mode={mode}
|
||||
readOnly={readOnly}
|
||||
@ -51,7 +73,7 @@ function VariablesField({ id, name, label, readOnly }) {
|
||||
{meta.error}
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
VariablesField.propTypes = {
|
||||
@ -59,9 +81,11 @@ VariablesField.propTypes = {
|
||||
name: string.isRequired,
|
||||
label: string.isRequired,
|
||||
readOnly: bool,
|
||||
promptId: string,
|
||||
};
|
||||
VariablesField.defaultProps = {
|
||||
readOnly: false,
|
||||
promptId: null,
|
||||
};
|
||||
|
||||
export default VariablesField;
|
||||
export default withI18n()(VariablesField);
|
||||
|
||||
@ -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,7 @@ 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 }) {
|
||||
const [inventories, setInventories] = useState([]);
|
||||
const [count, setCount] = useState(0);
|
||||
const [error, setError] = useState(null);
|
||||
@ -47,14 +35,7 @@ function InventoryLookup({
|
||||
}, [history.location]);
|
||||
|
||||
return (
|
||||
<FormGroup
|
||||
label={i18n._(t`Inventory`)}
|
||||
isRequired={required}
|
||||
fieldId="inventory-lookup"
|
||||
isValid={isValid}
|
||||
helperTextInvalid={helperTextInvalid}
|
||||
>
|
||||
{tooltip && <FieldTooltip content={tooltip} />}
|
||||
<>
|
||||
<Lookup
|
||||
id="inventory-lookup"
|
||||
header={i18n._(t`Inventory`)}
|
||||
@ -100,20 +81,18 @@ function InventoryLookup({
|
||||
)}
|
||||
/>
|
||||
<LookupErrorMessage error={error} />
|
||||
</FormGroup>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
InventoryLookup.propTypes = {
|
||||
value: Inventory,
|
||||
tooltip: string,
|
||||
onChange: func.isRequired,
|
||||
required: bool,
|
||||
};
|
||||
|
||||
InventoryLookup.defaultProps = {
|
||||
value: null,
|
||||
tooltip: '',
|
||||
required: false,
|
||||
};
|
||||
|
||||
|
||||
@ -3,10 +3,9 @@ import { withRouter } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { FormGroup, ToolbarItem } from '@patternfly/react-core';
|
||||
import { ToolbarItem } from '@patternfly/react-core';
|
||||
import { CredentialsAPI, CredentialTypesAPI } from '@api';
|
||||
import AnsibleSelect from '@components/AnsibleSelect';
|
||||
import { FieldTooltip } from '@components/FormField';
|
||||
import CredentialChip from '@components/CredentialChip';
|
||||
import { getQSConfig, parseQueryString } from '@util/qs';
|
||||
import Lookup from './Lookup';
|
||||
@ -31,7 +30,7 @@ async function loadCredentials(params, selectedCredentialTypeId) {
|
||||
}
|
||||
|
||||
function MultiCredentialsLookup(props) {
|
||||
const { tooltip, value, onChange, onError, history, i18n } = props;
|
||||
const { value, onChange, onError, history, i18n } = props;
|
||||
const [credentialTypes, setCredentialTypes] = useState([]);
|
||||
const [selectedType, setSelectedType] = useState(null);
|
||||
const [credentials, setCredentials] = useState([]);
|
||||
@ -81,99 +80,95 @@ function MultiCredentialsLookup(props) {
|
||||
const isMultiple = selectedType && selectedType.kind === 'vault';
|
||||
|
||||
return (
|
||||
<FormGroup label={i18n._(t`Credentials`)} fieldId="multiCredential">
|
||||
{tooltip && <FieldTooltip content={tooltip} />}
|
||||
<Lookup
|
||||
id="multiCredential"
|
||||
header={i18n._(t`Credentials`)}
|
||||
value={value}
|
||||
multiple
|
||||
onChange={onChange}
|
||||
qsConfig={QS_CONFIG}
|
||||
renderItemChip={renderChip}
|
||||
renderOptionsList={({ state, dispatch, canDelete }) => {
|
||||
return (
|
||||
<Fragment>
|
||||
{credentialTypes && credentialTypes.length > 0 && (
|
||||
<ToolbarItem css=" display: flex; align-items: center;">
|
||||
<div css="flex: 0 0 25%; margin-right: 32px">
|
||||
{i18n._(t`Selected Category`)}
|
||||
</div>
|
||||
<AnsibleSelect
|
||||
css="flex: 1 1 75%;"
|
||||
id="multiCredentialsLookUp-select"
|
||||
label={i18n._(t`Selected Category`)}
|
||||
data={credentialTypes.map(type => ({
|
||||
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))
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</ToolbarItem>
|
||||
)}
|
||||
<OptionsList
|
||||
value={state.selectedItems}
|
||||
options={credentials}
|
||||
optionCount={credentialsCount}
|
||||
searchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created By (Username)`),
|
||||
key: 'created_by__username',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified By (Username)`),
|
||||
key: 'modified_by__username',
|
||||
},
|
||||
]}
|
||||
sortColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
},
|
||||
]}
|
||||
multiple={isMultiple}
|
||||
header={i18n._(t`Credentials`)}
|
||||
name="credentials"
|
||||
qsConfig={QS_CONFIG}
|
||||
readOnly={!canDelete}
|
||||
selectItem={item => {
|
||||
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}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
<Lookup
|
||||
id="multiCredential"
|
||||
header={i18n._(t`Credentials`)}
|
||||
value={value}
|
||||
multiple
|
||||
onChange={onChange}
|
||||
qsConfig={QS_CONFIG}
|
||||
renderItemChip={renderChip}
|
||||
renderOptionsList={({ state, dispatch, canDelete }) => {
|
||||
return (
|
||||
<Fragment>
|
||||
{credentialTypes && credentialTypes.length > 0 && (
|
||||
<ToolbarItem css=" display: flex; align-items: center;">
|
||||
<div css="flex: 0 0 25%; margin-right: 32px">
|
||||
{i18n._(t`Selected Category`)}
|
||||
</div>
|
||||
<AnsibleSelect
|
||||
css="flex: 1 1 75%;"
|
||||
id="multiCredentialsLookUp-select"
|
||||
label={i18n._(t`Selected Category`)}
|
||||
data={credentialTypes.map(type => ({
|
||||
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))
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</ToolbarItem>
|
||||
)}
|
||||
<OptionsList
|
||||
value={state.selectedItems}
|
||||
options={credentials}
|
||||
optionCount={credentialsCount}
|
||||
searchColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Created By (Username)`),
|
||||
key: 'created_by__username',
|
||||
},
|
||||
{
|
||||
name: i18n._(t`Modified By (Username)`),
|
||||
key: 'modified_by__username',
|
||||
},
|
||||
]}
|
||||
sortColumns={[
|
||||
{
|
||||
name: i18n._(t`Name`),
|
||||
key: 'name',
|
||||
},
|
||||
]}
|
||||
multiple={isMultiple}
|
||||
header={i18n._(t`Credentials`)}
|
||||
name="credentials"
|
||||
qsConfig={QS_CONFIG}
|
||||
readOnly={!canDelete}
|
||||
selectItem={item => {
|
||||
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}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
MultiCredentialsLookup.propTypes = {
|
||||
tooltip: PropTypes.string,
|
||||
value: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
id: PropTypes.number,
|
||||
@ -188,7 +183,6 @@ MultiCredentialsLookup.propTypes = {
|
||||
};
|
||||
|
||||
MultiCredentialsLookup.defaultProps = {
|
||||
tooltip: '',
|
||||
value: [],
|
||||
};
|
||||
|
||||
|
||||
@ -6,9 +6,12 @@ import { Formik, useField } from 'formik';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
|
||||
import { Form } from '@patternfly/react-core';
|
||||
import { Form, FormGroup } from '@patternfly/react-core';
|
||||
|
||||
import FormField, { FormSubmitError } from '@components/FormField';
|
||||
import FormField, {
|
||||
FormSubmitError,
|
||||
FieldTooltip,
|
||||
} from '@components/FormField';
|
||||
import FormActionGroup from '@components/FormActionGroup/FormActionGroup';
|
||||
import { VariablesField } from '@components/CodeMirrorInput';
|
||||
import { required } from '@util/validators';
|
||||
@ -23,7 +26,7 @@ function HostFormFields({ host, i18n }) {
|
||||
const hostAddMatch = useRouteMatch('/hosts/add');
|
||||
const inventoryFieldArr = useField({
|
||||
name: 'inventory',
|
||||
validate: required(i18n._(t`Select aå value for this field`), i18n),
|
||||
validate: required(i18n._(t`Select a value for this field`), i18n),
|
||||
});
|
||||
const inventoryMeta = inventoryFieldArr[1];
|
||||
const inventoryHelpers = inventoryFieldArr[2];
|
||||
@ -45,22 +48,35 @@ function HostFormFields({ host, i18n }) {
|
||||
label={i18n._(t`Description`)}
|
||||
/>
|
||||
{hostAddMatch && (
|
||||
<InventoryLookup
|
||||
value={inventory}
|
||||
onBlur={() => inventoryHelpers.setTouched()}
|
||||
tooltip={i18n._(
|
||||
t`Select the inventory that this host will belong to.`
|
||||
)}
|
||||
<FormGroup
|
||||
label={i18n._(t`Inventory`)}
|
||||
isRequired
|
||||
fieldId="inventory-lookup"
|
||||
isValid={!inventoryMeta.touched || !inventoryMeta.error}
|
||||
helperTextInvalid={inventoryMeta.error}
|
||||
onChange={value => {
|
||||
inventoryHelpers.setValuealue(value.id);
|
||||
setInventory(value);
|
||||
}}
|
||||
required
|
||||
touched={inventoryMeta.touched}
|
||||
error={inventoryMeta.error}
|
||||
/>
|
||||
>
|
||||
<FieldTooltip
|
||||
content={i18n._(
|
||||
t`Select the inventory that this host will belong to.`
|
||||
)}
|
||||
/>
|
||||
<InventoryLookup
|
||||
value={inventory}
|
||||
onBlur={() => inventoryHelpers.setTouched()}
|
||||
tooltip={i18n._(
|
||||
t`Select the inventory that this host will belong to.`
|
||||
)}
|
||||
isValid={!inventoryMeta.touched || !inventoryMeta.error}
|
||||
helperTextInvalid={inventoryMeta.error}
|
||||
onChange={value => {
|
||||
inventoryHelpers.setValue(value.id);
|
||||
setInventory(value);
|
||||
}}
|
||||
required
|
||||
touched={inventoryMeta.touched}
|
||||
error={inventoryMeta.error}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
<FormFullWidthLayout>
|
||||
<VariablesField
|
||||
|
||||
@ -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('<JobTemplateAdd />', () => {
|
||||
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('<JobTemplateAdd />', () => {
|
||||
.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('<JobTemplateAdd />', () => {
|
||||
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('<JobTemplateAdd />', () => {
|
||||
});
|
||||
});
|
||||
wrapper.update();
|
||||
act(() => {
|
||||
wrapper.find('InventoryLookup').invoke('onChange')({
|
||||
id: 1,
|
||||
organization: 1,
|
||||
});
|
||||
});
|
||||
wrapper.update();
|
||||
await act(async () => {
|
||||
wrapper.find('form').simulate('submit');
|
||||
});
|
||||
|
||||
@ -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 }) {
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<VariablesDetail
|
||||
value={extra_vars}
|
||||
rows={4}
|
||||
label={i18n._(t`Variables`)}
|
||||
/>
|
||||
</DetailList>
|
||||
<CardActionsRow>
|
||||
{summary_fields.user_capabilities &&
|
||||
|
||||
@ -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('<JobTemplateEdit />', () => {
|
||||
);
|
||||
});
|
||||
const updatedTemplateData = {
|
||||
job_type: 'check',
|
||||
name: 'new name',
|
||||
inventory: 1,
|
||||
};
|
||||
@ -206,14 +218,18 @@ describe('<JobTemplateEdit />', () => {
|
||||
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('<JobTemplateEdit />', () => {
|
||||
const expected = {
|
||||
...mockJobTemplate,
|
||||
...updatedTemplateData,
|
||||
become_enabled: false,
|
||||
};
|
||||
delete expected.summary_fields;
|
||||
delete expected.id;
|
||||
|
||||
@ -27,7 +27,7 @@ import {
|
||||
FormFullWidthLayout,
|
||||
FormCheckboxLayout,
|
||||
} from '@components/FormLayout';
|
||||
import CollapsibleSection from '@components/CollapsibleSection';
|
||||
import { VariablesField } from '@components/CodeMirrorInput';
|
||||
import { required } from '@util/validators';
|
||||
import { JobTemplate } from '@types';
|
||||
import {
|
||||
@ -202,8 +202,6 @@ class JobTemplateForm extends Component {
|
||||
return <ContentError error={contentError} />;
|
||||
}
|
||||
|
||||
const AdvancedFieldsWrapper = template.isNew ? CollapsibleSection : 'div';
|
||||
|
||||
return (
|
||||
<Form autoComplete="off" onSubmit={handleSubmit}>
|
||||
<FormColumnLayout>
|
||||
@ -245,34 +243,57 @@ class JobTemplateForm extends Component {
|
||||
id="template-job-type"
|
||||
data={jobTypeOptions}
|
||||
{...field}
|
||||
onChange={(event, value) => {
|
||||
form.setFieldValue('job_type', value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</Field>
|
||||
</FieldWithPrompt>
|
||||
<Field
|
||||
name="inventory"
|
||||
validate={required(i18n._(t`Select a value for this field`), i18n)}
|
||||
<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.`)}
|
||||
>
|
||||
{({ form }) => (
|
||||
<InventoryLookup
|
||||
value={inventory}
|
||||
onBlur={() => form.setFieldTouched('inventory')}
|
||||
tooltip={i18n._(t`Select the inventory containing the hosts
|
||||
you want this job to manage.`)}
|
||||
isValid={!form.touched.inventory || !form.errors.inventory}
|
||||
helperTextInvalid={form.errors.inventory}
|
||||
onChange={value => {
|
||||
form.setFieldValue('inventory', value.id);
|
||||
form.setFieldValue('organizationId', value.organization);
|
||||
this.setState({ inventory: value });
|
||||
}}
|
||||
required
|
||||
touched={form.touched.inventory}
|
||||
error={form.errors.inventory}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
<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}
|
||||
/>
|
||||
{(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
|
||||
@ -288,12 +309,26 @@ class JobTemplateForm extends Component {
|
||||
)}
|
||||
</Field>
|
||||
{project && project.allow_override && (
|
||||
<FormField
|
||||
id="scm_branch"
|
||||
name="scm_branch"
|
||||
type="text"
|
||||
<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 (
|
||||
<TextInput
|
||||
id="template-scm-branch"
|
||||
{...field}
|
||||
onChange={(value, event) => {
|
||||
field.onChange(event);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</Field>
|
||||
</FieldWithPrompt>
|
||||
)}
|
||||
<Field
|
||||
name="playbook"
|
||||
@ -328,6 +363,29 @@ class JobTemplateForm extends Component {
|
||||
}}
|
||||
</Field>
|
||||
<FormFullWidthLayout>
|
||||
<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.`
|
||||
)}
|
||||
>
|
||||
<Field name="credentials" fieldId="template-credentials">
|
||||
{({ field }) => {
|
||||
return (
|
||||
<MultiCredentialsLookup
|
||||
value={field.value}
|
||||
onChange={newCredentials =>
|
||||
setFieldValue('credentials', newCredentials)
|
||||
}
|
||||
onError={this.setContentError}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</Field>
|
||||
</FieldWithPrompt>
|
||||
<Field name="labels">
|
||||
{({ field }) => (
|
||||
<FormGroup label={i18n._(t`Labels`)} fieldId="template-labels">
|
||||
@ -344,243 +402,248 @@ class JobTemplateForm extends Component {
|
||||
</FormGroup>
|
||||
)}
|
||||
</Field>
|
||||
<Field name="credentials" fieldId="template-credentials">
|
||||
{({ field }) => (
|
||||
<MultiCredentialsLookup
|
||||
value={field.value}
|
||||
onChange={newCredentials =>
|
||||
setFieldValue('credentials', newCredentials)
|
||||
}
|
||||
onError={this.setContentError}
|
||||
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>
|
||||
<AdvancedFieldsWrapper label="Advanced">
|
||||
<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>
|
||||
}
|
||||
/>
|
||||
<FormField
|
||||
id="template-limit"
|
||||
name="limit"
|
||||
type="text"
|
||||
label={i18n._(t`Limit`)}
|
||||
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.`)}
|
||||
/>
|
||||
<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>
|
||||
<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 }) => (
|
||||
<FormGroup
|
||||
fieldId="template-verbosity"
|
||||
label={i18n._(t`Verbosity`)}
|
||||
>
|
||||
<FieldTooltip
|
||||
content={i18n._(t`Control the level of output ansible will
|
||||
produce as the playbook executes.`)}
|
||||
/>
|
||||
<AnsibleSelect
|
||||
id="template-verbosity"
|
||||
data={verbosityOptions}
|
||||
{...field}
|
||||
/>
|
||||
</FormGroup>
|
||||
<AnsibleSelect
|
||||
id="template-verbosity"
|
||||
data={verbosityOptions}
|
||||
{...field}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
<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>
|
||||
<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.`)}
|
||||
>
|
||||
<Field name="diff_mode">
|
||||
{({ field, form }) => (
|
||||
<FormGroup
|
||||
fieldId="template-show-changes"
|
||||
label={i18n._(t`Show Changes`)}
|
||||
>
|
||||
<FieldTooltip
|
||||
content={i18n._(t`If enabled, show the changes made by
|
||||
Ansible tasks, where supported. This is equivalent
|
||||
to Ansible’s --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)
|
||||
}
|
||||
/>
|
||||
<div>
|
||||
<Switch
|
||||
id="template-show-changes"
|
||||
label={field.value ? i18n._(t`On`) : i18n._(t`Off`)}
|
||||
isChecked={field.value}
|
||||
onChange={checked =>
|
||||
form.setFieldValue(field.name, checked)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</FormGroup>
|
||||
);
|
||||
}}
|
||||
</Field>
|
||||
</FieldWithPrompt>
|
||||
<FormFullWidthLayout>
|
||||
<Field name="instanceGroups">
|
||||
{({ field, form }) => (
|
||||
<InstanceGroupsLookup
|
||||
value={field.value}
|
||||
onChange={value => form.setFieldValue(field.name, value)}
|
||||
tooltip={i18n._(t`Select the Instance Groups for this Organization
|
||||
to run on.`)}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
<FormFullWidthLayout>
|
||||
<Field name="instanceGroups">
|
||||
<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 }) => (
|
||||
<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>
|
||||
<Field name="job_tags">
|
||||
{({ field, form }) => (
|
||||
<FormGroup
|
||||
label={i18n._(t`Job Tags`)}
|
||||
fieldId="template-job-tags"
|
||||
>
|
||||
<FieldTooltip
|
||||
content={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.`)}
|
||||
/>
|
||||
<TagMultiSelect
|
||||
value={field.value}
|
||||
onChange={value =>
|
||||
form.setFieldValue(field.name, value)
|
||||
}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
</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 }) => (
|
||||
<FormGroup
|
||||
label={i18n._(t`Skip Tags`)}
|
||||
fieldId="template-skip-tags"
|
||||
>
|
||||
<FieldTooltip
|
||||
content={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.`)}
|
||||
/>
|
||||
<TagMultiSelect
|
||||
value={field.value}
|
||||
onChange={value =>
|
||||
form.setFieldValue(field.name, value)
|
||||
}
|
||||
/>
|
||||
</FormGroup>
|
||||
<TagMultiSelect
|
||||
value={field.value}
|
||||
onChange={value =>
|
||||
form.setFieldValue(field.name, value)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
<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
|
||||
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
|
||||
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
|
||||
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}
|
||||
</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.`)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</FormColumnLayout>
|
||||
</AdvancedFieldsWrapper>
|
||||
<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
|
||||
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
|
||||
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} />
|
||||
@ -603,7 +666,16 @@ const FormikApp = withFormik({
|
||||
? 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',
|
||||
@ -629,6 +701,7 @@ const FormikApp = withFormik({
|
||||
initialInstanceGroups: [],
|
||||
instanceGroups: [],
|
||||
credentials: summary_fields.credentials || [],
|
||||
extra_vars: template.extra_vars || '---\n',
|
||||
};
|
||||
},
|
||||
handleSubmit: async (values, { props, setErrors }) => {
|
||||
@ -638,6 +711,20 @@ const FormikApp = withFormik({
|
||||
setErrors(errors);
|
||||
}
|
||||
},
|
||||
validate: (values, { i18n }) => {
|
||||
const errors = {};
|
||||
|
||||
if (
|
||||
(!values.inventory || values.inventory === '') &&
|
||||
!values.ask_inventory_on_launch
|
||||
) {
|
||||
errors.inventory = i18n._(
|
||||
t`Please select an Inventory or check the Prompt on Launch option.`
|
||||
);
|
||||
}
|
||||
|
||||
return errors;
|
||||
},
|
||||
})(JobTemplateForm);
|
||||
|
||||
export { JobTemplateForm as _JobTemplateForm };
|
||||
|
||||
@ -140,13 +140,10 @@ describe('<JobTemplateForm />', () => {
|
||||
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('<JobTemplateForm />', () => {
|
||||
});
|
||||
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('<JobTemplateForm />', () => {
|
||||
);
|
||||
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('<JobTemplateForm />', () => {
|
||||
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');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user