mirror of
https://github.com/ansible/awx.git
synced 2026-05-13 12:27:37 -02:30
Add project manual scm type subform
This commit is contained in:
@@ -97,7 +97,13 @@ class App extends Component {
|
|||||||
MeAPI.read(),
|
MeAPI.read(),
|
||||||
]);
|
]);
|
||||||
const {
|
const {
|
||||||
data: { ansible_version, custom_virtualenvs, version },
|
data: {
|
||||||
|
ansible_version,
|
||||||
|
custom_virtualenvs,
|
||||||
|
project_base_dir,
|
||||||
|
project_local_paths,
|
||||||
|
version,
|
||||||
|
},
|
||||||
} = configRes;
|
} = configRes;
|
||||||
const {
|
const {
|
||||||
data: {
|
data: {
|
||||||
@@ -105,7 +111,14 @@ class App extends Component {
|
|||||||
},
|
},
|
||||||
} = meRes;
|
} = meRes;
|
||||||
|
|
||||||
this.setState({ ansible_version, custom_virtualenvs, version, me });
|
this.setState({
|
||||||
|
ansible_version,
|
||||||
|
custom_virtualenvs,
|
||||||
|
project_base_dir,
|
||||||
|
project_local_paths,
|
||||||
|
version,
|
||||||
|
me,
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.setState({ configError: err });
|
this.setState({ configError: err });
|
||||||
}
|
}
|
||||||
@@ -115,6 +128,8 @@ class App extends Component {
|
|||||||
const {
|
const {
|
||||||
ansible_version,
|
ansible_version,
|
||||||
custom_virtualenvs,
|
custom_virtualenvs,
|
||||||
|
project_base_dir,
|
||||||
|
project_local_paths,
|
||||||
isAboutModalOpen,
|
isAboutModalOpen,
|
||||||
isNavOpen,
|
isNavOpen,
|
||||||
me,
|
me,
|
||||||
@@ -169,7 +184,14 @@ class App extends Component {
|
|||||||
<Fragment>
|
<Fragment>
|
||||||
<Page usecondensed="True" header={header} sidebar={sidebar}>
|
<Page usecondensed="True" header={header} sidebar={sidebar}>
|
||||||
<ConfigProvider
|
<ConfigProvider
|
||||||
value={{ ansible_version, custom_virtualenvs, me, version }}
|
value={{
|
||||||
|
ansible_version,
|
||||||
|
custom_virtualenvs,
|
||||||
|
project_base_dir,
|
||||||
|
project_local_paths,
|
||||||
|
me,
|
||||||
|
version,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{render({ routeGroups })}
|
{render({ routeGroups })}
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
|
|||||||
@@ -189,10 +189,6 @@
|
|||||||
z-index: 20;
|
z-index: 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pf-c-alert__icon {
|
|
||||||
--pf-c-alert__icon--Color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.at-u-textRight {
|
.at-u-textRight {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ function ProjectAdd({ history, i18n }) {
|
|||||||
const [formSubmitError, setFormSubmitError] = useState(null);
|
const [formSubmitError, setFormSubmitError] = useState(null);
|
||||||
|
|
||||||
const handleSubmit = async values => {
|
const handleSubmit = async values => {
|
||||||
|
if (values.scm_type === 'manual') {
|
||||||
|
values.scm_type = '';
|
||||||
|
}
|
||||||
setFormSubmitError(null);
|
setFormSubmitError(null);
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ describe('<ProjectAdd />', () => {
|
|||||||
scm_url: 'https://foo.bar',
|
scm_url: 'https://foo.bar',
|
||||||
scm_clean: true,
|
scm_clean: true,
|
||||||
credential: 100,
|
credential: 100,
|
||||||
|
local_path: '',
|
||||||
organization: 2,
|
organization: 2,
|
||||||
scm_update_on_launch: true,
|
scm_update_on_launch: true,
|
||||||
scm_update_cache_timeout: 3,
|
scm_update_cache_timeout: 3,
|
||||||
@@ -116,9 +117,15 @@ describe('<ProjectAdd />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('handleSubmit should throw an error', async () => {
|
test('handleSubmit should throw an error', async () => {
|
||||||
|
const config = {
|
||||||
|
project_local_paths: ['foobar', 'qux'],
|
||||||
|
project_base_dir: 'dir/foo/bar',
|
||||||
|
};
|
||||||
ProjectsAPI.create.mockImplementation(() => Promise.reject(new Error()));
|
ProjectsAPI.create.mockImplementation(() => Promise.reject(new Error()));
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(<ProjectAdd />);
|
wrapper = mountWithContexts(<ProjectAdd />, {
|
||||||
|
context: { config },
|
||||||
|
});
|
||||||
});
|
});
|
||||||
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||||
const formik = wrapper.find('Formik').instance();
|
const formik = wrapper.find('Formik').instance();
|
||||||
@@ -127,6 +134,7 @@ describe('<ProjectAdd />', () => {
|
|||||||
{
|
{
|
||||||
values: {
|
values: {
|
||||||
...projectData,
|
...projectData,
|
||||||
|
scm_type: 'manual',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
() => resolve()
|
() => resolve()
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import { t } from '@lingui/macro';
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { Project } from '@types';
|
import { Project } from '@types';
|
||||||
import { formatDateString } from '@util/dates';
|
import { formatDateString } from '@util/dates';
|
||||||
|
import { Config } from '@contexts/Config';
|
||||||
import { Button, CardBody, List, ListItem } from '@patternfly/react-core';
|
import { Button, CardBody, List, ListItem } from '@patternfly/react-core';
|
||||||
import { DetailList, Detail } from '@components/DetailList';
|
import { DetailList, Detail } from '@components/DetailList';
|
||||||
import { CredentialChip } from '@components/Chip';
|
import { CredentialChip } from '@components/Chip';
|
||||||
|
import { toTitleCase } from '@util/strings';
|
||||||
|
|
||||||
const ActionButtonWrapper = styled.div`
|
const ActionButtonWrapper = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -25,6 +27,7 @@ function ProjectDetail({ project, i18n }) {
|
|||||||
custom_virtualenv,
|
custom_virtualenv,
|
||||||
description,
|
description,
|
||||||
id,
|
id,
|
||||||
|
local_path,
|
||||||
modified,
|
modified,
|
||||||
name,
|
name,
|
||||||
scm_branch,
|
scm_branch,
|
||||||
@@ -93,10 +96,21 @@ function ProjectDetail({ project, i18n }) {
|
|||||||
{summary_fields.organization && (
|
{summary_fields.organization && (
|
||||||
<Detail
|
<Detail
|
||||||
label={i18n._(t`Organization`)}
|
label={i18n._(t`Organization`)}
|
||||||
value={summary_fields.organization.name}
|
value={
|
||||||
|
<Link
|
||||||
|
to={`/organizations/${summary_fields.organization.id}/details`}
|
||||||
|
>
|
||||||
|
{summary_fields.organization.name}
|
||||||
|
</Link>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Detail label={i18n._(t`SCM Type`)} value={scm_type} />
|
<Detail
|
||||||
|
label={i18n._(t`SCM Type`)}
|
||||||
|
value={
|
||||||
|
scm_type === '' ? i18n._(t`Manual`) : toTitleCase(project.scm_type)
|
||||||
|
}
|
||||||
|
/>
|
||||||
<Detail label={i18n._(t`SCM URL`)} value={scm_url} />
|
<Detail label={i18n._(t`SCM URL`)} value={scm_url} />
|
||||||
<Detail label={i18n._(t`SCM Branch`)} value={scm_branch} />
|
<Detail label={i18n._(t`SCM Branch`)} value={scm_branch} />
|
||||||
<Detail label={i18n._(t`SCM Refspec`)} value={scm_refspec} />
|
<Detail label={i18n._(t`SCM Refspec`)} value={scm_refspec} />
|
||||||
@@ -123,6 +137,15 @@ function ProjectDetail({ project, i18n }) {
|
|||||||
label={i18n._(t`Ansible Environment`)}
|
label={i18n._(t`Ansible Environment`)}
|
||||||
value={custom_virtualenv}
|
value={custom_virtualenv}
|
||||||
/>
|
/>
|
||||||
|
<Config>
|
||||||
|
{({ project_base_dir }) => (
|
||||||
|
<Detail
|
||||||
|
label={i18n._(t`Project Base Path`)}
|
||||||
|
value={project_base_dir}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Config>
|
||||||
|
<Detail label={i18n._(t`Playbook Directory`)} value={local_path} />
|
||||||
{/* TODO: Link to user in users */}
|
{/* TODO: Link to user in users */}
|
||||||
<Detail label={i18n._(t`Created`)} value={createdBy} />
|
<Detail label={i18n._(t`Created`)} value={createdBy} />
|
||||||
{/* TODO: Link to user in users */}
|
{/* TODO: Link to user in users */}
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ describe('<ProjectDetail />', () => {
|
|||||||
assertDetail('Name', mockProject.name);
|
assertDetail('Name', mockProject.name);
|
||||||
assertDetail('Description', mockProject.description);
|
assertDetail('Description', mockProject.description);
|
||||||
assertDetail('Organization', mockProject.summary_fields.organization.name);
|
assertDetail('Organization', mockProject.summary_fields.organization.name);
|
||||||
assertDetail('SCM Type', mockProject.scm_type);
|
assertDetail('SCM Type', 'Git');
|
||||||
assertDetail('SCM URL', mockProject.scm_url);
|
assertDetail('SCM URL', mockProject.scm_url);
|
||||||
assertDetail('SCM Branch', mockProject.scm_branch);
|
assertDetail('SCM Branch', mockProject.scm_branch);
|
||||||
assertDetail('SCM Refspec', mockProject.scm_refspec);
|
assertDetail('SCM Refspec', mockProject.scm_refspec);
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ function ProjectEdit({ project, history, i18n }) {
|
|||||||
const [formSubmitError, setFormSubmitError] = useState(null);
|
const [formSubmitError, setFormSubmitError] = useState(null);
|
||||||
|
|
||||||
const handleSubmit = async values => {
|
const handleSubmit = async values => {
|
||||||
|
if (values.scm_type === 'manual') {
|
||||||
|
values.scm_type = '';
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
data: { id },
|
data: { id },
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ describe('<ProjectEdit />', () => {
|
|||||||
scm_url: 'https://foo.bar',
|
scm_url: 'https://foo.bar',
|
||||||
scm_clean: true,
|
scm_clean: true,
|
||||||
credential: 100,
|
credential: 100,
|
||||||
|
local_path: '',
|
||||||
organization: 2,
|
organization: 2,
|
||||||
scm_update_on_launch: true,
|
scm_update_on_launch: true,
|
||||||
scm_update_cache_timeout: 3,
|
scm_update_cache_timeout: 3,
|
||||||
@@ -115,9 +116,18 @@ describe('<ProjectEdit />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('handleSubmit should throw an error', async () => {
|
test('handleSubmit should throw an error', async () => {
|
||||||
|
const config = {
|
||||||
|
project_local_paths: [],
|
||||||
|
project_base_dir: 'foo/bar',
|
||||||
|
};
|
||||||
ProjectsAPI.update.mockImplementation(() => Promise.reject(new Error()));
|
ProjectsAPI.update.mockImplementation(() => Promise.reject(new Error()));
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(<ProjectEdit project={projectData} />);
|
wrapper = mountWithContexts(
|
||||||
|
<ProjectEdit project={{ ...projectData, scm_type: 'manual' }} />,
|
||||||
|
{
|
||||||
|
context: { config },
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import ListActionButton from '@components/ListActionButton';
|
|||||||
import ProjectSyncButton from '../shared/ProjectSyncButton';
|
import ProjectSyncButton from '../shared/ProjectSyncButton';
|
||||||
import { StatusIcon } from '@components/Sparkline';
|
import { StatusIcon } from '@components/Sparkline';
|
||||||
import VerticalSeparator from '@components/VerticalSeparator';
|
import VerticalSeparator from '@components/VerticalSeparator';
|
||||||
|
import { toTitleCase } from '@util/strings';
|
||||||
import { Project } from '@types';
|
import { Project } from '@types';
|
||||||
|
|
||||||
class ProjectListItem extends React.Component {
|
class ProjectListItem extends React.Component {
|
||||||
@@ -97,7 +98,9 @@ class ProjectListItem extends React.Component {
|
|||||||
</Link>
|
</Link>
|
||||||
</DataListCell>,
|
</DataListCell>,
|
||||||
<DataListCell key="type">
|
<DataListCell key="type">
|
||||||
{project.scm_type.toUpperCase()}
|
{project.scm_type === ''
|
||||||
|
? i18n._(t`Manual`)
|
||||||
|
: toTitleCase(project.scm_type)}
|
||||||
</DataListCell>,
|
</DataListCell>,
|
||||||
<DataListCell key="revision">
|
<DataListCell key="revision">
|
||||||
{project.scm_revision.substring(0, 7)}
|
{project.scm_revision.substring(0, 7)}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
SvnSubForm,
|
SvnSubForm,
|
||||||
InsightsSubForm,
|
InsightsSubForm,
|
||||||
SubFormTitle,
|
SubFormTitle,
|
||||||
|
ManualSubForm,
|
||||||
} from './ProjectSubForms';
|
} from './ProjectSubForms';
|
||||||
|
|
||||||
const ScmTypeFormRow = styled(FormRow)`
|
const ScmTypeFormRow = styled(FormRow)`
|
||||||
@@ -173,199 +174,224 @@ function ProjectForm({ project, ...props }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Formik
|
<Config>
|
||||||
initialValues={{
|
{({ project_base_dir, project_local_paths }) => (
|
||||||
allow_override: project.allow_override || false,
|
<Formik
|
||||||
credential: project.credential || '',
|
initialValues={{
|
||||||
custom_virtualenv: project.custom_virtualenv || '',
|
allow_override: project.allow_override || false,
|
||||||
description: project.description || '',
|
base_dir: project_base_dir || '',
|
||||||
name: project.name || '',
|
credential: project.credential || '',
|
||||||
organization: project.organization || '',
|
custom_virtualenv: project.custom_virtualenv || '',
|
||||||
scm_branch: project.scm_branch || '',
|
description: project.description || '',
|
||||||
scm_clean: project.scm_clean || false,
|
local_path: project.local_path || null,
|
||||||
scm_delete_on_update: project.scm_delete_on_update || false,
|
name: project.name || '',
|
||||||
scm_refspec: project.scm_refspec || '',
|
organization: project.organization || '',
|
||||||
scm_type:
|
scm_branch: project.scm_branch || '',
|
||||||
project.scm_type === ''
|
scm_clean: project.scm_clean || false,
|
||||||
? 'manual'
|
scm_delete_on_update: project.scm_delete_on_update || false,
|
||||||
: project.scm_type === undefined
|
scm_refspec: project.scm_refspec || '',
|
||||||
? ''
|
scm_type:
|
||||||
: project.scm_type,
|
project.scm_type === ''
|
||||||
scm_update_cache_timeout: project.scm_update_cache_timeout || 0,
|
? 'manual'
|
||||||
scm_update_on_launch: project.scm_update_on_launch || false,
|
: project.scm_type === undefined
|
||||||
scm_url: project.scm_url || '',
|
? ''
|
||||||
}}
|
: project.scm_type,
|
||||||
onSubmit={handleSubmit}
|
scm_update_cache_timeout: project.scm_update_cache_timeout || 0,
|
||||||
render={formik => (
|
scm_update_on_launch: project.scm_update_on_launch || false,
|
||||||
<Form
|
scm_url: project.scm_url || '',
|
||||||
autoComplete="off"
|
}}
|
||||||
onSubmit={formik.handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
css="padding: 0 24px"
|
render={formik => (
|
||||||
>
|
<Form
|
||||||
<FormRow>
|
autoComplete="off"
|
||||||
<FormField
|
onSubmit={formik.handleSubmit}
|
||||||
id="project-name"
|
css="padding: 0 24px"
|
||||||
label={i18n._(t`Name`)}
|
>
|
||||||
name="name"
|
<FormRow>
|
||||||
type="text"
|
<FormField
|
||||||
validate={required(null, i18n)}
|
id="project-name"
|
||||||
isRequired
|
label={i18n._(t`Name`)}
|
||||||
/>
|
name="name"
|
||||||
<FormField
|
type="text"
|
||||||
id="project-description"
|
validate={required(null, i18n)}
|
||||||
label={i18n._(t`Description`)}
|
|
||||||
name="description"
|
|
||||||
type="text"
|
|
||||||
/>
|
|
||||||
<Field
|
|
||||||
name="organization"
|
|
||||||
validate={required(
|
|
||||||
i18n._(t`Select a value for this field`),
|
|
||||||
i18n
|
|
||||||
)}
|
|
||||||
render={({ form }) => (
|
|
||||||
<OrganizationLookup
|
|
||||||
helperTextInvalid={form.errors.organization}
|
|
||||||
isValid={
|
|
||||||
!form.touched.organization || !form.errors.organization
|
|
||||||
}
|
|
||||||
onBlur={() => form.setFieldTouched('organization')}
|
|
||||||
onChange={value => {
|
|
||||||
form.setFieldValue('organization', value.id);
|
|
||||||
setOrganization(value);
|
|
||||||
}}
|
|
||||||
value={organization}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Field
|
|
||||||
name="scm_type"
|
|
||||||
validate={required(
|
|
||||||
i18n._(t`Select a value for this field`),
|
|
||||||
i18n
|
|
||||||
)}
|
|
||||||
render={({ field, form }) => (
|
|
||||||
<FormGroup
|
|
||||||
fieldId="project-scm-type"
|
|
||||||
helperTextInvalid={form.errors.scm_type}
|
|
||||||
isRequired
|
isRequired
|
||||||
isValid={!form.touched.scm_type || !form.errors.scm_type}
|
/>
|
||||||
label={i18n._(t`SCM Type`)}
|
<FormField
|
||||||
>
|
id="project-description"
|
||||||
<AnsibleSelect
|
label={i18n._(t`Description`)}
|
||||||
{...field}
|
name="description"
|
||||||
id="scm_type"
|
type="text"
|
||||||
data={[
|
/>
|
||||||
|
<Field
|
||||||
|
name="organization"
|
||||||
|
validate={required(
|
||||||
|
i18n._(t`Select a value for this field`),
|
||||||
|
i18n
|
||||||
|
)}
|
||||||
|
render={({ form }) => (
|
||||||
|
<OrganizationLookup
|
||||||
|
helperTextInvalid={form.errors.organization}
|
||||||
|
isValid={
|
||||||
|
!form.touched.organization || !form.errors.organization
|
||||||
|
}
|
||||||
|
onBlur={() => form.setFieldTouched('organization')}
|
||||||
|
onChange={value => {
|
||||||
|
form.setFieldValue('organization', value.id);
|
||||||
|
setOrganization(value);
|
||||||
|
}}
|
||||||
|
value={organization}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Field
|
||||||
|
name="scm_type"
|
||||||
|
validate={required(
|
||||||
|
i18n._(t`Select a value for this field`),
|
||||||
|
i18n
|
||||||
|
)}
|
||||||
|
render={({ field, form }) => (
|
||||||
|
<FormGroup
|
||||||
|
fieldId="project-scm-type"
|
||||||
|
helperTextInvalid={form.errors.scm_type}
|
||||||
|
isRequired
|
||||||
|
isValid={!form.touched.scm_type || !form.errors.scm_type}
|
||||||
|
label={i18n._(t`SCM Type`)}
|
||||||
|
>
|
||||||
|
<AnsibleSelect
|
||||||
|
{...field}
|
||||||
|
id="scm_type"
|
||||||
|
data={[
|
||||||
|
{
|
||||||
|
value: '',
|
||||||
|
key: '',
|
||||||
|
label: i18n._(t`Choose an SCM Type`),
|
||||||
|
isDisabled: true,
|
||||||
|
},
|
||||||
|
...scmTypeOptions.map(([value, label]) => {
|
||||||
|
if (label === 'Manual') {
|
||||||
|
value = 'manual';
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
key: value,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
]}
|
||||||
|
onChange={(event, value) => {
|
||||||
|
form.setFieldValue('scm_type', value);
|
||||||
|
resetScmTypeFields(value, form);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{formik.values.scm_type !== '' && (
|
||||||
|
<ScmTypeFormRow>
|
||||||
|
<SubFormTitle size="md">
|
||||||
|
{i18n._(t`Type Details`)}
|
||||||
|
</SubFormTitle>
|
||||||
|
{
|
||||||
{
|
{
|
||||||
value: '',
|
manual: (
|
||||||
key: '',
|
<ManualSubForm
|
||||||
label: i18n._(t`Choose an SCM Type`),
|
localPath={formik.initialValues.local_path}
|
||||||
isDisabled: true,
|
project_base_dir={project_base_dir}
|
||||||
},
|
project_local_paths={project_local_paths}
|
||||||
...scmTypeOptions.map(([value, label]) => {
|
/>
|
||||||
if (label === 'Manual') {
|
),
|
||||||
value = 'manual';
|
git: (
|
||||||
}
|
<GitSubForm
|
||||||
return {
|
credential={credentials.scm}
|
||||||
label,
|
onCredentialSelection={handleCredentialSelection}
|
||||||
value,
|
scmUpdateOnLaunch={
|
||||||
key: value,
|
formik.values.scm_update_on_launch
|
||||||
};
|
}
|
||||||
}),
|
/>
|
||||||
]}
|
),
|
||||||
onChange={(event, value) => {
|
hg: (
|
||||||
form.setFieldValue('scm_type', value);
|
<HgSubForm
|
||||||
resetScmTypeFields(value, form);
|
credential={credentials.scm}
|
||||||
}}
|
onCredentialSelection={handleCredentialSelection}
|
||||||
/>
|
scmUpdateOnLaunch={
|
||||||
</FormGroup>
|
formik.values.scm_update_on_launch
|
||||||
)}
|
}
|
||||||
/>
|
/>
|
||||||
{formik.values.scm_type !== '' && (
|
),
|
||||||
<ScmTypeFormRow>
|
svn: (
|
||||||
<SubFormTitle size="md">{i18n._(t`Type Details`)}</SubFormTitle>
|
<SvnSubForm
|
||||||
{
|
credential={credentials.scm}
|
||||||
{
|
onCredentialSelection={handleCredentialSelection}
|
||||||
git: (
|
scmUpdateOnLaunch={
|
||||||
<GitSubForm
|
formik.values.scm_update_on_launch
|
||||||
credential={credentials.scm}
|
}
|
||||||
onCredentialSelection={handleCredentialSelection}
|
/>
|
||||||
scmUpdateOnLaunch={formik.values.scm_update_on_launch}
|
),
|
||||||
|
insights: (
|
||||||
|
<InsightsSubForm
|
||||||
|
credential={credentials.insights}
|
||||||
|
onCredentialSelection={handleCredentialSelection}
|
||||||
|
scmUpdateOnLaunch={
|
||||||
|
formik.values.scm_update_on_launch
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}[formik.values.scm_type]
|
||||||
|
}
|
||||||
|
</ScmTypeFormRow>
|
||||||
|
)}
|
||||||
|
<Config>
|
||||||
|
{({ custom_virtualenvs }) =>
|
||||||
|
custom_virtualenvs &&
|
||||||
|
custom_virtualenvs.length > 1 && (
|
||||||
|
<Field
|
||||||
|
name="custom_virtualenv"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormGroup
|
||||||
|
fieldId="project-custom-virtualenv"
|
||||||
|
label={i18n._(t`Ansible Environment`)}
|
||||||
|
>
|
||||||
|
<FieldTooltip
|
||||||
|
content={i18n._(t`Select the playbook to be executed by
|
||||||
|
this job.`)}
|
||||||
|
/>
|
||||||
|
<AnsibleSelect
|
||||||
|
id="project-custom-virtualenv"
|
||||||
|
data={[
|
||||||
|
{
|
||||||
|
label: i18n._(
|
||||||
|
t`Use Default Ansible Environment`
|
||||||
|
),
|
||||||
|
value: '/venv/ansible/',
|
||||||
|
key: 'default',
|
||||||
|
},
|
||||||
|
...custom_virtualenvs
|
||||||
|
.filter(datum => datum !== '/venv/ansible/')
|
||||||
|
.map(datum => ({
|
||||||
|
label: datum,
|
||||||
|
value: datum,
|
||||||
|
key: datum,
|
||||||
|
})),
|
||||||
|
]}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
),
|
)
|
||||||
hg: (
|
}
|
||||||
<HgSubForm
|
</Config>
|
||||||
credential={credentials.scm}
|
</FormRow>
|
||||||
onCredentialSelection={handleCredentialSelection}
|
<FormActionGroup
|
||||||
scmUpdateOnLaunch={formik.values.scm_update_on_launch}
|
onCancel={handleCancel}
|
||||||
/>
|
onSubmit={formik.handleSubmit}
|
||||||
),
|
/>
|
||||||
svn: (
|
</Form>
|
||||||
<SvnSubForm
|
)}
|
||||||
credential={credentials.scm}
|
/>
|
||||||
onCredentialSelection={handleCredentialSelection}
|
|
||||||
scmUpdateOnLaunch={formik.values.scm_update_on_launch}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
insights: (
|
|
||||||
<InsightsSubForm
|
|
||||||
credential={credentials.insights}
|
|
||||||
onCredentialSelection={handleCredentialSelection}
|
|
||||||
scmUpdateOnLaunch={formik.values.scm_update_on_launch}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
}[formik.values.scm_type]
|
|
||||||
}
|
|
||||||
</ScmTypeFormRow>
|
|
||||||
)}
|
|
||||||
<Config>
|
|
||||||
{({ custom_virtualenvs }) =>
|
|
||||||
custom_virtualenvs &&
|
|
||||||
custom_virtualenvs.length > 1 && (
|
|
||||||
<Field
|
|
||||||
name="custom_virtualenv"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormGroup
|
|
||||||
fieldId="project-custom-virtualenv"
|
|
||||||
label={i18n._(t`Ansible Environment`)}
|
|
||||||
>
|
|
||||||
<FieldTooltip
|
|
||||||
content={i18n._(t`Select the playbook to be executed by
|
|
||||||
this job.`)}
|
|
||||||
/>
|
|
||||||
<AnsibleSelect
|
|
||||||
id="project-custom-virtualenv"
|
|
||||||
data={[
|
|
||||||
{
|
|
||||||
label: i18n._(t`Use Default Ansible Environment`),
|
|
||||||
value: '/venv/ansible/',
|
|
||||||
key: 'default',
|
|
||||||
},
|
|
||||||
...custom_virtualenvs
|
|
||||||
.filter(datum => datum !== '/venv/ansible/')
|
|
||||||
.map(datum => ({
|
|
||||||
label: datum,
|
|
||||||
value: datum,
|
|
||||||
key: datum,
|
|
||||||
})),
|
|
||||||
]}
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</Config>
|
|
||||||
</FormRow>
|
|
||||||
<FormActionGroup
|
|
||||||
onCancel={handleCancel}
|
|
||||||
onSubmit={formik.handleSubmit}
|
|
||||||
/>
|
|
||||||
</Form>
|
|
||||||
)}
|
)}
|
||||||
/>
|
</Config>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ describe('<ProjectAdd />', () => {
|
|||||||
id: 100,
|
id: 100,
|
||||||
credential_type_id: 4,
|
credential_type_id: 4,
|
||||||
kind: 'scm',
|
kind: 'scm',
|
||||||
|
name: 'alpha',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -216,6 +217,59 @@ describe('<ProjectAdd />', () => {
|
|||||||
expect(formik.state.values.credential).toEqual(123);
|
expect(formik.state.values.credential).toEqual(123);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('manual subform should display expected fields', async () => {
|
||||||
|
const config = {
|
||||||
|
project_local_paths: ['foobar', 'qux'],
|
||||||
|
project_base_dir: 'dir/foo/bar',
|
||||||
|
};
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<ProjectForm
|
||||||
|
handleSubmit={jest.fn()}
|
||||||
|
handleCancel={jest.fn()}
|
||||||
|
project={{ scm_type: '', local_path: '/_foo__bar' }}
|
||||||
|
/>,
|
||||||
|
{
|
||||||
|
context: { config },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||||
|
const playbookDirectorySelect = wrapper.find(
|
||||||
|
'FormGroup[label="Playbook Directory"] FormSelect'
|
||||||
|
);
|
||||||
|
await act(async () => {
|
||||||
|
playbookDirectorySelect
|
||||||
|
.props()
|
||||||
|
.onChange('foobar', { target: { name: 'foobar' } });
|
||||||
|
});
|
||||||
|
expect(wrapper.find('FormGroup[label="Project Base Path"]').length).toBe(1);
|
||||||
|
expect(wrapper.find('FormGroup[label="Playbook Directory"]').length).toBe(
|
||||||
|
1
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('manual subform should display warning message when playbook directory is empty', async () => {
|
||||||
|
const config = {
|
||||||
|
project_local_paths: [],
|
||||||
|
project_base_dir: 'dir/foo/bar',
|
||||||
|
};
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<ProjectForm
|
||||||
|
handleSubmit={jest.fn()}
|
||||||
|
handleCancel={jest.fn()}
|
||||||
|
project={{ scm_type: '', local_path: '' }}
|
||||||
|
/>,
|
||||||
|
{
|
||||||
|
context: { config },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||||
|
expect(wrapper.find('ManualSubForm Alert').length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
test('should reset scm subform values when scm type changes', async () => {
|
test('should reset scm subform values when scm type changes', async () => {
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(
|
wrapper = mountWithContexts(
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { withI18n } from '@lingui/react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import { Field } from 'formik';
|
||||||
|
import AnsibleSelect from '@components/AnsibleSelect';
|
||||||
|
import FormField, { FieldTooltip } from '@components/FormField';
|
||||||
|
import { FormGroup, Alert } from '@patternfly/react-core';
|
||||||
|
import { BrandName } from '../../../../variables';
|
||||||
|
|
||||||
|
// Setting BrandName to a variable here is necessary to get the jest tests
|
||||||
|
// passing. Attempting to use BrandName in the template literal results
|
||||||
|
// in failing tests.
|
||||||
|
const brandName = BrandName;
|
||||||
|
|
||||||
|
const ManualSubForm = ({
|
||||||
|
i18n,
|
||||||
|
localPath,
|
||||||
|
project_base_dir,
|
||||||
|
project_local_paths,
|
||||||
|
}) => {
|
||||||
|
const localPaths = [...new Set([...project_local_paths, localPath])];
|
||||||
|
const options = [
|
||||||
|
{
|
||||||
|
value: '',
|
||||||
|
key: '',
|
||||||
|
label: i18n._(t`Choose a Playbook Directory`),
|
||||||
|
},
|
||||||
|
...localPaths
|
||||||
|
.filter(path => path)
|
||||||
|
.map(path => ({
|
||||||
|
value: path,
|
||||||
|
key: path,
|
||||||
|
label: path,
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{options.length === 1 && (
|
||||||
|
<Alert
|
||||||
|
title={i18n._(t`WARNING: `)}
|
||||||
|
css="grid-column: 1/-1"
|
||||||
|
variant="warning"
|
||||||
|
isInline
|
||||||
|
>
|
||||||
|
{i18n._(t`
|
||||||
|
There are no available playbook directories in ${project_base_dir}.
|
||||||
|
Either that directory is empty, or all of the contents are already
|
||||||
|
assigned to other projects. Create a new directory there and make
|
||||||
|
sure the playbook files can be read by the "awx" system user,
|
||||||
|
or have ${brandName} directly retrieve your playbooks from
|
||||||
|
source control using the SCM Type option above.`)}
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
<FormField
|
||||||
|
id="project-base-dir"
|
||||||
|
label={i18n._(t`Project Base Path`)}
|
||||||
|
name="base_dir"
|
||||||
|
type="text"
|
||||||
|
isReadOnly
|
||||||
|
tooltip={
|
||||||
|
<span>
|
||||||
|
{i18n._(t`Base path used for locating playbooks. Directories
|
||||||
|
found inside this path will be listed in the playbook directory drop-down.
|
||||||
|
Together the base path and selected playbook directory provide the full
|
||||||
|
path used to locate playbooks.`)}
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
{i18n._(t`Change PROJECTS_ROOT when deploying
|
||||||
|
${brandName} to change this location.`)}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{options.length !== 1 && (
|
||||||
|
<Field
|
||||||
|
name="local_path"
|
||||||
|
render={({ field, form }) => (
|
||||||
|
<FormGroup
|
||||||
|
fieldId="project-local-path"
|
||||||
|
label={i18n._(t`Playbook Directory`)}
|
||||||
|
>
|
||||||
|
<FieldTooltip
|
||||||
|
content={i18n._(t`Select from the list of directories found in
|
||||||
|
the Project Base Path. Together the base path and the playbook
|
||||||
|
directory provide the full path used to locate playbooks.`)}
|
||||||
|
/>
|
||||||
|
<AnsibleSelect
|
||||||
|
{...field}
|
||||||
|
id="local_path"
|
||||||
|
data={options}
|
||||||
|
onChange={(event, value) => {
|
||||||
|
form.setFieldValue('local_path', value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withI18n()(ManualSubForm);
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
|
export { SubFormTitle } from './SharedFields';
|
||||||
export { default as GitSubForm } from './GitSubForm';
|
export { default as GitSubForm } from './GitSubForm';
|
||||||
export { default as HgSubForm } from './HgSubForm';
|
export { default as HgSubForm } from './HgSubForm';
|
||||||
export { default as SvnSubForm } from './SvnSubForm';
|
|
||||||
export { default as InsightsSubForm } from './InsightsSubForm';
|
export { default as InsightsSubForm } from './InsightsSubForm';
|
||||||
export { SubFormTitle } from './SharedFields';
|
export { default as ManualSubForm } from './ManualSubForm';
|
||||||
|
export { default as SvnSubForm } from './SvnSubForm';
|
||||||
|
|||||||
Reference in New Issue
Block a user