mirror of
https://github.com/ansible/awx.git
synced 2026-01-11 18:09:57 -03:30
Add playbook select and project field validation
This commit is contained in:
parent
e19035079e
commit
156d03fa45
@ -4,6 +4,12 @@ class Projects extends Base {
|
||||
constructor(http) {
|
||||
super(http);
|
||||
this.baseUrl = '/api/v2/projects/';
|
||||
|
||||
this.readPlaybooks = this.readPlaybooks.bind(this);
|
||||
}
|
||||
|
||||
readPlaybooks(id) {
|
||||
return this.http.get(`${this.baseUrl}${id}/playbooks/`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -17,13 +17,16 @@ class AnsibleSelect extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { value, data, i18n } = this.props;
|
||||
const { id, data, i18n, isValid, onBlur, value } = this.props;
|
||||
|
||||
return (
|
||||
<FormSelect
|
||||
id={id}
|
||||
value={value}
|
||||
onChange={this.onSelectChange}
|
||||
onBlur={onBlur}
|
||||
aria-label={i18n._(t`Select Input`)}
|
||||
isValid={isValid}
|
||||
>
|
||||
{data.map(datum => (
|
||||
<FormSelectOption
|
||||
@ -40,10 +43,15 @@ class AnsibleSelect extends React.Component {
|
||||
|
||||
AnsibleSelect.defaultProps = {
|
||||
data: [],
|
||||
isValid: true,
|
||||
onBlur: () => {},
|
||||
};
|
||||
|
||||
AnsibleSelect.propTypes = {
|
||||
data: PropTypes.arrayOf(PropTypes.object),
|
||||
id: PropTypes.string.isRequired,
|
||||
isValid: PropTypes.bool,
|
||||
onBlur: PropTypes.func,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
@ -19,6 +19,7 @@ describe('<AnsibleSelect />', () => {
|
||||
test('initially renders succesfully', async () => {
|
||||
mountWithContexts(
|
||||
<AnsibleSelect
|
||||
id="bar"
|
||||
value="foo"
|
||||
name="bar"
|
||||
onChange={() => {}}
|
||||
@ -31,6 +32,7 @@ describe('<AnsibleSelect />', () => {
|
||||
const spy = jest.spyOn(_AnsibleSelect.prototype, 'onSelectChange');
|
||||
const wrapper = mountWithContexts(
|
||||
<AnsibleSelect
|
||||
id="bar"
|
||||
value="foo"
|
||||
name="bar"
|
||||
onChange={() => {}}
|
||||
@ -45,6 +47,7 @@ describe('<AnsibleSelect />', () => {
|
||||
test('Returns correct select options', () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<AnsibleSelect
|
||||
id="bar"
|
||||
value="foo"
|
||||
name="bar"
|
||||
onChange={() => {}}
|
||||
|
||||
@ -186,6 +186,7 @@ class Lookup extends React.Component {
|
||||
columns,
|
||||
multiple,
|
||||
name,
|
||||
onBlur,
|
||||
required,
|
||||
i18n,
|
||||
} = this.props;
|
||||
@ -209,7 +210,7 @@ class Lookup extends React.Component {
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<InputGroup>
|
||||
<InputGroup onBlur={onBlur}>
|
||||
<Button
|
||||
aria-label="Search"
|
||||
id={id}
|
||||
|
||||
@ -4,7 +4,7 @@ import { withRouter, Redirect } from 'react-router-dom';
|
||||
import { CardBody } from '@patternfly/react-core';
|
||||
import ContentError from '@components/ContentError';
|
||||
import ContentLoading from '@components/ContentLoading';
|
||||
import { JobTemplatesAPI } from '@api';
|
||||
import { JobTemplatesAPI, ProjectsAPI } from '@api';
|
||||
import { JobTemplate } from '@types';
|
||||
import JobTemplateForm from '../shared/JobTemplateForm';
|
||||
|
||||
@ -21,6 +21,7 @@ class JobTemplateEdit extends Component {
|
||||
contentError: null,
|
||||
formSubmitError: null,
|
||||
relatedCredentials: [],
|
||||
relatedProjectPlaybooks: [],
|
||||
};
|
||||
|
||||
const {
|
||||
@ -31,6 +32,9 @@ class JobTemplateEdit extends Component {
|
||||
this.handleCancel = this.handleCancel.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.loadRelatedCredentials = this.loadRelatedCredentials.bind(this);
|
||||
this.loadRelatedProjectPlaybooks = this.loadRelatedProjectPlaybooks.bind(
|
||||
this
|
||||
);
|
||||
this.submitLabels = this.submitLabels.bind(this);
|
||||
}
|
||||
|
||||
@ -41,15 +45,13 @@ class JobTemplateEdit extends Component {
|
||||
async loadRelated() {
|
||||
this.setState({ contentError: null, hasContentLoading: true });
|
||||
try {
|
||||
const [
|
||||
relatedCredentials,
|
||||
// relatedProjectPlaybooks,
|
||||
] = await Promise.all([
|
||||
const [relatedCredentials, relatedProjectPlaybooks] = await Promise.all([
|
||||
this.loadRelatedCredentials(),
|
||||
// this.loadRelatedProjectPlaybooks(),
|
||||
this.loadRelatedProjectPlaybooks(),
|
||||
]);
|
||||
this.setState({
|
||||
relatedCredentials,
|
||||
relatedProjectPlaybooks,
|
||||
});
|
||||
} catch (contentError) {
|
||||
this.setState({ contentError });
|
||||
@ -86,7 +88,20 @@ class JobTemplateEdit extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
async handleSubmit(values, newLabels = [], removedLabels = []) {
|
||||
async loadRelatedProjectPlaybooks() {
|
||||
const {
|
||||
template: { project },
|
||||
} = this.props;
|
||||
try {
|
||||
const { data: playbooks = [] } = await ProjectsAPI.readPlaybooks(project);
|
||||
this.setState({ relatedProjectPlaybooks: playbooks });
|
||||
return playbooks;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async handleSubmit(values) {
|
||||
const {
|
||||
template: { id },
|
||||
history,
|
||||
@ -95,14 +110,16 @@ class JobTemplateEdit extends Component {
|
||||
this.setState({ formSubmitError: null });
|
||||
try {
|
||||
await JobTemplatesAPI.update(id, { ...values });
|
||||
await Promise.all([this.submitLabels(newLabels, removedLabels)]);
|
||||
await Promise.all([
|
||||
this.submitLabels(values.newLabels, values.removedLabels),
|
||||
]);
|
||||
history.push(this.detailsUrl);
|
||||
} catch (formSubmitError) {
|
||||
this.setState({ formSubmitError });
|
||||
}
|
||||
}
|
||||
|
||||
async submitLabels(newLabels, removedLabels) {
|
||||
async submitLabels(newLabels = [], removedLabels = []) {
|
||||
const {
|
||||
template: { id },
|
||||
} = this.props;
|
||||
@ -131,7 +148,12 @@ class JobTemplateEdit extends Component {
|
||||
|
||||
render() {
|
||||
const { template } = this.props;
|
||||
const { contentError, formSubmitError, hasContentLoading } = this.state;
|
||||
const {
|
||||
contentError,
|
||||
formSubmitError,
|
||||
hasContentLoading,
|
||||
relatedProjectPlaybooks,
|
||||
} = this.state;
|
||||
const canEdit = template.summary_fields.user_capabilities.edit;
|
||||
|
||||
if (hasContentLoading) {
|
||||
@ -152,6 +174,7 @@ class JobTemplateEdit extends Component {
|
||||
template={template}
|
||||
handleCancel={this.handleCancel}
|
||||
handleSubmit={this.handleSubmit}
|
||||
relatedProjectPlaybooks={relatedProjectPlaybooks}
|
||||
/>
|
||||
{formSubmitError ? <div> error </div> : null}
|
||||
</CardBody>
|
||||
|
||||
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { Formik, Field } from 'formik';
|
||||
import { withFormik, Field } from 'formik';
|
||||
import { Form, FormGroup, Tooltip, Card } from '@patternfly/react-core';
|
||||
import { QuestionCircleIcon as PFQuestionCircleIcon } from '@patternfly/react-icons';
|
||||
import ContentError from '@components/ContentError';
|
||||
@ -18,7 +18,7 @@ import styled from 'styled-components';
|
||||
import { JobTemplate } from '@types';
|
||||
import InventoriesLookup from './InventoriesLookup';
|
||||
import ProjectLookup from './ProjectLookup';
|
||||
import { LabelsAPI } from '@api';
|
||||
import { LabelsAPI, ProjectsAPI } from '@api';
|
||||
|
||||
const QuestionCircleIcon = styled(PFQuestionCircleIcon)`
|
||||
margin-left: 10px;
|
||||
@ -47,6 +47,7 @@ class JobTemplateForm extends Component {
|
||||
summary_fields: {
|
||||
inventory: null,
|
||||
labels: { results: [] },
|
||||
project: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -61,14 +62,21 @@ class JobTemplateForm extends Component {
|
||||
removedLabels: [],
|
||||
project: props.template.summary_fields.project,
|
||||
inventory: props.template.summary_fields.inventory,
|
||||
relatedProjectPlaybooks: props.relatedProjectPlaybooks,
|
||||
};
|
||||
this.handleNewLabel = this.handleNewLabel.bind(this);
|
||||
this.loadLabels = this.loadLabels.bind(this);
|
||||
this.removeLabel = this.removeLabel.bind(this);
|
||||
this.handleProjectValidation = this.handleProjectValidation.bind(this);
|
||||
this.loadRelatedProjectPlaybooks = this.loadRelatedProjectPlaybooks.bind(
|
||||
this
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.loadLabels(QSConfig);
|
||||
async componentDidMount() {
|
||||
const { validateField } = this.props;
|
||||
await this.loadLabels(QSConfig);
|
||||
validateField('project');
|
||||
}
|
||||
|
||||
async loadLabels(QueryConfig) {
|
||||
@ -101,7 +109,7 @@ class JobTemplateForm extends Component {
|
||||
|
||||
handleNewLabel(label) {
|
||||
const { newLabels } = this.state;
|
||||
const { template } = this.props;
|
||||
const { template, setFieldValue } = this.props;
|
||||
const isIncluded = newLabels.some(newLabel => newLabel.name === label.name);
|
||||
if (isIncluded) {
|
||||
const filteredLabels = newLabels.filter(
|
||||
@ -109,6 +117,13 @@ class JobTemplateForm extends Component {
|
||||
);
|
||||
this.setState({ newLabels: filteredLabels });
|
||||
} else if (typeof label === 'string') {
|
||||
setFieldValue('newLabels', [
|
||||
...newLabels,
|
||||
{
|
||||
name: label,
|
||||
organization: template.summary_fields.inventory.organization_id,
|
||||
},
|
||||
]);
|
||||
this.setState({
|
||||
newLabels: [
|
||||
...newLabels,
|
||||
@ -119,6 +134,10 @@ class JobTemplateForm extends Component {
|
||||
],
|
||||
});
|
||||
} else {
|
||||
setFieldValue('newLabels', [
|
||||
...newLabels,
|
||||
{ name: label.name, associate: true, id: label.id },
|
||||
]);
|
||||
this.setState({
|
||||
newLabels: [
|
||||
...newLabels,
|
||||
@ -130,13 +149,20 @@ class JobTemplateForm extends Component {
|
||||
|
||||
removeLabel(label) {
|
||||
const { removedLabels, newLabels } = this.state;
|
||||
const { template } = this.props;
|
||||
const { template, setFieldValue } = this.props;
|
||||
|
||||
const isAssociatedLabel = template.summary_fields.labels.results.some(
|
||||
tempLabel => tempLabel.id === label.id
|
||||
);
|
||||
|
||||
if (isAssociatedLabel) {
|
||||
setFieldValue(
|
||||
'removedLabels',
|
||||
removedLabels.concat({
|
||||
disassociate: true,
|
||||
id: label.id,
|
||||
})
|
||||
);
|
||||
this.setState({
|
||||
removedLabels: removedLabels.concat({
|
||||
disassociate: true,
|
||||
@ -147,10 +173,34 @@ class JobTemplateForm extends Component {
|
||||
const filteredLabels = newLabels.filter(
|
||||
newLabel => newLabel.name !== label.name
|
||||
);
|
||||
setFieldValue('newLabels', filteredLabels);
|
||||
this.setState({ newLabels: filteredLabels });
|
||||
}
|
||||
}
|
||||
|
||||
async loadRelatedProjectPlaybooks(project) {
|
||||
try {
|
||||
const { data: playbooks = [] } = await ProjectsAPI.readPlaybooks(project);
|
||||
this.setState({ relatedProjectPlaybooks: playbooks });
|
||||
} catch (contentError) {
|
||||
this.setState({ contentError });
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
loadedLabels,
|
||||
@ -158,10 +208,15 @@ class JobTemplateForm extends Component {
|
||||
hasContentLoading,
|
||||
inventory,
|
||||
project,
|
||||
newLabels,
|
||||
removedLabels,
|
||||
relatedProjectPlaybooks = [],
|
||||
} = this.state;
|
||||
const { handleCancel, handleSubmit, i18n, template } = this.props;
|
||||
const {
|
||||
handleCancel,
|
||||
handleSubmit,
|
||||
handleBlur,
|
||||
i18n,
|
||||
template,
|
||||
} = this.props;
|
||||
const jobTypeOptions = [
|
||||
{
|
||||
value: '',
|
||||
@ -177,6 +232,28 @@ class JobTemplateForm extends Component {
|
||||
isDisabled: false,
|
||||
},
|
||||
];
|
||||
const playbookOptions = relatedProjectPlaybooks
|
||||
.map(playbook => {
|
||||
return {
|
||||
value: playbook,
|
||||
key: playbook,
|
||||
label: playbook,
|
||||
isDisabled: false,
|
||||
};
|
||||
})
|
||||
.reduce(
|
||||
(arr, playbook) => {
|
||||
return arr.concat(playbook);
|
||||
},
|
||||
[
|
||||
{
|
||||
value: '',
|
||||
key: '',
|
||||
label: i18n._(t`Choose a playbook`),
|
||||
isDisabled: false,
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
if (hasContentLoading) {
|
||||
return (
|
||||
@ -195,129 +272,180 @@ class JobTemplateForm extends Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<Formik
|
||||
initialValues={{
|
||||
name: template.name,
|
||||
description: template.description,
|
||||
job_type: template.job_type,
|
||||
inventory: template.inventory || '',
|
||||
project: template.project || '',
|
||||
playbook: template.playbook,
|
||||
labels: template.summary_fields.labels.results,
|
||||
}}
|
||||
onSubmit={values => {
|
||||
handleSubmit(values, newLabels, removedLabels);
|
||||
}}
|
||||
render={formik => (
|
||||
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
|
||||
<FormRow>
|
||||
<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`)}
|
||||
/>
|
||||
<Field
|
||||
name="job_type"
|
||||
validate={required(null, i18n)}
|
||||
render={({ field }) => (
|
||||
<FormGroup
|
||||
fieldId="template-job-type"
|
||||
isRequired
|
||||
label={i18n._(t`Job Type`)}
|
||||
>
|
||||
<Tooltip
|
||||
position="right"
|
||||
content={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.`)}
|
||||
>
|
||||
<QuestionCircleIcon />
|
||||
</Tooltip>
|
||||
<AnsibleSelect data={jobTypeOptions} {...field} />
|
||||
</FormGroup>
|
||||
)}
|
||||
/>
|
||||
<Field
|
||||
name="inventory"
|
||||
validate={required(null, i18n)}
|
||||
render={({ form }) => (
|
||||
<InventoriesLookup
|
||||
value={inventory}
|
||||
tooltip={i18n._(t`Select the inventory containing the hosts
|
||||
you want this job to manage.`)}
|
||||
onChange={value => {
|
||||
form.setFieldValue('inventory', value.id);
|
||||
this.setState({ inventory: value });
|
||||
}}
|
||||
required
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Field
|
||||
name="project"
|
||||
validate={required(null, i18n)}
|
||||
render={({ form }) => (
|
||||
<ProjectLookup
|
||||
value={project}
|
||||
tooltip={i18n._(t`Select the project containing the playbook
|
||||
you want this job to execute.`)}
|
||||
onChange={value => {
|
||||
form.setFieldValue('project', value.id);
|
||||
this.setState({ project: value });
|
||||
}}
|
||||
required
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
id="template-playbook"
|
||||
name="playbook"
|
||||
type="text"
|
||||
label={i18n._(t`Playbook`)}
|
||||
tooltip={i18n._(
|
||||
t`Select the playbook to be executed by this job.`
|
||||
)}
|
||||
isRequired
|
||||
validate={required(null, i18n)}
|
||||
/>
|
||||
</FormRow>
|
||||
<FormRow>
|
||||
<FormGroup label={i18n._(t`Labels`)} fieldId="template-labels">
|
||||
<Tooltip
|
||||
position="right"
|
||||
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.`
|
||||
)}
|
||||
<Form autoComplete="off" onSubmit={handleSubmit}>
|
||||
<FormRow>
|
||||
<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`)}
|
||||
/>
|
||||
<Field
|
||||
name="job_type"
|
||||
validate={required(null, i18n)}
|
||||
onBlur={handleBlur}
|
||||
render={({ form, field }) => {
|
||||
const isValid =
|
||||
form && (!form.touched[field.name] || !form.errors[field.name]);
|
||||
return (
|
||||
<FormGroup
|
||||
fieldId="template-job-type"
|
||||
helperTextInvalid={form.errors.job_type}
|
||||
isRequired
|
||||
isValid={isValid}
|
||||
label={i18n._(t`Job Type`)}
|
||||
>
|
||||
<QuestionCircleIcon />
|
||||
</Tooltip>
|
||||
<MultiSelect
|
||||
onAddNewItem={this.handleNewLabel}
|
||||
onRemoveItem={this.removeLabel}
|
||||
associatedItems={template.summary_fields.labels.results}
|
||||
options={loadedLabels}
|
||||
<Tooltip
|
||||
position="right"
|
||||
content={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.`)}
|
||||
>
|
||||
<QuestionCircleIcon />
|
||||
</Tooltip>
|
||||
<AnsibleSelect
|
||||
isValid={isValid}
|
||||
id="job_type"
|
||||
data={jobTypeOptions}
|
||||
{...field}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Field
|
||||
name="inventory"
|
||||
validate={required(null, i18n)}
|
||||
render={({ form }) => (
|
||||
<InventoriesLookup
|
||||
value={inventory}
|
||||
tooltip={i18n._(t`Select the inventory containing the hosts
|
||||
you want this job to manage.`)}
|
||||
onChange={value => {
|
||||
form.setFieldValue('inventory', value.id);
|
||||
this.setState({ inventory: value });
|
||||
}}
|
||||
required
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Field
|
||||
name="project"
|
||||
validate={this.handleProjectValidation()}
|
||||
render={({ form }) => {
|
||||
const isValid = form && !form.errors.project;
|
||||
return (
|
||||
<ProjectLookup
|
||||
helperTextInvalid={form.errors.project}
|
||||
isValid={isValid}
|
||||
value={project}
|
||||
onBlur={handleBlur}
|
||||
tooltip={i18n._(t`Select the project containing the playbook
|
||||
you want this job to execute.`)}
|
||||
onChange={value => {
|
||||
this.loadRelatedProjectPlaybooks(value.id);
|
||||
form.setFieldValue('project', value.id);
|
||||
form.setFieldTouched('project');
|
||||
this.setState({ project: value });
|
||||
}}
|
||||
required
|
||||
/>
|
||||
</FormGroup>
|
||||
</FormRow>
|
||||
<FormActionGroup
|
||||
onCancel={handleCancel}
|
||||
onSubmit={formik.handleSubmit}
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Field
|
||||
name="playbook"
|
||||
validate={required(i18n._(t`Select a value for this field`), i18n)}
|
||||
onBlur={handleBlur}
|
||||
render={({ field, form }) => {
|
||||
const isValid =
|
||||
form && (!form.touched[field.name] || !form.errors[field.name]);
|
||||
return (
|
||||
<FormGroup
|
||||
fieldId="template-playbook"
|
||||
helperTextInvalid={form.errors.playbook}
|
||||
isRequired
|
||||
isValid={isValid}
|
||||
label={i18n._(t`Playbook`)}
|
||||
>
|
||||
<Tooltip
|
||||
position="right"
|
||||
content={i18n._(
|
||||
t`Select the playbook to be executed by this job.`
|
||||
)}
|
||||
>
|
||||
<QuestionCircleIcon />
|
||||
</Tooltip>
|
||||
<AnsibleSelect
|
||||
id="playbook"
|
||||
data={playbookOptions}
|
||||
isValid={isValid}
|
||||
form={form}
|
||||
{...field}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</FormRow>
|
||||
<FormRow>
|
||||
<FormGroup label={i18n._(t`Labels`)} fieldId="template-labels">
|
||||
<Tooltip
|
||||
position="right"
|
||||
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.`
|
||||
)}
|
||||
>
|
||||
<QuestionCircleIcon />
|
||||
</Tooltip>
|
||||
<MultiSelect
|
||||
onAddNewItem={this.handleNewLabel}
|
||||
onRemoveItem={this.removeLabel}
|
||||
associatedItems={template.summary_fields.labels.results}
|
||||
options={loadedLabels}
|
||||
/>
|
||||
</Form>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
</FormRow>
|
||||
<FormActionGroup onCancel={handleCancel} onSubmit={handleSubmit} />
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const FormikApp = withFormik({
|
||||
mapPropsToValues(props) {
|
||||
const { template = {} } = props;
|
||||
const {
|
||||
name = '',
|
||||
description = '',
|
||||
job_type = '',
|
||||
inventory = '',
|
||||
playbook = '',
|
||||
project = '',
|
||||
summary_fields = { labels: { results: [] } },
|
||||
} = { ...template };
|
||||
|
||||
return {
|
||||
name: name || '',
|
||||
description: description || '',
|
||||
job_type: job_type || '',
|
||||
inventory: inventory || '',
|
||||
project: project || '',
|
||||
playbook: playbook || '',
|
||||
labels: summary_fields.labels.results,
|
||||
};
|
||||
},
|
||||
handleSubmit: (values, bag) => bag.props.handleSubmit(values),
|
||||
})(JobTemplateForm);
|
||||
|
||||
export { JobTemplateForm as _JobTemplateForm };
|
||||
export default withI18n()(withRouter(JobTemplateForm));
|
||||
export default withI18n()(withRouter(FormikApp));
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
|
||||
import { sleep } from '@testUtils/testUtils';
|
||||
import { shallow } from 'enzyme';
|
||||
import JobTemplateForm, { _JobTemplateForm } from './JobTemplateForm';
|
||||
import { LabelsAPI } from '@api';
|
||||
|
||||
@ -41,12 +42,15 @@ describe('<JobTemplateForm />', () => {
|
||||
|
||||
test('initially renders successfully', async done => {
|
||||
const wrapper = mountWithContexts(
|
||||
<JobTemplateForm
|
||||
template={mockData}
|
||||
handleSubmit={jest.fn()}
|
||||
handleCancel={jest.fn()}
|
||||
/>
|
||||
shallow(
|
||||
<JobTemplateForm
|
||||
template={mockData}
|
||||
handleSubmit={jest.fn()}
|
||||
handleCancel={jest.fn()}
|
||||
/>
|
||||
).get(0)
|
||||
);
|
||||
|
||||
await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0);
|
||||
expect(LabelsAPI.read).toHaveBeenCalled();
|
||||
expect(
|
||||
@ -60,11 +64,13 @@ describe('<JobTemplateForm />', () => {
|
||||
|
||||
test('should update form values on input changes', async done => {
|
||||
const wrapper = mountWithContexts(
|
||||
<JobTemplateForm
|
||||
template={mockData}
|
||||
handleSubmit={jest.fn()}
|
||||
handleCancel={jest.fn()}
|
||||
/>
|
||||
shallow(
|
||||
<JobTemplateForm
|
||||
template={mockData}
|
||||
handleSubmit={jest.fn()}
|
||||
handleCancel={jest.fn()}
|
||||
/>
|
||||
).get(0)
|
||||
);
|
||||
await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0);
|
||||
const form = wrapper.find('Formik');
|
||||
@ -90,7 +96,7 @@ describe('<JobTemplateForm />', () => {
|
||||
name: 'project',
|
||||
});
|
||||
expect(form.state('values').project).toEqual(4);
|
||||
wrapper.find('input#template-playbook').simulate('change', {
|
||||
wrapper.find('AnsibleSelect[name="playbook"]').simulate('change', {
|
||||
target: { value: 'new baz type', name: 'playbook' },
|
||||
});
|
||||
expect(form.state('values').playbook).toEqual('new baz type');
|
||||
|
||||
@ -17,12 +17,23 @@ const loadProjects = async params => ProjectsAPI.read(params);
|
||||
|
||||
class ProjectLookup extends React.Component {
|
||||
render() {
|
||||
const { value, tooltip, onChange, required, i18n } = this.props;
|
||||
const {
|
||||
helperTextInvalid,
|
||||
i18n,
|
||||
isValid,
|
||||
onChange,
|
||||
required,
|
||||
tooltip,
|
||||
value,
|
||||
onBlur,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<FormGroup
|
||||
fieldId="project-lookup"
|
||||
fieldId="project"
|
||||
helperTextInvalid={helperTextInvalid}
|
||||
isRequired={required}
|
||||
isValid={isValid}
|
||||
label={i18n._(t`Project`)}
|
||||
>
|
||||
{tooltip && (
|
||||
@ -31,10 +42,11 @@ class ProjectLookup extends React.Component {
|
||||
</Tooltip>
|
||||
)}
|
||||
<Lookup
|
||||
id="project-lookup"
|
||||
id="project"
|
||||
lookupHeader={i18n._(t`Projects`)}
|
||||
name="project"
|
||||
value={value}
|
||||
onBlur={onBlur}
|
||||
onLookupSave={onChange}
|
||||
getItems={loadProjects}
|
||||
required={required}
|
||||
@ -47,15 +59,21 @@ class ProjectLookup extends React.Component {
|
||||
|
||||
ProjectLookup.propTypes = {
|
||||
value: Project,
|
||||
tooltip: string,
|
||||
helperTextInvalid: string,
|
||||
isValid: bool,
|
||||
onBlur: func,
|
||||
onChange: func.isRequired,
|
||||
required: bool,
|
||||
tooltip: string,
|
||||
};
|
||||
|
||||
ProjectLookup.defaultProps = {
|
||||
value: null,
|
||||
tooltip: '',
|
||||
helperTextInvalid: '',
|
||||
isValid: true,
|
||||
required: false,
|
||||
tooltip: '',
|
||||
value: null,
|
||||
onBlur: () => {},
|
||||
};
|
||||
|
||||
export default withI18n()(ProjectLookup);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user