From a8140e86d7f38fec3d2fc4dac13eeae0862c6b15 Mon Sep 17 00:00:00 2001 From: Marliana Lara Date: Wed, 30 Oct 2019 07:55:43 -0400 Subject: [PATCH] Encapsulate each scm type subform in its own component --- .../Project/ProjectList/ProjectListItem.jsx | 2 +- .../screens/Project/shared/ProjectForm.jsx | 677 ++++++------------ .../Project/shared/ProjectForm.test.jsx | 2 +- .../shared/ProjectSubForms/GitSubForm.jsx | 84 +++ .../shared/ProjectSubForms/HgSubForm.jsx | 44 ++ .../ProjectSubForms/InsightsSubForm.jsx | 42 ++ .../shared/ProjectSubForms/SharedFields.jsx | 135 ++++ .../shared/ProjectSubForms/SvnSubForm.jsx | 40 ++ .../Project/shared/ProjectSubForms/index.js | 5 + 9 files changed, 585 insertions(+), 446 deletions(-) create mode 100644 awx/ui_next/src/screens/Project/shared/ProjectSubForms/GitSubForm.jsx create mode 100644 awx/ui_next/src/screens/Project/shared/ProjectSubForms/HgSubForm.jsx create mode 100644 awx/ui_next/src/screens/Project/shared/ProjectSubForms/InsightsSubForm.jsx create mode 100644 awx/ui_next/src/screens/Project/shared/ProjectSubForms/SharedFields.jsx create mode 100644 awx/ui_next/src/screens/Project/shared/ProjectSubForms/SvnSubForm.jsx create mode 100644 awx/ui_next/src/screens/Project/shared/ProjectSubForms/index.js diff --git a/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.jsx b/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.jsx index 3df2871ac6..965c5d33af 100644 --- a/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.jsx +++ b/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.jsx @@ -91,7 +91,7 @@ class ProjectListItem extends React.Component { {project.name} diff --git a/awx/ui_next/src/screens/Project/shared/ProjectForm.jsx b/awx/ui_next/src/screens/Project/shared/ProjectForm.jsx index 7478954a5e..ea1e784848 100644 --- a/awx/ui_next/src/screens/Project/shared/ProjectForm.jsx +++ b/awx/ui_next/src/screens/Project/shared/ProjectForm.jsx @@ -1,30 +1,27 @@ import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; -import { withRouter, Link } from 'react-router-dom'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; -import { withFormik, Field } from 'formik'; +import { Formik, Field } from 'formik'; import { Config } from '@contexts/Config'; -import { - Form as _Form, - FormGroup, - Title as _Title, -} from '@patternfly/react-core'; +import { Form, FormGroup } from '@patternfly/react-core'; import AnsibleSelect from '@components/AnsibleSelect'; import ContentError from '@components/ContentError'; import ContentLoading from '@components/ContentLoading'; import FormActionGroup from '@components/FormActionGroup/FormActionGroup'; -import FormField, { CheckboxField, FieldTooltip } from '@components/FormField'; +import FormField, { FieldTooltip } from '@components/FormField'; import FormRow from '@components/FormRow'; import OrganizationLookup from '@components/Lookup/OrganizationLookup'; -import CredentialLookup from '@components/Lookup/CredentialLookup'; import { CredentialTypesAPI, ProjectsAPI } from '@api'; import { required } from '@util/validators'; import styled from 'styled-components'; - -const Form = styled(_Form)` - padding: 0 24px; -`; +import { + GitSubForm, + HgSubForm, + SvnSubForm, + InsightsSubForm, + SubFormTitle, +} from './ProjectSubForms'; const ScmTypeFormRow = styled(FormRow)` background-color: #f5f5f5; @@ -33,19 +30,10 @@ const ScmTypeFormRow = styled(FormRow)` padding: 24px; `; -const OptionsFormGroup = styled.div` - grid-column: 1/-1; -`; - -const Title = styled(_Title)` - --pf-c-title--m-md--FontWeight: 700; - grid-column: 1 / -1; -`; - function ProjectForm(props) { - const { values, handleCancel, handleSubmit, i18n } = props; + const { project, handleCancel, handleSubmit, i18n } = props; const [contentError, setContentError] = useState(null); - const [hasContentLoading, setHasContentLoading] = useState(true); + const [isLoading, setIsLoading] = useState(true); const [organization, setOrganization] = useState(null); const [scmTypeOptions, setScmTypeOptions] = useState(null); const [scmCredential, setScmCredential] = useState({ @@ -58,45 +46,48 @@ function ProjectForm(props) { }); useEffect(() => { - async function fetchCredTypeId(params) { - try { - const { - data: { - results: [credential], - }, - } = await CredentialTypesAPI.read(params); - return credential.id; - } catch (error) { - setContentError(error); - return null; - } - } - async function fetchData() { - const insightsTypeId = await fetchCredTypeId({ name: 'Insights' }); - const scmTypeId = await fetchCredTypeId({ kind: 'scm' }); - const { - data: { - actions: { - GET: { - scm_type: { choices }, + try { + const [ + { + data: { + results: [scmCredentialType], }, }, - }, - } = await ProjectsAPI.readOptions(); - setInsightsCredential({ typeId: insightsTypeId }); - setScmCredential({ typeId: scmTypeId }); - setScmTypeOptions(choices); - setHasContentLoading(false); + { + data: { + results: [insightsCredentialType], + }, + }, + { + data: { + actions: { + GET: { + scm_type: { choices }, + }, + }, + }, + }, + ] = await Promise.all([ + CredentialTypesAPI.read({ kind: 'scm' }), + CredentialTypesAPI.read({ name: 'Insights' }), + ProjectsAPI.readOptions(), + ]); + + setScmCredential({ typeId: scmCredentialType.id }); + setInsightsCredential({ typeId: insightsCredentialType.id }); + setScmTypeOptions(choices); + } catch (error) { + setContentError(error); + } finally { + setIsLoading(false); + } } fetchData(); }, []); - const resetScmTypeFields = (value, form) => { - if (form.initialValues.scm_type === value) { - return; - } + const resetScmTypeFields = form => { const scmFormFields = [ 'scm_url', 'scm_branch', @@ -115,63 +106,7 @@ function ProjectForm(props) { }); }; - const gitScmTooltip = ( - - {i18n._(t`Example URLs for GIT SCM include:`)} - - - {i18n._(t`Note: When using SSH protocol for GitHub or - Bitbucket, enter an SSH key only, do not enter a username - (other than git). Additionally, GitHub and Bitbucket do - not support password authentication when using SSH. GIT - read only protocol (git://) does not use username or - password information.`)} - - ); - - const hgScmTooltip = ( - - {i18n._(t`Example URLs for Mercurial SCM include:`)} - - {i18n._(t`Note: Mercurial does not support password authentication - for SSH. Do not put the username and key in the URL. If using - Bitbucket and SSH, do not supply your Bitbucket username. - `)} - - ); - - const svnScmTooltip = ( - - {i18n._(t`Example URLs for Subversion SCM include:`)} - - - ); - - const scmUrlTooltips = { - git: gitScmTooltip, - hg: hgScmTooltip, - svn: svnScmTooltip, - }; - - const scmBranchLabels = { - git: i18n._(t`SCM Branch/Tag/Commit`), - hg: i18n._(t`SCM Branch/Tag/Revision`), - svn: i18n._(t`Revision #`), - }; - - if (hasContentLoading) { + if (isLoading) { return ; } @@ -180,343 +115,197 @@ function ProjectForm(props) { } return ( -
- - - - ( - form.setFieldTouched('organization')} - onChange={value => { - form.setFieldValue('organization', value.id); - setOrganization(value); - }} - value={organization} - required - /> - )} - /> - ( - ( + + + - { - if (option[1] === 'Manual') { - option[0] = 'manual'; - } - return { - label: option[1], - value: option[0], - key: option[0], - }; - }), - ]} - onChange={(event, value) => { - form.setFieldValue('scm_type', value); - resetScmTypeFields(value, form); - }} - /> - - )} - /> - {values.scm_type !== '' && ( - - {i18n._(t`Type Details`)} - {(values.scm_type === 'git' || - values.scm_type === 'hg' || - values.scm_type === 'svn') && ( - - )} - {(values.scm_type === 'git' || - values.scm_type === 'hg' || - values.scm_type === 'svn') && ( - - )} - {values.scm_type === 'git' && ( - - {i18n._(t`A refspec to fetch (passed to the Ansible git - module). This parameter allows access to references via - the branch field not otherwise available.`)} -
-
- {i18n._( - t`Note: This field assumes the remote name is "origin".` - )} -
-
- {i18n._(t`Examples include:`)} -
    -
  • refs/*:refs/remotes/origin/*
  • -
  • - refs/pull/62/head:refs/remotes/origin/pull/62/head -
  • -
- {i18n._(t`The first fetches all references. The second - fetches the Github pull request number 62, in this example - the branch needs to be "pull/62/head".`)} -
-
- {i18n._(t`For more information, refer to the`)}{' '} - - {i18n._(t`Ansible Tower Documentation.`)} - - - } - /> - )} - {(values.scm_type === 'git' || - values.scm_type === 'hg' || - values.scm_type === 'svn') && ( - ( - { - form.setFieldValue('credential', credential.id); - setScmCredential({ - ...scmCredential, - value: credential, - }); - }} - /> - )} - /> - )} - {values.scm_type === 'insights' && ( - ( - form.setFieldTouched('credential')} - onChange={credential => { - form.setFieldValue('credential', credential.id); - setInsightsCredential({ - ...insightsCredential, - value: credential, - }); - }} - value={insightsCredential.value} - required - /> - )} - /> - )} - {/* - PF Bug: FormGroup doesn't pass down className - Workaround is to wrap FormGroup with an extra div - Cleanup when upgraded to @patternfly/react-core@3.103.4 - */} - {values.scm_type !== 'manual' && ( - - - - - - - {values.scm_type !== 'insights' && ( - - )} - - - - )} - {values.scm_type !== 'manual' && values.scm_update_on_launch && ( - <> - {i18n._(t`Option Details`)} - + + ( + form.setFieldTouched('organization')} + onChange={value => { + form.setFieldValue('organization', value.id); + setOrganization(value); + }} + value={organization} + required /> - + )} + /> + ( + + { + if (label === 'Manual') { + value = 'manual'; + } + return { + label, + value, + key: value, + }; + }), + ]} + onChange={(event, value) => { + form.setFieldValue('scm_type', value); + resetScmTypeFields(form); + }} + /> + + )} + /> + {formik.values.scm_type !== '' && ( + + {i18n._(t`Type Details`)} + { + { + git: ( + + ), + hg: ( + + ), + svn: ( + + ), + insights: ( + + ), + }[formik.values.scm_type] + } + )} -
- )} - - {({ custom_virtualenvs }) => - custom_virtualenvs && - custom_virtualenvs.length > 1 && ( - ( - - - datum !== '/venv/ansible/') - .map(datum => ({ - label: datum, - value: datum, - key: datum, - })), - ]} - {...field} - /> - - )} - /> - ) - } - -
- - + + {({ custom_virtualenvs }) => + custom_virtualenvs && + custom_virtualenvs.length > 1 && ( + ( + + + datum !== '/venv/ansible/') + .map(datum => ({ + label: datum, + value: datum, + key: datum, + })), + ]} + {...field} + /> + + )} + /> + ) + } + + + + + )} + /> ); } -const FormikApp = withFormik({ - mapPropsToValues(props) { - const { project = {} } = props; - - return { - credential: project.credential || '', - custom_virtualenv: project.custom_virtualenv || '', - description: project.description || '', - name: project.name || '', - organization: project.organization || '', - scm_branch: project.scm_branch || '', - scm_clean: project.scm_clean || false, - scm_delete_on_update: project.scm_delete_on_update || false, - scm_refspec: project.scm_refspec || '', - scm_type: project.scm_type || '', - scm_update_on_launch: project.scm_update_on_launch || false, - scm_url: project.scm_url || '', - scm_update_cache_timeout: project.scm_update_cache_timeout || 0, - allow_override: project.allow_override || false, - }; - }, - handleSubmit: (values, { props }) => props.handleSubmit(values), -})(ProjectForm); - ProjectForm.propTypes = { handleCancel: PropTypes.func.isRequired, handleSubmit: PropTypes.func.isRequired, @@ -527,4 +316,4 @@ ProjectForm.defaultProps = { project: {}, }; -export default withI18n()(withRouter(FormikApp)); +export default withI18n()(ProjectForm); diff --git a/awx/ui_next/src/screens/Project/shared/ProjectForm.test.jsx b/awx/ui_next/src/screens/Project/shared/ProjectForm.test.jsx index 00ee3fbf99..18b4c5f680 100644 --- a/awx/ui_next/src/screens/Project/shared/ProjectForm.test.jsx +++ b/awx/ui_next/src/screens/Project/shared/ProjectForm.test.jsx @@ -243,7 +243,7 @@ describe('', () => { }); wrapper.update(); await act(async () => { - scmTypeSelect.props().onChange('git', { target: { name: 'insights' } }); + scmTypeSelect.props().onChange('svn', { target: { name: 'Subversion' } }); }); wrapper.update(); expect(formik.state.values.scm_url).toEqual(''); diff --git a/awx/ui_next/src/screens/Project/shared/ProjectSubForms/GitSubForm.jsx b/awx/ui_next/src/screens/Project/shared/ProjectSubForms/GitSubForm.jsx new file mode 100644 index 0000000000..6bcbb83c48 --- /dev/null +++ b/awx/ui_next/src/screens/Project/shared/ProjectSubForms/GitSubForm.jsx @@ -0,0 +1,84 @@ +import React from 'react'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import FormField from '@components/FormField'; +import { + UrlFormField, + BranchFormField, + ScmCredentialFormField, + ScmTypeOptions, +} from './SharedFields'; + +const GitSubForm = ({ + i18n, + scmCredential, + setScmCredential, + scmUpdateOnLaunch, +}) => ( + <> + + {i18n._(t`Example URLs for GIT SCM include:`)} +
    +
  • https://github.com/ansible/ansible.git
  • +
  • git@github.com:ansible/ansible.git
  • +
  • git://servername.example.com/ansible.git
  • +
+ {i18n._(t`Note: When using SSH protocol for GitHub or + Bitbucket, enter an SSH key only, do not enter a username + (other than git). Additionally, GitHub and Bitbucket do + not support password authentication when using SSH. GIT + read only protocol (git://) does not use username or + password information.`)} + + } + /> + + + {i18n._(t`A refspec to fetch (passed to the Ansible git + module). This parameter allows access to references via + the branch field not otherwise available.`)} +
+
+ {i18n._(t`Note: This field assumes the remote name is "origin".`)} +
+
+ {i18n._(t`Examples include:`)} +
    +
  • refs/*:refs/remotes/origin/*
  • +
  • refs/pull/62/head:refs/remotes/origin/pull/62/head
  • +
+ {i18n._(t`The first fetches all references. The second + fetches the Github pull request number 62, in this example + the branch needs to be "pull/62/head".`)} +
+
+ {i18n._(t`For more information, refer to the`)}{' '} + + {i18n._(t`Ansible Tower Documentation.`)} + + + } + /> + + + +); + +export default withI18n()(GitSubForm); diff --git a/awx/ui_next/src/screens/Project/shared/ProjectSubForms/HgSubForm.jsx b/awx/ui_next/src/screens/Project/shared/ProjectSubForms/HgSubForm.jsx new file mode 100644 index 0000000000..6cc642686e --- /dev/null +++ b/awx/ui_next/src/screens/Project/shared/ProjectSubForms/HgSubForm.jsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import { + UrlFormField, + BranchFormField, + ScmCredentialFormField, + ScmTypeOptions, +} from './SharedFields'; + +const HgSubForm = ({ + i18n, + scmCredential, + setScmCredential, + scmUpdateOnLaunch, +}) => ( + <> + + {i18n._(t`Example URLs for Mercurial SCM include:`)} +
    +
  • https://bitbucket.org/username/project
  • +
  • ssh://hg@bitbucket.org/username/project
  • +
  • ssh://server.example.com/path
  • +
+ {i18n._(t`Note: Mercurial does not support password authentication + for SSH. Do not put the username and key in the URL. If using + Bitbucket and SSH, do not supply your Bitbucket username. + `)} + + } + /> + + + + +); + +export default withI18n()(HgSubForm); diff --git a/awx/ui_next/src/screens/Project/shared/ProjectSubForms/InsightsSubForm.jsx b/awx/ui_next/src/screens/Project/shared/ProjectSubForms/InsightsSubForm.jsx new file mode 100644 index 0000000000..4e854c8b20 --- /dev/null +++ b/awx/ui_next/src/screens/Project/shared/ProjectSubForms/InsightsSubForm.jsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import { Field } from 'formik'; +import CredentialLookup from '@components/Lookup/CredentialLookup'; +import { required } from '@util/validators'; +import { ScmTypeOptions } from './SharedFields'; + +const InsightsSubForm = ({ + i18n, + setInsightsCredential, + insightsCredential, + scmUpdateOnLaunch, +}) => ( + <> + ( + form.setFieldTouched('credential')} + onChange={credential => { + form.setFieldValue('credential', credential.id); + setInsightsCredential({ + ...insightsCredential, + value: credential, + }); + }} + value={insightsCredential.value} + required + /> + )} + /> + + +); + +export default withI18n()(InsightsSubForm); diff --git a/awx/ui_next/src/screens/Project/shared/ProjectSubForms/SharedFields.jsx b/awx/ui_next/src/screens/Project/shared/ProjectSubForms/SharedFields.jsx new file mode 100644 index 0000000000..40b1dce826 --- /dev/null +++ b/awx/ui_next/src/screens/Project/shared/ProjectSubForms/SharedFields.jsx @@ -0,0 +1,135 @@ +import React from 'react'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import { Field } from 'formik'; +import CredentialLookup from '@components/Lookup/CredentialLookup'; +import FormField, { CheckboxField } from '@components/FormField'; +import { required } from '@util/validators'; +import FormRow from '@components/FormRow'; +import { FormGroup, Title } from '@patternfly/react-core'; +import styled from 'styled-components'; + +export const SubFormTitle = styled(Title)` + --pf-c-title--m-md--FontWeight: 700; + grid-column: 1 / -1; +`; + +export const UrlFormField = withI18n()(({ i18n, tooltip }) => ( + +)); + +export const BranchFormField = withI18n()(({ i18n, label }) => ( + +)); + +export const ScmCredentialFormField = withI18n()( + ({ i18n, setScmCredential, scmCredential }) => ( + ( + { + form.setFieldValue('credential', credential.id); + setScmCredential({ + ...scmCredential, + value: credential, + }); + }} + /> + )} + /> + ) +); + +export const ScmTypeOptions = withI18n()( + ({ i18n, scmUpdateOnLaunch, hideAllowOverride }) => ( + <> + + + + + + {!hideAllowOverride && ( + + )} + + + {scmUpdateOnLaunch && ( + <> + {i18n._(t`Option Details`)} + + + )} + + ) +); diff --git a/awx/ui_next/src/screens/Project/shared/ProjectSubForms/SvnSubForm.jsx b/awx/ui_next/src/screens/Project/shared/ProjectSubForms/SvnSubForm.jsx new file mode 100644 index 0000000000..9f7e9a4659 --- /dev/null +++ b/awx/ui_next/src/screens/Project/shared/ProjectSubForms/SvnSubForm.jsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import { + UrlFormField, + BranchFormField, + ScmCredentialFormField, + ScmTypeOptions, +} from './SharedFields'; + +const SvnSubForm = ({ + i18n, + scmCredential, + setScmCredential, + scmUpdateOnLaunch, +}) => ( + <> + + {i18n._(t`Example URLs for Subversion SCM include:`)} +
    +
  • https://github.com/ansible/ansible
  • +
  • svn://servername.example.com/path
  • +
  • svn+ssh://servername.example.com/path
  • +
+ + } + /> + + + + +); + +export default withI18n()(SvnSubForm); diff --git a/awx/ui_next/src/screens/Project/shared/ProjectSubForms/index.js b/awx/ui_next/src/screens/Project/shared/ProjectSubForms/index.js new file mode 100644 index 0000000000..9443f17054 --- /dev/null +++ b/awx/ui_next/src/screens/Project/shared/ProjectSubForms/index.js @@ -0,0 +1,5 @@ +export { default as GitSubForm } from './GitSubForm'; +export { default as HgSubForm } from './HgSubForm'; +export { default as SvnSubForm } from './SvnSubForm'; +export { default as InsightsSubForm } from './InsightsSubForm'; +export { SubFormTitle } from './SharedFields';