From ff823c9fdbbc1502820ff8b56eca311eed6ff985 Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Tue, 18 Feb 2020 14:30:59 -0500 Subject: [PATCH 01/19] update forms to useField fomik hook --- .../CodeMirrorInput/VariablesField.jsx | 24 +- .../components/FormField/CheckboxField.jsx | 49 +- .../src/components/FormField/FormField.jsx | 14 +- .../components/FormField/PasswordField.jsx | 76 ++- .../src/screens/Host/shared/HostForm.jsx | 66 +-- .../Inventory/shared/InventoryForm.jsx | 143 +++-- .../Organization/shared/OrganizationForm.jsx | 139 ++--- .../shared/OrganizationForm.test.jsx | 5 + .../screens/Project/shared/ProjectForm.jsx | 488 +++++++++--------- .../ProjectSubForms/InsightsSubForm.jsx | 54 +- .../shared/ProjectSubForms/ManualSubForm.jsx | 56 +- .../shared/ProjectSubForms/SharedFields.jsx | 17 +- .../src/screens/Team/shared/TeamForm.jsx | 87 ++-- .../src/screens/User/shared/UserForm.jsx | 197 ++++--- 14 files changed, 692 insertions(+), 723 deletions(-) diff --git a/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx b/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx index 79bd41a826..34d6e8608e 100644 --- a/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx +++ b/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import { string, bool } from 'prop-types'; -import { Field, useFormikContext } from 'formik'; +import { useField } from 'formik'; import { Split, SplitItem } from '@patternfly/react-core'; import { yamlToJson, jsonToYaml, isJson } from '@util/yaml'; import CodeMirrorInput from './CodeMirrorInput'; @@ -8,9 +8,8 @@ import YamlJsonToggle from './YamlJsonToggle'; import { JSON_MODE, YAML_MODE } from './constants'; function VariablesField({ id, name, label, readOnly }) { - const { values } = useFormikContext(); - const value = values[name]; - const [mode, setMode] = useState(isJson(value) ? JSON_MODE : YAML_MODE); + const [field, meta, helpers] = useField(name); + const [mode, setMode] = useState(isJson(field.value) ? JSON_MODE : YAML_MODE); return ( @@ -31,10 +30,10 @@ function VariablesField({ id, name, label, readOnly }) { newMode === YAML_MODE ? jsonToYaml(field.value) : yamlToJson(field.value); - form.setFieldValue(name, newVal); + helpers.setValue(newVal); setMode(newMode); } catch (err) { - form.setFieldError(name, err.message); + helpers.setError(err.message); } }} /> @@ -45,16 +44,13 @@ function VariablesField({ id, name, label, readOnly }) { readOnly={readOnly} {...field} onChange={newVal => { - form.setFieldValue(name, newVal); + helpers.setValue(newVal); }} - hasErrors={!!form.errors[field.name]} + hasErrors={!!meta.error} /> - {form.errors[field.name] ? ( -
- {form.errors[field.name]} + {meta.error ? ( +
+ {meta.error}
) : null}
diff --git a/awx/ui_next/src/components/FormField/CheckboxField.jsx b/awx/ui_next/src/components/FormField/CheckboxField.jsx index 9e854d803a..a04a78b02c 100644 --- a/awx/ui_next/src/components/FormField/CheckboxField.jsx +++ b/awx/ui_next/src/components/FormField/CheckboxField.jsx @@ -1,6 +1,6 @@ import React from 'react'; import { string, func } from 'prop-types'; -import { Field } from 'formik'; +import { useField } from 'formik'; import { Checkbox, Tooltip } from '@patternfly/react-core'; import { QuestionCircleIcon as PFQuestionCircleIcon } from '@patternfly/react-icons'; import styled from 'styled-components'; @@ -10,32 +10,29 @@ const QuestionCircleIcon = styled(PFQuestionCircleIcon)` `; function CheckboxField({ id, name, label, tooltip, validate, ...rest }) { + const [field] = useField({ name, validate }); return ( - - {({ field }) => ( - - {label} -   - {tooltip && ( - - - - )} - - } - id={id} - {...rest} - isChecked={field.value} - {...field} - onChange={(value, event) => { - field.onChange(event); - }} - /> - )} - + + {label} +   + {tooltip && ( + + + + )} + + } + id={id} + {...rest} + isChecked={field.value} + {...field} + onChange={(value, event) => { + field.onChange(event); + }} + /> ); } CheckboxField.propTypes = { diff --git a/awx/ui_next/src/components/FormField/FormField.jsx b/awx/ui_next/src/components/FormField/FormField.jsx index f55eabb131..b92fee5484 100644 --- a/awx/ui_next/src/components/FormField/FormField.jsx +++ b/awx/ui_next/src/components/FormField/FormField.jsx @@ -1,7 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Field } from 'formik'; -import { FormGroup, TextInput, Tooltip } from '@patternfly/react-core'; +import { useField } from 'formik'; import { QuestionCircleIcon as PFQuestionCircleIcon } from '@patternfly/react-icons'; import styled from 'styled-components'; @@ -21,16 +20,13 @@ function FormField(props) { ...rest } = props; - return ( - - {({ field, form }) => { - const isValid = - form && (!form.touched[field.name] || !form.errors[field.name]); + const [field, meta] = useField({ name, validate }); + const isValid = !(meta.touched && meta.error); return ( )} + isValid={isValid} + helperTextInvalid={meta.error} { setInputType(inputType === 'text' ? 'password' : 'text'); }; return ( - - {({ field, form }) => { - const isValid = - form && (!form.touched[field.name] || !form.errors[field.name]); - return ( - + + + - - { - field.onChange(event); - }} - /> - - - ); - }} - + {inputType === 'password' && } + {inputType === 'text' && } + + + { + field.onChange(event); + }} + /> + + ); } diff --git a/awx/ui_next/src/screens/Host/shared/HostForm.jsx b/awx/ui_next/src/screens/Host/shared/HostForm.jsx index 0e1919be58..fd947f839d 100644 --- a/awx/ui_next/src/screens/Host/shared/HostForm.jsx +++ b/awx/ui_next/src/screens/Host/shared/HostForm.jsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { func, shape } from 'prop-types'; import { useRouteMatch } from 'react-router-dom'; -import { Formik, Field } from 'formik'; +import { Formik, useField } from 'formik'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; @@ -15,26 +15,21 @@ import { VariablesField } from '@components/CodeMirrorInput'; import { required } from '@util/validators'; import { InventoryLookup } from '@components/Lookup'; -function HostForm({ handleSubmit, handleCancel, host, submitError, i18n }) { +function HostFormFields({ host, i18n }) { const [inventory, setInventory] = useState( host ? host.summary_fields.inventory : '' ); const hostAddMatch = useRouteMatch('/hosts/add'); + const inventoryFieldArr = useField({ + name: 'inventory', + validate: required(i18n._(t`Select aĆ„ value for this field`), i18n), + }); + const inventoryMeta = inventoryFieldArr[1]; + const inventoryHelpers = inventoryFieldArr[2]; return ( - - {formik => ( -
- + <> {hostAddMatch && ( - - {({ form }) => ( form.setFieldTouched('inventory')} + onBlur={() => inventoryHelpers.setTouched()} tooltip={i18n._( t`Select the inventory that this host will belong to.` )} - isValid={!form.touched.inventory || !form.errors.inventory} - helperTextInvalid={form.errors.inventory} + isValid={!inventoryMeta.touched || !inventoryMeta.error} + helperTextInvalid={inventoryMeta.error} onChange={value => { - form.setFieldValue('inventory', value.id); + inventoryHelpers.setValuealue(value.id); setInventory(value); }} required - touched={form.touched.inventory} - error={form.errors.inventory} + touched={inventoryMeta.touched} + error={inventoryMeta.error} /> )} - - )} - - - + + ); +} + +function HostForm({ handleSubmit, host, submitError, handleCancel, ...rest }) { + return ( + + {formik => ( + + { - onSubmit(values); - }} - > - {formik => ( - - + <> - - {({ form, field }) => ( form.setFieldTouched('organization')} + helperTextInvalid={organizationMeta.error} + isValid={!organizationMeta.touched || !organizationMeta.error} + onBlur={() => organizationHelpers.setTouched()} onChange={value => { - form.setFieldValue('organization', value); + organizationHelpers.setValue(value); }} - value={field.value} - touched={form.touched.organization} - error={form.errors.organization} + value={organizationField.value} + touched={organizationMeta.touched} + error={organizationMeta.error} required /> - )} - - - {({ field, form }) => ( - form.setFieldValue('insights_credential', value) - } - value={field.value} + onChange={value => insightsCredentialHelpers.setValue(value)} + value={insightsCredentialField.value} /> - )} - - - - - {({ field, form }) => ( { - form.setFieldValue('instanceGroups', value); + instanceGroupsHelpers.setValue(value); }} /> - )} - - - - - + + ); +} + +function InventoryForm({ + inventory = {}, + onSubmit, + onCancel, + submitError, + instanceGroups, + ...rest +}) { + const initialValues = { + name: inventory.name || '', + description: inventory.description || '', + variables: inventory.variables || '---', + organization: + (inventory.summary_fields && inventory.summary_fields.organization) || + null, + instanceGroups: instanceGroups || [], + insights_credential: + (inventory.summary_fields && + inventory.summary_fields.insights_credential) || + null, + }; + + return ( + { + onSubmit(values); + }} + > + {formik => ( + + + + + + {custom_virtualenvs && custom_virtualenvs.length > 1 && ( + + value !== defaultVenv.value) + .map(value => ({ value, label: value, key: value })), + ]} + {...venvField} + /> + + )} + + + ); +} + +function OrganizationForm({ + organization, + onCancel, + onSubmit, + submitError, + ...rest +}) { const [contentError, setContentError] = useState(null); const [hasContentLoading, setHasContentLoading] = useState(true); const [initialInstanceGroups, setInitialInstanceGroups] = useState([]); @@ -100,64 +166,11 @@ function OrganizationForm({ > {formik => ( - - - - - {custom_virtualenvs && custom_virtualenvs.length > 1 && ( - - {({ field }) => ( - - value !== defaultVenv.value) - .map(value => ({ value, label: value, key: value })), - ]} - {...field} - /> - - )} - - )} - - ', () => { }); test('changing inputs and saving triggers expected callback', async () => { + OrganizationsAPI.readInstanceGroups.mockReturnValue({ + data: { + results: mockInstanceGroups, + }, + }); let wrapper; const onSubmit = jest.fn(); await act(async () => { diff --git a/awx/ui_next/src/screens/Project/shared/ProjectForm.jsx b/awx/ui_next/src/screens/Project/shared/ProjectForm.jsx index 768624dc7c..e18eb7fa3a 100644 --- a/awx/ui_next/src/screens/Project/shared/ProjectForm.jsx +++ b/awx/ui_next/src/screens/Project/shared/ProjectForm.jsx @@ -3,9 +3,9 @@ import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; -import { Formik, Field } from 'formik'; +import { Formik, useField } from 'formik'; import { Config } from '@contexts/Config'; -import { Form, FormGroup } from '@patternfly/react-core'; +import { Form, FormGroup, Title } from '@patternfly/react-core'; import AnsibleSelect from '@components/AnsibleSelect'; import ContentError from '@components/ContentError'; import ContentLoading from '@components/ContentLoading'; @@ -24,17 +24,9 @@ import { HgSubForm, SvnSubForm, InsightsSubForm, - SubFormTitle, ManualSubForm, } from './ProjectSubForms'; -const ScmTypeFormRow = styled(FormRow)` - background-color: #f5f5f5; - grid-column: 1 / -1; - margin: 0 -24px; - padding: 24px; -`; - const fetchCredentials = async credential => { const [ { @@ -73,14 +65,238 @@ const fetchCredentials = async credential => { }; }; -function ProjectForm({ project, submitError, ...props }) { - const { i18n, handleCancel, handleSubmit } = props; +function ProjectFormFields({ + project_base_dir, + project_local_paths, + formik, + i18n, + setCredentials, + credentials, + scmTypeOptions, + setScmSubFormState, + scmSubFormState, + setOrganization, + organization, +}) { + const scmFormFields = { + scm_url: '', + scm_branch: '', + scm_refspec: '', + credential: '', + scm_clean: false, + scm_delete_on_update: false, + scm_update_on_launch: false, + allow_override: false, + scm_update_cache_timeout: 0, + }; + + const [scmTypeField, scmTypeMeta, scmTypeHelpers] = useField({ + name: 'scm_type', + validate: required(i18n._(t`Set a value for this field`), i18n), + }); + const [venvField] = useField('custom_virtualenv'); + const orgFieldArr = useField({ + name: 'organization', + validate: required(i18n._(t`Select a value for this field`), i18n), + }); + const organizationMeta = orgFieldArr[1]; + const organizationHelpers = orgFieldArr[2]; + + /* Save current scm subform field values to state */ + const saveSubFormState = form => { + const currentScmFormFields = { ...scmFormFields }; + + Object.keys(currentScmFormFields).forEach(label => { + currentScmFormFields[label] = form.values[label]; + }); + + setScmSubFormState(currentScmFormFields); + }; + + /** + * If scm type is !== the initial scm type value, + * reset scm subform field values to defaults. + * If scm type is === the initial scm type value, + * reset scm subform field values to scmSubFormState. + */ + const resetScmTypeFields = (value, form) => { + if (form.values.scm_type === form.initialValues.scm_type) { + saveSubFormState(formik); + } + + Object.keys(scmFormFields).forEach(label => { + if (value === form.initialValues.scm_type) { + form.setFieldValue(label, scmSubFormState[label]); + } else { + form.setFieldValue(label, scmFormFields[label]); + } + form.setFieldTouched(label, false); + }); + }; + + const handleCredentialSelection = (type, value) => { + setCredentials({ + ...credentials, + [type]: { + ...credentials[type], + value, + }, + }); + }; + + return ( + <> + + + organizationHelpers.setTouched()} + onChange={value => { + organizationHelpers.setValue(value.id); + setOrganization(value); + }} + value={organization} + required + /> + + { + if (label === 'Manual') { + value = 'manual'; + } + return { + label, + value, + key: value, + }; + }), + ]} + onChange={(event, value) => { + scmTypeHelpers.setValue(value); + resetScmTypeFields(value, formik); + }} + /> + + {formik.values.scm_type !== '' && ( + + + {i18n._(t`Type Details`)} + + { + { + manual: ( + + ), + git: ( + + ), + hg: ( + + ), + svn: ( + + ), + insights: ( + + ), + }[formik.values.scm_type] + } + + )} + + {({ custom_virtualenvs }) => + custom_virtualenvs && + custom_virtualenvs.length > 1 && ( + + {({ field }) => ( + + + datum !== '/venv/ansible/') + .map(datum => ({ + label: datum, + value: datum, + key: datum, + })), + ]} + {...venvField} + /> + + ) + } + + + ); +} + +function ProjectForm({ i18n, project, submitError, ...props }) { + const { handleCancel, handleSubmit } = props; const { summary_fields = {} } = project; const [contentError, setContentError] = useState(null); const [isLoading, setIsLoading] = useState(true); - const [organization, setOrganization] = useState( - summary_fields.organization || null - ); + const [organization, setOrganization] = useState(null); const [scmSubFormState, setScmSubFormState] = useState(null); const [scmTypeOptions, setScmTypeOptions] = useState(null); const [credentials, setCredentials] = useState({ @@ -114,60 +330,6 @@ function ProjectForm({ project, submitError, ...props }) { fetchData(); }, [summary_fields.credential]); - const scmFormFields = { - scm_url: '', - scm_branch: '', - scm_refspec: '', - credential: '', - scm_clean: false, - scm_delete_on_update: false, - scm_update_on_launch: false, - allow_override: false, - scm_update_cache_timeout: 0, - }; - - /* Save current scm subform field values to state */ - const saveSubFormState = form => { - const currentScmFormFields = { ...scmFormFields }; - - Object.keys(currentScmFormFields).forEach(label => { - currentScmFormFields[label] = form.values[label]; - }); - - setScmSubFormState(currentScmFormFields); - }; - - /** - * If scm type is !== the initial scm type value, - * reset scm subform field values to defaults. - * If scm type is === the initial scm type value, - * reset scm subform field values to scmSubFormState. - */ - const resetScmTypeFields = (value, form) => { - if (form.values.scm_type === form.initialValues.scm_type) { - saveSubFormState(form); - } - - Object.keys(scmFormFields).forEach(label => { - if (value === form.initialValues.scm_type) { - form.setFieldValue(label, scmSubFormState[label]); - } else { - form.setFieldValue(label, scmFormFields[label]); - } - form.setFieldTouched(label, false); - }); - }; - - const handleCredentialSelection = (type, value) => { - setCredentials({ - ...credentials, - [type]: { - ...credentials[type], - value, - }, - }); - }; - if (isLoading) { return ; } @@ -211,183 +373,19 @@ function ProjectForm({ project, submitError, ...props }) { onSubmit={formik.handleSubmit} css="padding: 0 24px" > - - - - - {({ form }) => ( - form.setFieldTouched('organization')} - onChange={value => { - form.setFieldValue('organization', value.id); - setOrganization(value); - }} - value={organization} - required - /> - )} - - - {({ field, form }) => ( - - { - if (label === 'Manual') { - value = 'manual'; - } - return { - label, - value, - key: value, - }; - }), - ]} - onChange={(event, value) => { - form.setFieldValue('scm_type', value); - resetScmTypeFields(value, form); - }} - /> - - )} - - {formik.values.scm_type !== '' && ( - - - {i18n._(t`Type Details`)} - - { - { - manual: ( - - ), - git: ( - - ), - hg: ( - - ), - svn: ( - - ), - insights: ( - - ), - }[formik.values.scm_type] - } - - )} - - {({ custom_virtualenvs }) => - custom_virtualenvs && - custom_virtualenvs.length > 1 && ( - - {({ field }) => ( - - - datum !== '/venv/ansible/') - .map(datum => ({ - label: datum, - value: datum, - key: datum, - })), - ]} - {...field} - /> - - )} - - ) - } - - ( - <> - - {({ form }) => ( - form.setFieldTouched('credential')} - onChange={value => { - onCredentialSelection('insights', value); - form.setFieldValue('credential', value.id); - }} - value={credential.value} - required - /> - )} - - - -); +}) => { + const credFieldArr = useField({ + name: 'credential', + validate: required(i18n._(t`Select a value for this field`), i18n), + }); + const credMeta = credFieldArr[1]; + const credHelpers = credFieldArr[2]; + + return ( + <> + credHelpers.setTouched()} + onChange={value => { + onCredentialSelection('insights', value); + credHelpers.setValue(value.id); + }} + value={credential.value} + required + /> + + + ); +}; export default withI18n()(InsightsSubForm); diff --git a/awx/ui_next/src/screens/Project/shared/ProjectSubForms/ManualSubForm.jsx b/awx/ui_next/src/screens/Project/shared/ProjectSubForms/ManualSubForm.jsx index 94a624b380..adf1d2fb15 100644 --- a/awx/ui_next/src/screens/Project/shared/ProjectSubForms/ManualSubForm.jsx +++ b/awx/ui_next/src/screens/Project/shared/ProjectSubForms/ManualSubForm.jsx @@ -1,7 +1,7 @@ import React from 'react'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; -import { Field } from 'formik'; +import { useField } from 'formik'; import { required } from '@util/validators'; import AnsibleSelect from '@components/AnsibleSelect'; import FormField, { FieldTooltip } from '@components/FormField'; @@ -34,6 +34,10 @@ const ManualSubForm = ({ label: path, })), ]; + const [pathField, pathMeta, pathHelpers] = useField({ + name: 'local_path', + validate: required(i18n._(t`Select a value for this field`), i18n), + }); return ( <> @@ -72,35 +76,27 @@ const ManualSubForm = ({ } /> - {options.length !== 1 && ( - - {({ field, form }) => ( - - - { - form.setFieldValue('local_path', value); - }} - /> - - )} - + + + { + pathHelpers.setValue(value); + }} + /> + )} ); diff --git a/awx/ui_next/src/screens/Project/shared/ProjectSubForms/SharedFields.jsx b/awx/ui_next/src/screens/Project/shared/ProjectSubForms/SharedFields.jsx index 1268cd7230..199c1b2d2a 100644 --- a/awx/ui_next/src/screens/Project/shared/ProjectSubForms/SharedFields.jsx +++ b/awx/ui_next/src/screens/Project/shared/ProjectSubForms/SharedFields.jsx @@ -1,11 +1,10 @@ import React from 'react'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; -import { Field } from 'formik'; +import { useField } 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'; @@ -41,21 +40,21 @@ export const BranchFormField = withI18n()(({ i18n, label }) => ( )); export const ScmCredentialFormField = withI18n()( - ({ i18n, credential, onCredentialSelection }) => ( - - {({ form }) => ( + ({ i18n, credential, onCredentialSelection }) => { + const credHelpers = useField('credential')[2]; + + return ( { onCredentialSelection('scm', value); - form.setFieldValue('credential', value ? value.id : ''); + credHelpers.setValue(value ? value.id : ''); }} /> - )} - - ) + ); + } ); export const ScmTypeOptions = withI18n()( diff --git a/awx/ui_next/src/screens/Team/shared/TeamForm.jsx b/awx/ui_next/src/screens/Team/shared/TeamForm.jsx index 040d6d4dca..d5ba307fa2 100644 --- a/awx/ui_next/src/screens/Team/shared/TeamForm.jsx +++ b/awx/ui_next/src/screens/Team/shared/TeamForm.jsx @@ -2,19 +2,58 @@ import React, { useState } from 'react'; import PropTypes from 'prop-types'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; -import { Formik, Field } from 'formik'; +import { Formik, useField } from 'formik'; import { Form } from '@patternfly/react-core'; import FormActionGroup from '@components/FormActionGroup/FormActionGroup'; import FormField, { FormSubmitError } from '@components/FormField'; -import FormRow from '@components/FormRow'; import OrganizationLookup from '@components/Lookup/OrganizationLookup'; import { required } from '@util/validators'; -function TeamForm(props) { - const { team, handleCancel, handleSubmit, submitError, i18n } = props; +function TeamFormFields(props) { + const { team, i18n } = props; const [organization, setOrganization] = useState( team.summary_fields ? team.summary_fields.organization : null ); + const orgFieldArr = useField({ + name: 'organization', + validate: required(i18n._(t`Select a value for this field`), i18n), + }); + const orgMeta = orgFieldArr[1]; + const orgHelpers = orgFieldArr[2]; + + return ( + <> + + + orgHelpers.setTouched('organization')} + onChange={value => { + orgHelpers.setValue(value.id); + setOrganization(value); + }} + value={organization} + required + /> + + ); +} + +function TeamForm(props) { + const { team, handleCancel, handleSubmit, submitError, ...rest } = props; return ( - - - - - {({ form }) => ( - form.setFieldTouched('organization')} - onChange={value => { - form.setFieldValue('organization', value.id); - setOrganization(value); - }} - value={organization} - required - /> - )} - - + + + + undefined + } + isRequired={!user.id} + /> + undefined + } + isRequired={!user.id} + /> + + + {!user.id && ( + organizationHelpers.setTouched()} + onChange={value => { + organizationHelpers.setValue(value.id); + setOrganization(value); + }} + value={organization} + required + /> + )} + + + + + ); +} + +function UserForm({ user, handleCancel, handleSubmit, submitError, i18n }) { const handleValidateAndSubmit = (values, { setErrors }) => { if (values.password !== values.confirm_password) { setErrors({ @@ -81,106 +173,7 @@ function UserForm({ user, handleCancel, handleSubmit, submitError, i18n }) { > {formik => ( - - - - undefined - } - isRequired={!user.id} - /> - undefined - } - isRequired={!user.id} - /> - - - - - {!user.id && ( - - {({ form }) => ( - form.setFieldTouched('organization')} - onChange={value => { - form.setFieldValue('organization', value.id); - setOrganization(value); - }} - value={organization} - required - /> - )} - - )} - - {({ form, field }) => { - const isValid = - !form.touched.user_type || !form.errors.user_type; - return ( - - - - ); - }} - - + Date: Tue, 18 Feb 2020 14:35:41 -0500 Subject: [PATCH 02/19] update forms from FormRow to using FormLayout components --- .../CodeMirrorInput/VariablesField.jsx | 71 ++- .../FormActionGroup/FormActionGroup.jsx | 1 + .../src/components/FormLayout/FormLayout.jsx | 44 ++ .../src/components/FormLayout/index.js | 6 + .../src/components/FormRow/FormRow.jsx | 11 - .../src/components/FormRow/FormRow.test.jsx | 11 - awx/ui_next/src/components/FormRow/index.js | 1 - .../src/screens/Host/shared/HostForm.jsx | 76 +-- .../Inventory/shared/InventoryForm.jsx | 74 +-- .../Inventory/shared/InventoryGroupForm.jsx | 18 +- .../Organization/shared/OrganizationForm.jsx | 4 +- .../screens/Project/shared/ProjectForm.jsx | 265 +++++---- .../shared/ProjectSubForms/SharedFields.jsx | 53 +- .../Project/shared/ProjectSubForms/index.js | 1 - .../src/screens/Team/shared/TeamForm.jsx | 56 +- .../Template/shared/JobTemplateForm.jsx | 524 +++++++++--------- .../src/screens/User/shared/UserForm.jsx | 47 +- 17 files changed, 655 insertions(+), 608 deletions(-) create mode 100644 awx/ui_next/src/components/FormLayout/FormLayout.jsx create mode 100644 awx/ui_next/src/components/FormLayout/index.js delete mode 100644 awx/ui_next/src/components/FormRow/FormRow.jsx delete mode 100644 awx/ui_next/src/components/FormRow/FormRow.test.jsx delete mode 100644 awx/ui_next/src/components/FormRow/index.js diff --git a/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx b/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx index 34d6e8608e..734b68b53a 100644 --- a/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx +++ b/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx @@ -3,6 +3,7 @@ import { string, bool } from 'prop-types'; import { useField } from 'formik'; import { Split, SplitItem } from '@patternfly/react-core'; import { yamlToJson, jsonToYaml, isJson } from '@util/yaml'; +import { FormFullWidthLayout } from '@components/FormLayout'; import CodeMirrorInput from './CodeMirrorInput'; import YamlJsonToggle from './YamlJsonToggle'; import { JSON_MODE, YAML_MODE } from './constants'; @@ -12,50 +13,46 @@ function VariablesField({ id, name, label, readOnly }) { const [mode, setMode] = useState(isJson(field.value) ? JSON_MODE : YAML_MODE); return ( - - {({ field, form }) => ( -
- - - - - - { - try { - const newVal = - newMode === YAML_MODE - ? jsonToYaml(field.value) - : yamlToJson(field.value); - helpers.setValue(newVal); - setMode(newMode); - } catch (err) { - helpers.setError(err.message); - } - }} - /> - - - + + + + + + { - helpers.setValue(newVal); + 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); + } }} - hasErrors={!!meta.error} /> + + + { + helpers.setValue(newVal); + }} + hasErrors={!!meta.error} + /> {meta.error ? (
{meta.error} -
- ) : null}
- )} -
+ ) : null} + ); } VariablesField.propTypes = { diff --git a/awx/ui_next/src/components/FormActionGroup/FormActionGroup.jsx b/awx/ui_next/src/components/FormActionGroup/FormActionGroup.jsx index bae4fe0772..71adc9c2f7 100644 --- a/awx/ui_next/src/components/FormActionGroup/FormActionGroup.jsx +++ b/awx/ui_next/src/components/FormActionGroup/FormActionGroup.jsx @@ -9,6 +9,7 @@ const ActionGroup = styled(PFActionGroup)` display: flex; justify-content: flex-end; --pf-c-form__group--m-action--MarginTop: 0; + grid-column: 1 / -1; .pf-c-form__actions { & > button { diff --git a/awx/ui_next/src/components/FormLayout/FormLayout.jsx b/awx/ui_next/src/components/FormLayout/FormLayout.jsx new file mode 100644 index 0000000000..aeab53a3ef --- /dev/null +++ b/awx/ui_next/src/components/FormLayout/FormLayout.jsx @@ -0,0 +1,44 @@ +import styled from 'styled-components'; + +export const FormColumnLayout = styled.div` + width: 100%; + grid-column: 1 / 4; + display: grid; + grid-gap: 20px; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + + @media (min-width: 1210px) { + grid-template-columns: repeat(3, 1fr); + } +`; + +export const FormFullWidthLayout = styled.div` + grid-column: 1 / -1; + display: grid; + grid-template-columns: 1fr; + grid-gap: 20px; +`; + +export const FormCheckboxLayout = styled.div` + display: flex; + justify-content: flex-start; + flex-wrap: wrap; + + & > * { + margin-right: 30px; + margin-bottom: 10px; + } +`; + +export const SubFormLayout = styled.div` + grid-column: 1 / -1; + background-color: #f5f5f5; + margin: 0 -24px; + padding: 24px; + + & > .pf-c-title { + --pf-c-title--m-md--FontWeight: 700; + grid-column: 1 / -1; + margin-bottom: 20px; + } +`; diff --git a/awx/ui_next/src/components/FormLayout/index.js b/awx/ui_next/src/components/FormLayout/index.js new file mode 100644 index 0000000000..208a99d993 --- /dev/null +++ b/awx/ui_next/src/components/FormLayout/index.js @@ -0,0 +1,6 @@ +export { + FormColumnLayout, + FormFullWidthLayout, + FormCheckboxLayout, + SubFormLayout, +} from './FormLayout'; diff --git a/awx/ui_next/src/components/FormRow/FormRow.jsx b/awx/ui_next/src/components/FormRow/FormRow.jsx deleted file mode 100644 index a28913dd37..0000000000 --- a/awx/ui_next/src/components/FormRow/FormRow.jsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import styled from 'styled-components'; - -const Row = styled.div` - display: grid; - grid-gap: 20px; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); -`; -export default function FormRow({ children, className }) { - return {children}; -} diff --git a/awx/ui_next/src/components/FormRow/FormRow.test.jsx b/awx/ui_next/src/components/FormRow/FormRow.test.jsx deleted file mode 100644 index f913ef8c3c..0000000000 --- a/awx/ui_next/src/components/FormRow/FormRow.test.jsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import { mount } from 'enzyme'; - -import FormRow from './FormRow'; - -describe('FormRow', () => { - test('renders the expected content', () => { - const wrapper = mount(); - expect(wrapper).toHaveLength(1); - }); -}); diff --git a/awx/ui_next/src/components/FormRow/index.js b/awx/ui_next/src/components/FormRow/index.js deleted file mode 100644 index 8f063b5ff2..0000000000 --- a/awx/ui_next/src/components/FormRow/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './FormRow'; diff --git a/awx/ui_next/src/screens/Host/shared/HostForm.jsx b/awx/ui_next/src/screens/Host/shared/HostForm.jsx index fd947f839d..66ffe6759f 100644 --- a/awx/ui_next/src/screens/Host/shared/HostForm.jsx +++ b/awx/ui_next/src/screens/Host/shared/HostForm.jsx @@ -8,12 +8,12 @@ import { t } from '@lingui/macro'; import { Form } from '@patternfly/react-core'; -import FormRow from '@components/FormRow'; import FormField, { FormSubmitError } from '@components/FormField'; import FormActionGroup from '@components/FormActionGroup/FormActionGroup'; import { VariablesField } from '@components/CodeMirrorInput'; import { required } from '@util/validators'; import { InventoryLookup } from '@components/Lookup'; +import { FormColumnLayout } from '@components/FormLayout'; function HostFormFields({ host, i18n }) { const [inventory, setInventory] = useState( @@ -30,43 +30,43 @@ function HostFormFields({ host, i18n }) { return ( <> - - - {hostAddMatch && ( - + + {hostAddMatch && ( + inventoryHelpers.setTouched()} - tooltip={i18n._( - t`Select the inventory that this host will belong to.` - )} + tooltip={i18n._( + t`Select the inventory that this host will belong to.` + )} isValid={!inventoryMeta.touched || !inventoryMeta.error} helperTextInvalid={inventoryMeta.error} - onChange={value => { + onChange={value => { inventoryHelpers.setValuealue(value.id); - setInventory(value); - }} - required + setInventory(value); + }} + required touched={inventoryMeta.touched} error={inventoryMeta.error} - /> - )} - + /> + )} + ); } @@ -84,12 +84,14 @@ function HostForm({ handleSubmit, host, submitError, handleCancel, ...rest }) { > {formik => ( + - - + + + )}
diff --git a/awx/ui_next/src/screens/Inventory/shared/InventoryForm.jsx b/awx/ui_next/src/screens/Inventory/shared/InventoryForm.jsx index ee6d4667c9..883b151044 100644 --- a/awx/ui_next/src/screens/Inventory/shared/InventoryForm.jsx +++ b/awx/ui_next/src/screens/Inventory/shared/InventoryForm.jsx @@ -12,6 +12,7 @@ import { required } from '@util/validators'; import InstanceGroupsLookup from '@components/Lookup/InstanceGroupsLookup'; import OrganizationLookup from '@components/Lookup/OrganizationLookup'; import CredentialLookup from '@components/Lookup/CredentialLookup'; +import { FormColumnLayout } from '@components/FormLayout'; function InventoryFormFields({ i18n, credentialTypeId }) { const [organizationField, organizationMeta, organizationHelpers] = useField({ @@ -27,52 +28,52 @@ function InventoryFormFields({ i18n, credentialTypeId }) { const insightsCredentialHelpers = insightsCredentialFieldArr[2]; return ( <> - - - + + organizationHelpers.setTouched()} - onChange={value => { + onChange={value => { organizationHelpers.setValue(value); - }} + }} value={organizationField.value} touched={organizationMeta.touched} error={organizationMeta.error} - required - /> - + insightsCredentialHelpers.setValue(value)} value={insightsCredentialField.value} - /> - + { + onChange={value => { instanceGroupsHelpers.setValue(value); - }} - /> - + }} + /> + ); } @@ -108,13 +109,14 @@ function InventoryForm({ > {formik => (
+ - +
)}
diff --git a/awx/ui_next/src/screens/Inventory/shared/InventoryGroupForm.jsx b/awx/ui_next/src/screens/Inventory/shared/InventoryGroupForm.jsx index ccec0375ac..09b461a4be 100644 --- a/awx/ui_next/src/screens/Inventory/shared/InventoryGroupForm.jsx +++ b/awx/ui_next/src/screens/Inventory/shared/InventoryGroupForm.jsx @@ -6,11 +6,11 @@ import { Form, Card } from '@patternfly/react-core'; import { t } from '@lingui/macro'; import { CardBody } from '@components/Card'; -import FormRow from '@components/FormRow'; import FormField from '@components/FormField'; import FormActionGroup from '@components/FormActionGroup/FormActionGroup'; import { VariablesField } from '@components/CodeMirrorInput'; import { required } from '@util/validators'; +import { FormColumnLayout } from '@components/FormLayout'; function InventoryGroupForm({ i18n, @@ -31,7 +31,7 @@ function InventoryGroupForm({ {formik => (
- + - - - - - {error ?
error
: null} + + {error ?
error
: null} + )}
diff --git a/awx/ui_next/src/screens/Organization/shared/OrganizationForm.jsx b/awx/ui_next/src/screens/Organization/shared/OrganizationForm.jsx index d7b32f32cc..f8a4b02205 100644 --- a/awx/ui_next/src/screens/Organization/shared/OrganizationForm.jsx +++ b/awx/ui_next/src/screens/Organization/shared/OrganizationForm.jsx @@ -11,12 +11,12 @@ import { ConfigContext } from '@contexts/Config'; import AnsibleSelect from '@components/AnsibleSelect'; import ContentError from '@components/ContentError'; import ContentLoading from '@components/ContentLoading'; -import FormRow from '@components/FormRow'; import FormField, { FormSubmitError } from '@components/FormField'; import FormActionGroup from '@components/FormActionGroup/FormActionGroup'; import { InstanceGroupsLookup } from '@components/Lookup/'; import { getAddedAndRemoved } from '@util/lists'; import { required, minMaxValue } from '@util/validators'; +import { FormColumnLayout } from '@components/FormLayout'; function OrganizationFormFields({ i18n, @@ -166,6 +166,7 @@ function OrganizationForm({ > {formik => (
+ +
)}
diff --git a/awx/ui_next/src/screens/Project/shared/ProjectForm.jsx b/awx/ui_next/src/screens/Project/shared/ProjectForm.jsx index e18eb7fa3a..220462f98f 100644 --- a/awx/ui_next/src/screens/Project/shared/ProjectForm.jsx +++ b/awx/ui_next/src/screens/Project/shared/ProjectForm.jsx @@ -14,11 +14,10 @@ import FormField, { FieldTooltip, FormSubmitError, } from '@components/FormField'; -import FormRow from '@components/FormRow'; import OrganizationLookup from '@components/Lookup/OrganizationLookup'; import { CredentialTypesAPI, ProjectsAPI } from '@api'; import { required } from '@util/validators'; -import styled from 'styled-components'; +import { FormColumnLayout, SubFormLayout } from '@components/FormLayout'; import { GitSubForm, HgSubForm, @@ -146,147 +145,145 @@ function ProjectFormFields({ return ( <> - - - + + organizationHelpers.setTouched()} - onChange={value => { + onChange={value => { organizationHelpers.setValue(value.id); - setOrganization(value); - }} - value={organization} - required - /> - + - + { - if (label === 'Manual') { - value = 'manual'; - } - return { - label, - value, - key: value, - }; - }), - ]} - onChange={(event, value) => { + 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) => { scmTypeHelpers.setValue(value); resetScmTypeFields(value, formik); - }} - /> - - {formik.values.scm_type !== '' && ( - - - {i18n._(t`Type Details`)} - - { - { - manual: ( - - ), - git: ( - + + {formik.values.scm_type !== '' && ( + + {i18n._(t`Type Details`)} + + { + { + manual: ( + + ), + git: ( + - ), - hg: ( - + ), + hg: ( + - ), - svn: ( - + ), + svn: ( + - ), - insights: ( - + ), + insights: ( + - ), - }[formik.values.scm_type] - } - - )} - - {({ custom_virtualenvs }) => - custom_virtualenvs && - custom_virtualenvs.length > 1 && ( - - {({ field }) => ( - - - + ), + }[formik.values.scm_type] + } + + + )} + + {({ custom_virtualenvs }) => + custom_virtualenvs && + custom_virtualenvs.length > 1 && ( + + + datum !== '/venv/ansible/') - .map(datum => ({ - label: datum, - value: datum, - key: datum, - })), - ]} + value: '/venv/ansible/', + key: 'default', + }, + ...custom_virtualenvs + .filter(datum => datum !== '/venv/ansible/') + .map(datum => ({ + label: datum, + value: datum, + key: datum, + })), + ]} {...venvField} - /> - - ) - } - + /> + + ) + } + ); } @@ -373,6 +370,7 @@ function ProjectForm({ i18n, project, submitError, ...props }) { onSubmit={formik.handleSubmit} css="padding: 0 24px" > + - - + + + )}
diff --git a/awx/ui_next/src/screens/Project/shared/ProjectSubForms/SharedFields.jsx b/awx/ui_next/src/screens/Project/shared/ProjectSubForms/SharedFields.jsx index 199c1b2d2a..dca5a88d96 100644 --- a/awx/ui_next/src/screens/Project/shared/ProjectSubForms/SharedFields.jsx +++ b/awx/ui_next/src/screens/Project/shared/ProjectSubForms/SharedFields.jsx @@ -6,12 +6,10 @@ import CredentialLookup from '@components/Lookup/CredentialLookup'; import FormField, { CheckboxField } from '@components/FormField'; import { required } from '@util/validators'; 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; -`; +import { + FormCheckboxLayout, + FormFullWidthLayout, +} from '@components/FormLayout'; export const UrlFormField = withI18n()(({ i18n, tooltip }) => ( { - onCredentialSelection('scm', value); + { + onCredentialSelection('scm', value); credHelpers.setValue(value ? value.id : ''); - }} - /> + }} + /> ); } ); export const ScmTypeOptions = withI18n()( ({ i18n, scmUpdateOnLaunch, hideAllowOverride }) => ( - <> - - + + + {!hideAllowOverride && ( @@ -101,15 +95,16 @@ export const ScmTypeOptions = withI18n()( label={i18n._(t`Allow Branch Override`)} tooltip={i18n._( t`Allow changing the SCM branch or revision in a job - template that uses this project.` + template that uses this project.` )} /> )} - + + {scmUpdateOnLaunch && ( <> - {i18n._(t`Option Details`)} + {i18n._(t`Option Details`)} )} - + ) ); diff --git a/awx/ui_next/src/screens/Project/shared/ProjectSubForms/index.js b/awx/ui_next/src/screens/Project/shared/ProjectSubForms/index.js index 51386fe017..022673187f 100644 --- a/awx/ui_next/src/screens/Project/shared/ProjectSubForms/index.js +++ b/awx/ui_next/src/screens/Project/shared/ProjectSubForms/index.js @@ -1,4 +1,3 @@ -export { SubFormTitle } from './SharedFields'; export { default as GitSubForm } from './GitSubForm'; export { default as HgSubForm } from './HgSubForm'; export { default as InsightsSubForm } from './InsightsSubForm'; diff --git a/awx/ui_next/src/screens/Team/shared/TeamForm.jsx b/awx/ui_next/src/screens/Team/shared/TeamForm.jsx index d5ba307fa2..f515074150 100644 --- a/awx/ui_next/src/screens/Team/shared/TeamForm.jsx +++ b/awx/ui_next/src/screens/Team/shared/TeamForm.jsx @@ -8,6 +8,7 @@ import FormActionGroup from '@components/FormActionGroup/FormActionGroup'; import FormField, { FormSubmitError } from '@components/FormField'; import OrganizationLookup from '@components/Lookup/OrganizationLookup'; import { required } from '@util/validators'; +import { FormColumnLayout } from '@components/FormLayout'; function TeamFormFields(props) { const { team, i18n } = props; @@ -23,31 +24,31 @@ function TeamFormFields(props) { return ( <> - - - + + orgHelpers.setTouched('organization')} - onChange={value => { + onChange={value => { orgHelpers.setValue(value.id); - setOrganization(value); - }} - value={organization} - required - /> + setOrganization(value); + }} + value={organization} + required + /> ); } @@ -68,14 +69,15 @@ function TeamForm(props) {
+ - - + + +
)} diff --git a/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx b/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx index 1e3dd2caed..db1229cbe1 100644 --- a/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx +++ b/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx @@ -22,10 +22,13 @@ import FormField, { FormSubmitError, } from '@components/FormField'; import FieldWithPrompt from '@components/FieldWithPrompt'; -import FormRow from '@components/FormRow'; +import { + FormColumnLayout, + FormFullWidthLayout, + FormCheckboxLayout, +} from '@components/FormLayout'; import CollapsibleSection from '@components/CollapsibleSection'; import { required } from '@util/validators'; -import styled from 'styled-components'; import { JobTemplate } from '@types'; import { InventoryLookup, @@ -37,17 +40,6 @@ import { JobTemplatesAPI, ProjectsAPI } from '@api'; import LabelSelect from './LabelSelect'; import PlaybookSelect from './PlaybookSelect'; -const GridFormGroup = styled(FormGroup)` - & > label { - grid-column: 1 / -1; - } - - && { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); - } -`; - class JobTemplateForm extends Component { static propTypes = { template: JobTemplate, @@ -211,9 +203,10 @@ class JobTemplateForm extends Component { } const AdvancedFieldsWrapper = template.isNew ? CollapsibleSection : 'div'; + return (
- + - - - - {({ field }) => ( - - - setFieldValue('labels', labels)} - onError={this.setContentError} - /> - - )} - - - - - {({ field }) => ( - - 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.` - )} - /> - )} - - - - - - {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`)}{' '} - ansible.cfg.{' '} - {i18n._(t`Refer to the Ansible documentation for details - about the configuration file.`)} - - } - /> - - + + {({ field }) => ( - + - setFieldValue('labels', labels)} + onError={this.setContentError} /> )} - - - - {({ field, form }) => ( - - -
- - form.setFieldValue(field.name, checked) - } + + {({ field }) => ( + + 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.` + )} + /> + )} + + + + + {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`)}{' '} + ansible.cfg.{' '} + {i18n._(t`Refer to the Ansible documentation for details + about the configuration file.`)} + + } + /> + + + {({ field }) => ( + + + + + )} + + + + + {({ field, form }) => ( + + +
+ + form.setFieldValue(field.name, checked) + } + /> +
+
+ )} +
+ + + {({ field, form }) => ( + + form.setFieldValue(field.name, value) + } + tooltip={i18n._(t`Select the Instance Groups for this Organization + to run on.`)} + /> + )} + + + {({ field, form }) => ( + + + + form.setFieldValue(field.name, value) + } + /> + + )} + + + {({ field, form }) => ( + + + + form.setFieldValue(field.name, value) + } + /> + + )} + + + + + + {i18n._(t`Provisioning Callbacks`)} +   + + + } + id="option-callbacks" + isChecked={allowCallbacks} + onChange={checked => { + this.setState({ allowCallbacks: checked }); + }} + /> + + + + + + {allowCallbacks && ( + <> + {callbackUrl && ( + + + + )} + -
-
- )} -
-
- - {({ field, form }) => ( - form.setFieldValue(field.name, value)} - tooltip={i18n._(t`Select the Instance Groups for this Organization - to run on.`)} - /> - )} - - - {({ field, form }) => ( - - - form.setFieldValue(field.name, value)} - /> - - )} - - - {({ field, form }) => ( - - - form.setFieldValue(field.name, value)} - /> - - )} - - - - - {i18n._(t`Provisioning Callbacks`)} -   - - - } - id="option-callbacks" - isChecked={allowCallbacks} - onChange={checked => { - this.setState({ allowCallbacks: checked }); - }} - /> - - - -
- - {callbackUrl && ( - - - - )} - - -
-
- - + + )} + + + + + + ); } diff --git a/awx/ui_next/src/screens/User/shared/UserForm.jsx b/awx/ui_next/src/screens/User/shared/UserForm.jsx index 3a15e82fda..d51fc0cf9b 100644 --- a/awx/ui_next/src/screens/User/shared/UserForm.jsx +++ b/awx/ui_next/src/screens/User/shared/UserForm.jsx @@ -10,9 +10,9 @@ import FormField, { PasswordField, FormSubmitError, } from '@components/FormField'; -import FormRow from '@components/FormRow'; import OrganizationLookup from '@components/Lookup/OrganizationLookup'; import { required, requiredEmail } from '@util/validators'; +import { FormColumnLayout } from '@components/FormLayout'; function UserFormFields({ user, i18n }) { const [organization, setOrganization] = useState(null); @@ -41,7 +41,7 @@ function UserFormFields({ user, i18n }) { const organizationFieldArr = useField({ name: 'organization', validate: required(i18n._(t`Select a value for this field`), i18n), - }); + }); const organizationMeta = organizationFieldArr[1]; const organizationHelpers = organizationFieldArr[2]; @@ -99,6 +99,7 @@ function UserFormFields({ user, i18n }) { type="text" /> {!user.id && ( +<<<<<<< HEAD +======= + organizationHelpers.setTouched()} + onChange={value => { + organizationHelpers.setValue(value.id); + setOrganization(value); + }} + value={organization} + required + /> + )} + + + +>>>>>>> update forms from FormRow to using FormLayout components - ); + ); } function UserForm({ user, handleCancel, handleSubmit, submitError, i18n }) { @@ -173,12 +202,14 @@ function UserForm({ user, handleCancel, handleSubmit, submitError, i18n }) { > {formik => (
+ - - + + +
)} From ce909093c03ba6b61f7a78a1da27520677963373 Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Tue, 18 Feb 2020 14:36:18 -0500 Subject: [PATCH 03/19] update expanding container to trigger height check when any child changes --- .../src/components/CollapsibleSection/ExpandingContainer.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/ui_next/src/components/CollapsibleSection/ExpandingContainer.jsx b/awx/ui_next/src/components/CollapsibleSection/ExpandingContainer.jsx index 35804d324d..b89ce94d7b 100644 --- a/awx/ui_next/src/components/CollapsibleSection/ExpandingContainer.jsx +++ b/awx/ui_next/src/components/CollapsibleSection/ExpandingContainer.jsx @@ -19,7 +19,7 @@ function ExpandingContainer({ isExpanded, children }) { }); useEffect(() => { setContentHeight(ref.current.scrollHeight); - }, [setContentHeight]); + }, [setContentHeight, children]); const height = isExpanded ? contentHeight : '0'; return ( Date: Tue, 18 Feb 2020 14:36:48 -0500 Subject: [PATCH 04/19] add text area option to FormField component --- .../src/components/FormField/FormField.jsx | 95 +++++++++++++------ 1 file changed, 65 insertions(+), 30 deletions(-) diff --git a/awx/ui_next/src/components/FormField/FormField.jsx b/awx/ui_next/src/components/FormField/FormField.jsx index b92fee5484..19b1b8a0af 100644 --- a/awx/ui_next/src/components/FormField/FormField.jsx +++ b/awx/ui_next/src/components/FormField/FormField.jsx @@ -1,6 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; import { useField } from 'formik'; +import { + FormGroup, + TextInput, + TextArea, + Tooltip, +} from '@patternfly/react-core'; import { QuestionCircleIcon as PFQuestionCircleIcon } from '@patternfly/react-icons'; import styled from 'styled-components'; @@ -17,52 +23,81 @@ function FormField(props) { tooltipMaxWidth, validate, isRequired, + type, ...rest } = props; const [field, meta] = useField({ name, validate }); const isValid = !(meta.touched && meta.error); - return ( - + {(type === 'textarea' && ( + + {tooltip && ( + + + + )} +