update forms from FormRow to using FormLayout components

This commit is contained in:
John Mitchell
2020-02-18 14:35:41 -05:00
parent ff823c9fdb
commit df13a8fea9
17 changed files with 655 additions and 608 deletions

View File

@@ -3,6 +3,7 @@ import { string, bool } from 'prop-types';
import { useField } from 'formik'; import { useField } from 'formik';
import { Split, SplitItem } from '@patternfly/react-core'; import { Split, SplitItem } from '@patternfly/react-core';
import { yamlToJson, jsonToYaml, isJson } from '@util/yaml'; import { yamlToJson, jsonToYaml, isJson } from '@util/yaml';
import { FormFullWidthLayout } from '@components/FormLayout';
import CodeMirrorInput from './CodeMirrorInput'; import CodeMirrorInput from './CodeMirrorInput';
import YamlJsonToggle from './YamlJsonToggle'; import YamlJsonToggle from './YamlJsonToggle';
import { JSON_MODE, YAML_MODE } from './constants'; 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); const [mode, setMode] = useState(isJson(field.value) ? JSON_MODE : YAML_MODE);
return ( return (
<Field name={name}> <FormFullWidthLayout>
{({ field, form }) => ( <Split gutter="sm">
<div className="pf-c-form__group"> <SplitItem>
<Split gutter="sm"> <label htmlFor={id} className="pf-c-form__label">
<SplitItem> <span className="pf-c-form__label-text">{label}</span>
<label htmlFor={id} className="pf-c-form__label"> </label>
<span className="pf-c-form__label-text">{label}</span> </SplitItem>
</label> <SplitItem>
</SplitItem> <YamlJsonToggle
<SplitItem>
<YamlJsonToggle
mode={mode}
onChange={newMode => {
try {
const newVal =
newMode === YAML_MODE
? jsonToYaml(field.value)
: yamlToJson(field.value);
helpers.setValue(newVal);
setMode(newMode);
} catch (err) {
helpers.setError(err.message);
}
}}
/>
</SplitItem>
</Split>
<CodeMirrorInput
mode={mode} mode={mode}
readOnly={readOnly} onChange={newMode => {
{...field} try {
onChange={newVal => { const newVal =
helpers.setValue(newVal); newMode === YAML_MODE
? jsonToYaml(field.value)
: yamlToJson(field.value);
helpers.setValue(newVal);
setMode(newMode);
} catch (err) {
helpers.setError(err.message);
}
}} }}
hasErrors={!!meta.error}
/> />
</SplitItem>
</Split>
<CodeMirrorInput
mode={mode}
readOnly={readOnly}
{...field}
onChange={newVal => {
helpers.setValue(newVal);
}}
hasErrors={!!meta.error}
/>
{meta.error ? ( {meta.error ? (
<div className="pf-c-form__helper-text pf-m-error" aria-live="polite"> <div className="pf-c-form__helper-text pf-m-error" aria-live="polite">
{meta.error} {meta.error}
</div>
) : null}
</div> </div>
)} ) : null}
</Field> </FormFullWidthLayout>
); );
} }
VariablesField.propTypes = { VariablesField.propTypes = {

View File

@@ -9,6 +9,7 @@ const ActionGroup = styled(PFActionGroup)`
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
--pf-c-form__group--m-action--MarginTop: 0; --pf-c-form__group--m-action--MarginTop: 0;
grid-column: 1 / -1;
.pf-c-form__actions { .pf-c-form__actions {
& > button { & > button {

View File

@@ -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;
}
`;

View File

@@ -0,0 +1,6 @@
export {
FormColumnLayout,
FormFullWidthLayout,
FormCheckboxLayout,
SubFormLayout,
} from './FormLayout';

View File

@@ -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 <Row className={className}>{children}</Row>;
}

View File

@@ -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(<FormRow />);
expect(wrapper).toHaveLength(1);
});
});

View File

@@ -1 +0,0 @@
export { default } from './FormRow';

View File

@@ -8,12 +8,12 @@ import { t } from '@lingui/macro';
import { Form } from '@patternfly/react-core'; import { Form } from '@patternfly/react-core';
import FormRow from '@components/FormRow';
import FormField, { FormSubmitError } from '@components/FormField'; import FormField, { FormSubmitError } from '@components/FormField';
import FormActionGroup from '@components/FormActionGroup/FormActionGroup'; import FormActionGroup from '@components/FormActionGroup/FormActionGroup';
import { VariablesField } from '@components/CodeMirrorInput'; import { VariablesField } from '@components/CodeMirrorInput';
import { required } from '@util/validators'; import { required } from '@util/validators';
import { InventoryLookup } from '@components/Lookup'; import { InventoryLookup } from '@components/Lookup';
import { FormColumnLayout } from '@components/FormLayout';
function HostFormFields({ host, i18n }) { function HostFormFields({ host, i18n }) {
const [inventory, setInventory] = useState( const [inventory, setInventory] = useState(
@@ -30,43 +30,43 @@ function HostFormFields({ host, i18n }) {
return ( return (
<> <>
<FormField <FormField
id="host-name" id="host-name"
name="name" name="name"
type="text" type="text"
label={i18n._(t`Name`)} label={i18n._(t`Name`)}
validate={required(null, i18n)} validate={required(null, i18n)}
isRequired isRequired
/> />
<FormField <FormField
id="host-description" id="host-description"
name="description" name="description"
type="text" type="text"
label={i18n._(t`Description`)} label={i18n._(t`Description`)}
/> />
{hostAddMatch && ( {hostAddMatch && (
<InventoryLookup <InventoryLookup
value={inventory} value={inventory}
onBlur={() => inventoryHelpers.setTouched()} onBlur={() => inventoryHelpers.setTouched()}
tooltip={i18n._( tooltip={i18n._(
t`Select the inventory that this host will belong to.` t`Select the inventory that this host will belong to.`
)} )}
isValid={!inventoryMeta.touched || !inventoryMeta.error} isValid={!inventoryMeta.touched || !inventoryMeta.error}
helperTextInvalid={inventoryMeta.error} helperTextInvalid={inventoryMeta.error}
onChange={value => { onChange={value => {
inventoryHelpers.setValuealue(value.id); inventoryHelpers.setValuealue(value.id);
setInventory(value); setInventory(value);
}} }}
required required
touched={inventoryMeta.touched} touched={inventoryMeta.touched}
error={inventoryMeta.error} error={inventoryMeta.error}
/> />
)} )}
<VariablesField <VariablesField
id="host-variables" id="host-variables"
name="variables" name="variables"
label={i18n._(t`Variables`)} label={i18n._(t`Variables`)}
/> />
</> </>
); );
} }
@@ -84,12 +84,14 @@ function HostForm({ handleSubmit, host, submitError, handleCancel, ...rest }) {
> >
{formik => ( {formik => (
<Form autoComplete="off" onSubmit={formik.handleSubmit}> <Form autoComplete="off" onSubmit={formik.handleSubmit}>
<FormColumnLayout>
<HostFormFields host={host} {...rest} /> <HostFormFields host={host} {...rest} />
<FormSubmitError error={submitError} /> <FormSubmitError error={submitError} />
<FormActionGroup <FormActionGroup
onCancel={handleCancel} onCancel={handleCancel}
onSubmit={formik.handleSubmit} onSubmit={formik.handleSubmit}
/> />
</FormColumnLayout>
</Form> </Form>
)} )}
</Formik> </Formik>

View File

@@ -12,6 +12,7 @@ import { required } from '@util/validators';
import InstanceGroupsLookup from '@components/Lookup/InstanceGroupsLookup'; import InstanceGroupsLookup from '@components/Lookup/InstanceGroupsLookup';
import OrganizationLookup from '@components/Lookup/OrganizationLookup'; import OrganizationLookup from '@components/Lookup/OrganizationLookup';
import CredentialLookup from '@components/Lookup/CredentialLookup'; import CredentialLookup from '@components/Lookup/CredentialLookup';
import { FormColumnLayout } from '@components/FormLayout';
function InventoryFormFields({ i18n, credentialTypeId }) { function InventoryFormFields({ i18n, credentialTypeId }) {
const [organizationField, organizationMeta, organizationHelpers] = useField({ const [organizationField, organizationMeta, organizationHelpers] = useField({
@@ -27,52 +28,52 @@ function InventoryFormFields({ i18n, credentialTypeId }) {
const insightsCredentialHelpers = insightsCredentialFieldArr[2]; const insightsCredentialHelpers = insightsCredentialFieldArr[2];
return ( return (
<> <>
<FormField <FormField
id="inventory-name" id="inventory-name"
label={i18n._(t`Name`)} label={i18n._(t`Name`)}
name="name" name="name"
type="text" type="text"
validate={required(null, i18n)} validate={required(null, i18n)}
isRequired isRequired
/> />
<FormField <FormField
id="inventory-description" id="inventory-description"
label={i18n._(t`Description`)} label={i18n._(t`Description`)}
name="description" name="description"
type="text" type="text"
/> />
<OrganizationLookup <OrganizationLookup
helperTextInvalid={organizationMeta.error} helperTextInvalid={organizationMeta.error}
isValid={!organizationMeta.touched || !organizationMeta.error} isValid={!organizationMeta.touched || !organizationMeta.error}
onBlur={() => organizationHelpers.setTouched()} onBlur={() => organizationHelpers.setTouched()}
onChange={value => { onChange={value => {
organizationHelpers.setValue(value); organizationHelpers.setValue(value);
}} }}
value={organizationField.value} value={organizationField.value}
touched={organizationMeta.touched} touched={organizationMeta.touched}
error={organizationMeta.error} error={organizationMeta.error}
required required
/> />
<CredentialLookup <CredentialLookup
label={i18n._(t`Insights Credential`)} label={i18n._(t`Insights Credential`)}
credentialTypeId={credentialTypeId} credentialTypeId={credentialTypeId}
onChange={value => insightsCredentialHelpers.setValue(value)} onChange={value => insightsCredentialHelpers.setValue(value)}
value={insightsCredentialField.value} value={insightsCredentialField.value}
/> />
<InstanceGroupsLookup <InstanceGroupsLookup
value={instanceGroupsField.value} value={instanceGroupsField.value}
onChange={value => { onChange={value => {
instanceGroupsHelpers.setValue(value); instanceGroupsHelpers.setValue(value);
}} }}
/> />
<VariablesField <VariablesField
tooltip={i18n._( tooltip={i18n._(
t`Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax` t`Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax`
)} )}
id="inventory-variables" id="inventory-variables"
name="variables" name="variables"
label={i18n._(t`Variables`)} label={i18n._(t`Variables`)}
/> />
</> </>
); );
} }
@@ -108,13 +109,14 @@ function InventoryForm({
> >
{formik => ( {formik => (
<Form autoComplete="off" onSubmit={formik.handleSubmit}> <Form autoComplete="off" onSubmit={formik.handleSubmit}>
<FormColumnLayout>
<InventoryFormFields {...rest} /> <InventoryFormFields {...rest} />
<FormSubmitError error={submitError} /> <FormSubmitError error={submitError} />
<FormActionGroup <FormActionGroup
onCancel={onCancel} onCancel={onCancel}
onSubmit={formik.handleSubmit} onSubmit={formik.handleSubmit}
/> />
</FormRow> </FormColumnLayout>
</Form> </Form>
)} )}
</Formik> </Formik>

View File

@@ -6,11 +6,11 @@ import { Form, Card } from '@patternfly/react-core';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { CardBody } from '@components/Card'; import { CardBody } from '@components/Card';
import FormRow from '@components/FormRow';
import FormField from '@components/FormField'; import FormField from '@components/FormField';
import FormActionGroup from '@components/FormActionGroup/FormActionGroup'; import FormActionGroup from '@components/FormActionGroup/FormActionGroup';
import { VariablesField } from '@components/CodeMirrorInput'; import { VariablesField } from '@components/CodeMirrorInput';
import { required } from '@util/validators'; import { required } from '@util/validators';
import { FormColumnLayout } from '@components/FormLayout';
function InventoryGroupForm({ function InventoryGroupForm({
i18n, i18n,
@@ -31,7 +31,7 @@ function InventoryGroupForm({
<Formik initialValues={initialValues} onSubmit={handleSubmit}> <Formik initialValues={initialValues} onSubmit={handleSubmit}>
{formik => ( {formik => (
<Form autoComplete="off" onSubmit={formik.handleSubmit}> <Form autoComplete="off" onSubmit={formik.handleSubmit}>
<FormRow css="grid-template-columns: repeat(auto-fit, minmax(300px, 500px));"> <FormColumnLayout>
<FormField <FormField
id="inventoryGroup-name" id="inventoryGroup-name"
name="name" name="name"
@@ -46,19 +46,17 @@ function InventoryGroupForm({
type="text" type="text"
label={i18n._(t`Description`)} label={i18n._(t`Description`)}
/> />
</FormRow>
<FormRow>
<VariablesField <VariablesField
id="host-variables" id="host-variables"
name="variables" name="variables"
label={i18n._(t`Variables`)} label={i18n._(t`Variables`)}
/> />
</FormRow> <FormActionGroup
<FormActionGroup onCancel={handleCancel}
onCancel={handleCancel} onSubmit={formik.handleSubmit}
onSubmit={formik.handleSubmit} />
/> {error ? <div>error</div> : null}
{error ? <div>error</div> : null} </FormColumnLayout>
</Form> </Form>
)} )}
</Formik> </Formik>

View File

@@ -11,12 +11,12 @@ import { ConfigContext } from '@contexts/Config';
import AnsibleSelect from '@components/AnsibleSelect'; import AnsibleSelect from '@components/AnsibleSelect';
import ContentError from '@components/ContentError'; import ContentError from '@components/ContentError';
import ContentLoading from '@components/ContentLoading'; import ContentLoading from '@components/ContentLoading';
import FormRow from '@components/FormRow';
import FormField, { FormSubmitError } from '@components/FormField'; import FormField, { FormSubmitError } from '@components/FormField';
import FormActionGroup from '@components/FormActionGroup/FormActionGroup'; import FormActionGroup from '@components/FormActionGroup/FormActionGroup';
import { InstanceGroupsLookup } from '@components/Lookup/'; import { InstanceGroupsLookup } from '@components/Lookup/';
import { getAddedAndRemoved } from '@util/lists'; import { getAddedAndRemoved } from '@util/lists';
import { required, minMaxValue } from '@util/validators'; import { required, minMaxValue } from '@util/validators';
import { FormColumnLayout } from '@components/FormLayout';
function OrganizationFormFields({ function OrganizationFormFields({
i18n, i18n,
@@ -166,6 +166,7 @@ function OrganizationForm({
> >
{formik => ( {formik => (
<Form autoComplete="off" onSubmit={formik.handleSubmit}> <Form autoComplete="off" onSubmit={formik.handleSubmit}>
<FormColumnLayout>
<OrganizationFormFields <OrganizationFormFields
instanceGroups={instanceGroups} instanceGroups={instanceGroups}
setInstanceGroups={setInstanceGroups} setInstanceGroups={setInstanceGroups}
@@ -176,6 +177,7 @@ function OrganizationForm({
onCancel={handleCancel} onCancel={handleCancel}
onSubmit={formik.handleSubmit} onSubmit={formik.handleSubmit}
/> />
</FormColumnLayout>
</Form> </Form>
)} )}
</Formik> </Formik>

View File

@@ -14,11 +14,10 @@ import FormField, {
FieldTooltip, FieldTooltip,
FormSubmitError, FormSubmitError,
} from '@components/FormField'; } from '@components/FormField';
import FormRow from '@components/FormRow';
import OrganizationLookup from '@components/Lookup/OrganizationLookup'; import OrganizationLookup from '@components/Lookup/OrganizationLookup';
import { CredentialTypesAPI, ProjectsAPI } from '@api'; import { CredentialTypesAPI, ProjectsAPI } from '@api';
import { required } from '@util/validators'; import { required } from '@util/validators';
import styled from 'styled-components'; import { FormColumnLayout, SubFormLayout } from '@components/FormLayout';
import { import {
GitSubForm, GitSubForm,
HgSubForm, HgSubForm,
@@ -146,147 +145,145 @@ function ProjectFormFields({
return ( return (
<> <>
<FormField <FormField
id="project-name" id="project-name"
label={i18n._(t`Name`)} label={i18n._(t`Name`)}
name="name" name="name"
type="text" type="text"
validate={required(null, i18n)} validate={required(null, i18n)}
isRequired isRequired
/> />
<FormField <FormField
id="project-description" id="project-description"
label={i18n._(t`Description`)} label={i18n._(t`Description`)}
name="description" name="description"
type="text" type="text"
/> />
<OrganizationLookup <OrganizationLookup
helperTextInvalid={organizationMeta.error} helperTextInvalid={organizationMeta.error}
isValid={!organizationMeta.touched || !organizationMeta.error} isValid={!organizationMeta.touched || !organizationMeta.error}
onBlur={() => organizationHelpers.setTouched()} onBlur={() => organizationHelpers.setTouched()}
onChange={value => { onChange={value => {
organizationHelpers.setValue(value.id); organizationHelpers.setValue(value.id);
setOrganization(value); setOrganization(value);
}} }}
value={organization} value={organization}
required required
/> />
<FormGroup <FormGroup
fieldId="project-scm-type" fieldId="project-scm-type"
helperTextInvalid={scmTypeMeta.error} helperTextInvalid={scmTypeMeta.error}
isRequired isRequired
isValid={!scmTypeMeta.touched || !scmTypeMeta.error} isValid={!scmTypeMeta.touched || !scmTypeMeta.error}
label={i18n._(t`SCM Type`)} label={i18n._(t`SCM Type`)}
> >
<AnsibleSelect <AnsibleSelect
{...scmTypeField} {...scmTypeField}
id="scm_type" id="scm_type"
data={[ data={[
{ {
value: '', value: '',
key: '', key: '',
label: i18n._(t`Choose an SCM Type`), label: i18n._(t`Choose an SCM Type`),
isDisabled: true, isDisabled: true,
}, },
...scmTypeOptions.map(([value, label]) => { ...scmTypeOptions.map(([value, label]) => {
if (label === 'Manual') { if (label === 'Manual') {
value = 'manual'; value = 'manual';
} }
return { return {
label, label,
value, value,
key: value, key: value,
}; };
}), }),
]} ]}
onChange={(event, value) => { onChange={(event, value) => {
scmTypeHelpers.setValue(value); scmTypeHelpers.setValue(value);
resetScmTypeFields(value, formik); resetScmTypeFields(value, formik);
}} }}
/> />
</FormGroup> </FormGroup>
{formik.values.scm_type !== '' && ( {formik.values.scm_type !== '' && (
<ScmTypeFormRow> <SubFormLayout>
<SubFormTitle size="md"> <Title size="md">{i18n._(t`Type Details`)}</Title>
{i18n._(t`Type Details`)} <FormColumnLayout>
</SubFormTitle> {
{ {
{ manual: (
manual: ( <ManualSubForm
<ManualSubForm localPath={formik.initialValues.local_path}
localPath={formik.initialValues.local_path} project_base_dir={project_base_dir}
project_base_dir={project_base_dir} project_local_paths={project_local_paths}
project_local_paths={project_local_paths} />
/> ),
), git: (
git: ( <GitSubForm
<GitSubForm credential={credentials.scm}
credential={credentials.scm} onCredentialSelection={handleCredentialSelection}
onCredentialSelection={handleCredentialSelection}
scmUpdateOnLaunch={formik.values.scm_update_on_launch} scmUpdateOnLaunch={formik.values.scm_update_on_launch}
/> />
), ),
hg: ( hg: (
<HgSubForm <HgSubForm
credential={credentials.scm} credential={credentials.scm}
onCredentialSelection={handleCredentialSelection} onCredentialSelection={handleCredentialSelection}
scmUpdateOnLaunch={formik.values.scm_update_on_launch} scmUpdateOnLaunch={formik.values.scm_update_on_launch}
/> />
), ),
svn: ( svn: (
<SvnSubForm <SvnSubForm
credential={credentials.scm} credential={credentials.scm}
onCredentialSelection={handleCredentialSelection} onCredentialSelection={handleCredentialSelection}
scmUpdateOnLaunch={formik.values.scm_update_on_launch} scmUpdateOnLaunch={formik.values.scm_update_on_launch}
/> />
), ),
insights: ( insights: (
<InsightsSubForm <InsightsSubForm
credential={credentials.insights} credential={credentials.insights}
onCredentialSelection={handleCredentialSelection} onCredentialSelection={handleCredentialSelection}
scmUpdateOnLaunch={formik.values.scm_update_on_launch} scmUpdateOnLaunch={formik.values.scm_update_on_launch}
/> />
), ),
}[formik.values.scm_type] }[formik.values.scm_type]
} }
</ScmTypeFormRow> </FormColumnLayout>
)} </SubFormLayout>
<Config> )}
{({ custom_virtualenvs }) => <Config>
custom_virtualenvs && {({ custom_virtualenvs }) =>
custom_virtualenvs.length > 1 && ( custom_virtualenvs &&
<Field name="custom_virtualenv"> custom_virtualenvs.length > 1 && (
{({ field }) => ( <FormGroup
<FormGroup fieldId="project-custom-virtualenv"
fieldId="project-custom-virtualenv" label={i18n._(t`Ansible Environment`)}
label={i18n._(t`Ansible Environment`)} >
> <FieldTooltip
<FieldTooltip content={i18n._(t`Select the playbook to be executed by
content={i18n._(t`Select the playbook to be executed by this job.`)}
this job.`)} />
/> <AnsibleSelect
<AnsibleSelect id="project-custom-virtualenv"
id="project-custom-virtualenv" data={[
data={[ {
{
label: i18n._(t`Use Default Ansible Environment`), label: i18n._(t`Use Default Ansible Environment`),
value: '/venv/ansible/', value: '/venv/ansible/',
key: 'default', key: 'default',
}, },
...custom_virtualenvs ...custom_virtualenvs
.filter(datum => datum !== '/venv/ansible/') .filter(datum => datum !== '/venv/ansible/')
.map(datum => ({ .map(datum => ({
label: datum, label: datum,
value: datum, value: datum,
key: datum, key: datum,
})), })),
]} ]}
{...venvField} {...venvField}
/> />
</FormGroup> </FormGroup>
) )
} }
</Config> </Config>
</> </>
); );
} }
@@ -373,6 +370,7 @@ function ProjectForm({ i18n, project, submitError, ...props }) {
onSubmit={formik.handleSubmit} onSubmit={formik.handleSubmit}
css="padding: 0 24px" css="padding: 0 24px"
> >
<FormColumnLayout>
<ProjectFormFields <ProjectFormFields
project_base_dir={project_base_dir} project_base_dir={project_base_dir}
project_local_paths={project_local_paths} project_local_paths={project_local_paths}
@@ -386,11 +384,12 @@ function ProjectForm({ i18n, project, submitError, ...props }) {
setOrganization={setOrganization} setOrganization={setOrganization}
organization={organization} organization={organization}
/> />
<FormSubmitError error={submitError} /> <FormSubmitError error={submitError} />
<FormActionGroup <FormActionGroup
onCancel={handleCancel} onCancel={handleCancel}
onSubmit={formik.handleSubmit} onSubmit={formik.handleSubmit}
/> />
</FormColumnLayout>
</Form> </Form>
)} )}
</Formik> </Formik>

View File

@@ -6,12 +6,10 @@ import CredentialLookup from '@components/Lookup/CredentialLookup';
import FormField, { CheckboxField } from '@components/FormField'; import FormField, { CheckboxField } from '@components/FormField';
import { required } from '@util/validators'; import { required } from '@util/validators';
import { FormGroup, Title } from '@patternfly/react-core'; import { FormGroup, Title } from '@patternfly/react-core';
import styled from 'styled-components'; import {
FormCheckboxLayout,
export const SubFormTitle = styled(Title)` FormFullWidthLayout,
--pf-c-title--m-md--FontWeight: 700; } from '@components/FormLayout';
grid-column: 1 / -1;
`;
export const UrlFormField = withI18n()(({ i18n, tooltip }) => ( export const UrlFormField = withI18n()(({ i18n, tooltip }) => (
<FormField <FormField
@@ -44,28 +42,24 @@ export const ScmCredentialFormField = withI18n()(
const credHelpers = useField('credential')[2]; const credHelpers = useField('credential')[2];
return ( return (
<CredentialLookup <CredentialLookup
credentialTypeId={credential.typeId} credentialTypeId={credential.typeId}
label={i18n._(t`SCM Credential`)} label={i18n._(t`SCM Credential`)}
value={credential.value} value={credential.value}
onChange={value => { onChange={value => {
onCredentialSelection('scm', value); onCredentialSelection('scm', value);
credHelpers.setValue(value ? value.id : ''); credHelpers.setValue(value ? value.id : '');
}} }}
/> />
); );
} }
); );
export const ScmTypeOptions = withI18n()( export const ScmTypeOptions = withI18n()(
({ i18n, scmUpdateOnLaunch, hideAllowOverride }) => ( ({ i18n, scmUpdateOnLaunch, hideAllowOverride }) => (
<> <FormFullWidthLayout>
<FormGroup <FormGroup fieldId="project-option-checkboxes" label={i18n._(t`Options`)}>
css="grid-column: 1/-1" <FormCheckboxLayout>
fieldId="project-option-checkboxes"
label={i18n._(t`Options`)}
>
<FormRow>
<CheckboxField <CheckboxField
id="option-scm-clean" id="option-scm-clean"
name="scm_clean" name="scm_clean"
@@ -80,9 +74,9 @@ export const ScmTypeOptions = withI18n()(
label={i18n._(t`Delete`)} label={i18n._(t`Delete`)}
tooltip={i18n._( tooltip={i18n._(
t`Delete the local repository in its entirety prior to t`Delete the local repository in its entirety prior to
performing an update. Depending on the size of the performing an update. Depending on the size of the
repository this may significantly increase the amount repository this may significantly increase the amount
of time required to complete an update.` of time required to complete an update.`
)} )}
/> />
<CheckboxField <CheckboxField
@@ -91,7 +85,7 @@ export const ScmTypeOptions = withI18n()(
label={i18n._(t`Update Revision on Launch`)} label={i18n._(t`Update Revision on Launch`)}
tooltip={i18n._( tooltip={i18n._(
t`Each time a job runs using this project, update the t`Each time a job runs using this project, update the
revision of the project prior to starting the job.` revision of the project prior to starting the job.`
)} )}
/> />
{!hideAllowOverride && ( {!hideAllowOverride && (
@@ -101,15 +95,16 @@ export const ScmTypeOptions = withI18n()(
label={i18n._(t`Allow Branch Override`)} label={i18n._(t`Allow Branch Override`)}
tooltip={i18n._( tooltip={i18n._(
t`Allow changing the SCM branch or revision in a job t`Allow changing the SCM branch or revision in a job
template that uses this project.` template that uses this project.`
)} )}
/> />
)} )}
</FormRow> </FormCheckboxLayout>
</FormGroup> </FormGroup>
{scmUpdateOnLaunch && ( {scmUpdateOnLaunch && (
<> <>
<SubFormTitle size="md">{i18n._(t`Option Details`)}</SubFormTitle> <Title size="md">{i18n._(t`Option Details`)}</Title>
<FormField <FormField
id="project-cache-timeout" id="project-cache-timeout"
name="scm_update_cache_timeout" name="scm_update_cache_timeout"
@@ -125,6 +120,6 @@ export const ScmTypeOptions = withI18n()(
/> />
</> </>
)} )}
</> </FormFullWidthLayout>
) )
); );

View File

@@ -1,4 +1,3 @@
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 InsightsSubForm } from './InsightsSubForm'; export { default as InsightsSubForm } from './InsightsSubForm';

View File

@@ -8,6 +8,7 @@ import FormActionGroup from '@components/FormActionGroup/FormActionGroup';
import FormField, { FormSubmitError } from '@components/FormField'; import FormField, { FormSubmitError } from '@components/FormField';
import OrganizationLookup from '@components/Lookup/OrganizationLookup'; import OrganizationLookup from '@components/Lookup/OrganizationLookup';
import { required } from '@util/validators'; import { required } from '@util/validators';
import { FormColumnLayout } from '@components/FormLayout';
function TeamFormFields(props) { function TeamFormFields(props) {
const { team, i18n } = props; const { team, i18n } = props;
@@ -23,31 +24,31 @@ function TeamFormFields(props) {
return ( return (
<> <>
<FormField <FormField
id="team-name" id="team-name"
label={i18n._(t`Name`)} label={i18n._(t`Name`)}
name="name" name="name"
type="text" type="text"
validate={required(null, i18n)} validate={required(null, i18n)}
isRequired isRequired
/> />
<FormField <FormField
id="team-description" id="team-description"
label={i18n._(t`Description`)} label={i18n._(t`Description`)}
name="description" name="description"
type="text" type="text"
/> />
<OrganizationLookup <OrganizationLookup
helperTextInvalid={orgMeta.error} helperTextInvalid={orgMeta.error}
isValid={!orgMeta.touched || !orgMeta.error} isValid={!orgMeta.touched || !orgMeta.error}
onBlur={() => orgHelpers.setTouched('organization')} onBlur={() => orgHelpers.setTouched('organization')}
onChange={value => { onChange={value => {
orgHelpers.setValue(value.id); orgHelpers.setValue(value.id);
setOrganization(value); setOrganization(value);
}} }}
value={organization} value={organization}
required required
/> />
</> </>
); );
} }
@@ -68,14 +69,15 @@ function TeamForm(props) {
<Form <Form
autoComplete="off" autoComplete="off"
onSubmit={formik.handleSubmit} onSubmit={formik.handleSubmit}
css="padding: 0 24px"
> >
<FormColumnLayout>
<TeamFormFields team={team} {...rest} /> <TeamFormFields team={team} {...rest} />
<FormSubmitError error={submitError} /> <FormSubmitError error={submitError} />
<FormActionGroup <FormActionGroup
onCancel={handleCancel} onCancel={handleCancel}
onSubmit={formik.handleSubmit} onSubmit={formik.handleSubmit}
/> />
</FormColumnLayout>
</Form> </Form>
)} )}
</Formik> </Formik>

View File

@@ -22,10 +22,13 @@ import FormField, {
FormSubmitError, FormSubmitError,
} from '@components/FormField'; } from '@components/FormField';
import FieldWithPrompt from '@components/FieldWithPrompt'; import FieldWithPrompt from '@components/FieldWithPrompt';
import FormRow from '@components/FormRow'; import {
FormColumnLayout,
FormFullWidthLayout,
FormCheckboxLayout,
} from '@components/FormLayout';
import CollapsibleSection from '@components/CollapsibleSection'; import CollapsibleSection from '@components/CollapsibleSection';
import { required } from '@util/validators'; import { required } from '@util/validators';
import styled from 'styled-components';
import { JobTemplate } from '@types'; import { JobTemplate } from '@types';
import { import {
InventoryLookup, InventoryLookup,
@@ -37,17 +40,6 @@ import { JobTemplatesAPI, ProjectsAPI } from '@api';
import LabelSelect from './LabelSelect'; import LabelSelect from './LabelSelect';
import PlaybookSelect from './PlaybookSelect'; 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 { class JobTemplateForm extends Component {
static propTypes = { static propTypes = {
template: JobTemplate, template: JobTemplate,
@@ -211,9 +203,10 @@ class JobTemplateForm extends Component {
} }
const AdvancedFieldsWrapper = template.isNew ? CollapsibleSection : 'div'; const AdvancedFieldsWrapper = template.isNew ? CollapsibleSection : 'div';
return ( return (
<Form autoComplete="off" onSubmit={handleSubmit}> <Form autoComplete="off" onSubmit={handleSubmit}>
<FormRow> <FormColumnLayout>
<FormField <FormField
id="template-name" id="template-name"
name="name" name="name"
@@ -334,266 +327,265 @@ class JobTemplateForm extends Component {
); );
}} }}
</Field> </Field>
</FormRow> <FormFullWidthLayout>
<FormRow> <Field name="labels">
<Field name="labels">
{({ field }) => (
<FormGroup label={i18n._(t`Labels`)} fieldId="template-labels">
<FieldTooltip
content={i18n._(t`Optional labels that describe this job template,
such as 'dev' or 'test'. Labels can be used to group and filter
job templates and completed jobs.`)}
/>
<LabelSelect
value={field.value}
onChange={labels => setFieldValue('labels', labels)}
onError={this.setContentError}
/>
</FormGroup>
)}
</Field>
</FormRow>
<FormRow>
<Field name="credentials" fieldId="template-credentials">
{({ field }) => (
<MultiCredentialsLookup
value={field.value}
onChange={newCredentials =>
setFieldValue('credentials', newCredentials)
}
onError={this.setContentError}
tooltip={i18n._(
t`Select credentials that allow Tower to access the nodes this job will be ran against. You can only select one credential of each type. For machine credentials (SSH), checking "Prompt on launch" without selecting credentials will require you to select a machine credential at run time. If you select credentials and check "Prompt on launch", the selected credential(s) become the defaults that can be updated at run time.`
)}
/>
)}
</Field>
</FormRow>
<AdvancedFieldsWrapper label="Advanced">
<FormRow>
<FormField
id="template-forks"
name="forks"
type="number"
min="0"
label={i18n._(t`Forks`)}
tooltip={
<span>
{i18n._(t`The number of parallel or simultaneous
processes to use while executing the playbook. An empty value,
or a value less than 1 will use the Ansible default which is
usually 5. The default number of forks can be overwritten
with a change to`)}{' '}
<code>ansible.cfg</code>.{' '}
{i18n._(t`Refer to the Ansible documentation for details
about the configuration file.`)}
</span>
}
/>
<FormField
id="template-limit"
name="limit"
type="text"
label={i18n._(t`Limit`)}
tooltip={i18n._(t`Provide a host pattern to further constrain
the list of hosts that will be managed or affected by the
playbook. Multiple patterns are allowed. Refer to Ansible
documentation for more information and examples on patterns.`)}
/>
<Field name="verbosity">
{({ field }) => ( {({ field }) => (
<FormGroup <FormGroup label={i18n._(t`Labels`)} fieldId="template-labels">
fieldId="template-verbosity"
label={i18n._(t`Verbosity`)}
>
<FieldTooltip <FieldTooltip
content={i18n._(t`Control the level of output ansible will content={i18n._(t`Optional labels that describe this job template,
produce as the playbook executes.`)} such as 'dev' or 'test'. Labels can be used to group and filter
job templates and completed jobs.`)}
/> />
<AnsibleSelect <LabelSelect
id="template-verbosity" value={field.value}
data={verbosityOptions} onChange={labels => setFieldValue('labels', labels)}
{...field} onError={this.setContentError}
/> />
</FormGroup> </FormGroup>
)} )}
</Field> </Field>
<FormField <Field name="credentials" fieldId="template-credentials">
id="template-job-slicing" {({ field }) => (
name="job_slice_count" <MultiCredentialsLookup
type="number" value={field.value}
min="1" onChange={newCredentials =>
label={i18n._(t`Job Slicing`)} setFieldValue('credentials', newCredentials)
tooltip={i18n._(t`Divide the work done by this job template }
into the specified number of job slices, each running the onError={this.setContentError}
same tasks against a portion of the inventory.`)} 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.`
<FormField )}
id="template-timeout" />
name="timeout" )}
type="number" </Field>
min="0" <AdvancedFieldsWrapper label="Advanced">
label={i18n._(t`Timeout`)} <FormColumnLayout>
tooltip={i18n._(t`The amount of time (in seconds) to run <FormField
before the task is canceled. Defaults to 0 for no job id="template-forks"
timeout.`)} name="forks"
/> type="number"
<Field name="diff_mode"> min="0"
{({ field, form }) => ( label={i18n._(t`Forks`)}
<FormGroup tooltip={
fieldId="template-show-changes" <span>
label={i18n._(t`Show Changes`)} {i18n._(t`The number of parallel or simultaneous
> processes to use while executing the playbook. An empty value,
<FieldTooltip or a value less than 1 will use the Ansible default which is
content={i18n._(t`If enabled, show the changes made by usually 5. The default number of forks can be overwritten
Ansible tasks, where supported. This is equivalent with a change to`)}{' '}
to Ansible&#x2019s --diff mode.`)} <code>ansible.cfg</code>.{' '}
/> {i18n._(t`Refer to the Ansible documentation for details
<div> about the configuration file.`)}
<Switch </span>
id="template-show-changes" }
label={field.value ? i18n._(t`On`) : i18n._(t`Off`)} />
isChecked={field.value} <FormField
onChange={checked => id="template-limit"
form.setFieldValue(field.name, checked) name="limit"
} type="text"
label={i18n._(t`Limit`)}
tooltip={i18n._(t`Provide a host pattern to further constrain
the list of hosts that will be managed or affected by the
playbook. Multiple patterns are allowed. Refer to Ansible
documentation for more information and examples on patterns.`)}
/>
<Field name="verbosity">
{({ field }) => (
<FormGroup
fieldId="template-verbosity"
label={i18n._(t`Verbosity`)}
>
<FieldTooltip
content={i18n._(t`Control the level of output ansible will
produce as the playbook executes.`)}
/>
<AnsibleSelect
id="template-verbosity"
data={verbosityOptions}
{...field}
/>
</FormGroup>
)}
</Field>
<FormField
id="template-job-slicing"
name="job_slice_count"
type="number"
min="1"
label={i18n._(t`Job Slicing`)}
tooltip={i18n._(t`Divide the work done by this job template
into the specified number of job slices, each running the
same tasks against a portion of the inventory.`)}
/>
<FormField
id="template-timeout"
name="timeout"
type="number"
min="0"
label={i18n._(t`Timeout`)}
tooltip={i18n._(t`The amount of time (in seconds) to run
before the task is canceled. Defaults to 0 for no job
timeout.`)}
/>
<Field name="diff_mode">
{({ field, form }) => (
<FormGroup
fieldId="template-show-changes"
label={i18n._(t`Show Changes`)}
>
<FieldTooltip
content={i18n._(t`If enabled, show the changes made by
Ansible tasks, where supported. This is equivalent
to Ansible&#x2019s --diff mode.`)}
/>
<div>
<Switch
id="template-show-changes"
label={field.value ? i18n._(t`On`) : i18n._(t`Off`)}
isChecked={field.value}
onChange={checked =>
form.setFieldValue(field.name, checked)
}
/>
</div>
</FormGroup>
)}
</Field>
<FormFullWidthLayout>
<Field name="instanceGroups">
{({ field, form }) => (
<InstanceGroupsLookup
value={field.value}
onChange={value =>
form.setFieldValue(field.name, value)
}
tooltip={i18n._(t`Select the Instance Groups for this Organization
to run on.`)}
/>
)}
</Field>
<Field name="job_tags">
{({ field, form }) => (
<FormGroup
label={i18n._(t`Job Tags`)}
fieldId="template-job-tags"
>
<FieldTooltip
content={i18n._(t`Tags are useful when you have a large
playbook, and you want to run a specific part of a
play or task. Use commas to separate multiple tags.
Refer to Ansible Tower documentation for details on
the usage of tags.`)}
/>
<TagMultiSelect
value={field.value}
onChange={value =>
form.setFieldValue(field.name, value)
}
/>
</FormGroup>
)}
</Field>
<Field name="skip_tags">
{({ field, form }) => (
<FormGroup
label={i18n._(t`Skip Tags`)}
fieldId="template-skip-tags"
>
<FieldTooltip
content={i18n._(t`Skip tags are useful when you have a
large playbook, and you want to skip specific parts of a
play or task. Use commas to separate multiple tags. Refer
to Ansible Tower documentation for details on the usage
of tags.`)}
/>
<TagMultiSelect
value={field.value}
onChange={value =>
form.setFieldValue(field.name, value)
}
/>
</FormGroup>
)}
</Field>
<FormGroup
fieldId="template-option-checkboxes"
isInline
label={i18n._(t`Options`)}
>
<FormCheckboxLayout>
<CheckboxField
id="option-privilege-escalation"
name="become_enabled"
label={i18n._(t`Privilege Escalation`)}
tooltip={i18n._(t`If enabled, run this playbook as an
administrator.`)}
/>
<Checkbox
aria-label={i18n._(t`Provisioning Callbacks`)}
label={
<span>
{i18n._(t`Provisioning Callbacks`)}
&nbsp;
<FieldTooltip
content={i18n._(t`Enables creation of a provisioning
callback URL. Using the URL a host can contact BRAND_NAME
and request a configuration update using this job
template.`)}
/>
</span>
}
id="option-callbacks"
isChecked={allowCallbacks}
onChange={checked => {
this.setState({ allowCallbacks: checked });
}}
/>
<CheckboxField
id="option-concurrent"
name="allow_simultaneous"
label={i18n._(t`Concurrent Jobs`)}
tooltip={i18n._(t`If enabled, simultaneous runs of this job
template will be allowed.`)}
/>
<CheckboxField
id="option-fact-cache"
name="use_fact_cache"
label={i18n._(t`Fact Cache`)}
tooltip={i18n._(t`If enabled, use cached facts if available
and store discovered facts in the cache.`)}
/>
</FormCheckboxLayout>
</FormGroup>
</FormFullWidthLayout>
{allowCallbacks && (
<>
{callbackUrl && (
<FormGroup
label={i18n._(t`Provisioning Callback URL`)}
fieldId="template-callback-url"
>
<TextInput
id="template-callback-url"
isDisabled
value={callbackUrl}
/>
</FormGroup>
)}
<FormField
id="template-host-config-key"
name="host_config_key"
label={i18n._(t`Host Config Key`)}
validate={allowCallbacks ? required(null, i18n) : null}
/> />
</div> </>
</FormGroup> )}
)} </FormColumnLayout>
</Field> </AdvancedFieldsWrapper>
</FormRow> </FormFullWidthLayout>
<Field name="instanceGroups"> <FormSubmitError error={submitError} />
{({ field, form }) => ( <FormActionGroup onCancel={handleCancel} onSubmit={handleSubmit} />
<InstanceGroupsLookup </FormColumnLayout>
css="margin-top: 20px"
value={field.value}
onChange={value => form.setFieldValue(field.name, value)}
tooltip={i18n._(t`Select the Instance Groups for this Organization
to run on.`)}
/>
)}
</Field>
<Field name="job_tags">
{({ field, form }) => (
<FormGroup
label={i18n._(t`Job Tags`)}
css="margin-top: 20px"
fieldId="template-job-tags"
>
<FieldTooltip
content={i18n._(t`Tags are useful when you have a large
playbook, and you want to run a specific part of a
play or task. Use commas to separate multiple tags.
Refer to Ansible Tower documentation for details on
the usage of tags.`)}
/>
<TagMultiSelect
value={field.value}
onChange={value => form.setFieldValue(field.name, value)}
/>
</FormGroup>
)}
</Field>
<Field name="skip_tags">
{({ field, form }) => (
<FormGroup
label={i18n._(t`Skip Tags`)}
css="margin-top: 20px"
fieldId="template-skip-tags"
>
<FieldTooltip
content={i18n._(t`Skip tags are useful when you have a
large playbook, and you want to skip specific parts of a
play or task. Use commas to separate multiple tags. Refer
to Ansible Tower documentation for details on the usage
of tags.`)}
/>
<TagMultiSelect
value={field.value}
onChange={value => form.setFieldValue(field.name, value)}
/>
</FormGroup>
)}
</Field>
<GridFormGroup
fieldId="template-option-checkboxes"
isInline
label={i18n._(t`Options`)}
css="margin-top: 20px"
>
<CheckboxField
id="option-privilege-escalation"
name="become_enabled"
label={i18n._(t`Privilege Escalation`)}
tooltip={i18n._(t`If enabled, run this playbook as an
administrator.`)}
/>
<Checkbox
aria-label={i18n._(t`Provisioning Callbacks`)}
label={
<span>
{i18n._(t`Provisioning Callbacks`)}
&nbsp;
<FieldTooltip
content={i18n._(t`Enables creation of a provisioning
callback URL. Using the URL a host can contact BRAND_NAME
and request a configuration update using this job
template.`)}
/>
</span>
}
id="option-callbacks"
isChecked={allowCallbacks}
onChange={checked => {
this.setState({ allowCallbacks: checked });
}}
/>
<CheckboxField
id="option-concurrent"
name="allow_simultaneous"
label={i18n._(t`Concurrent Jobs`)}
tooltip={i18n._(t`If enabled, simultaneous runs of this job
template will be allowed.`)}
/>
<CheckboxField
id="option-fact-cache"
name="use_fact_cache"
label={i18n._(t`Fact Cache`)}
tooltip={i18n._(t`If enabled, use cached facts if available
and store discovered facts in the cache.`)}
/>
</GridFormGroup>
<div
css={`
${allowCallbacks ? '' : 'display: none'}
margin-top: 20px;
`}
>
<FormRow>
{callbackUrl && (
<FormGroup
label={i18n._(t`Provisioning Callback URL`)}
fieldId="template-callback-url"
>
<TextInput
id="template-callback-url"
isDisabled
value={callbackUrl}
/>
</FormGroup>
)}
<FormField
id="template-host-config-key"
name="host_config_key"
label={i18n._(t`Host Config Key`)}
validate={allowCallbacks ? required(null, i18n) : null}
/>
</FormRow>
</div>
</AdvancedFieldsWrapper>
<FormSubmitError error={submitError} />
<FormActionGroup onCancel={handleCancel} onSubmit={handleSubmit} />
</Form> </Form>
); );
} }

View File

@@ -10,9 +10,9 @@ import FormField, {
PasswordField, PasswordField,
FormSubmitError, FormSubmitError,
} from '@components/FormField'; } from '@components/FormField';
import FormRow from '@components/FormRow';
import OrganizationLookup from '@components/Lookup/OrganizationLookup'; import OrganizationLookup from '@components/Lookup/OrganizationLookup';
import { required, requiredEmail } from '@util/validators'; import { required, requiredEmail } from '@util/validators';
import { FormColumnLayout } from '@components/FormLayout';
function UserFormFields({ user, i18n }) { function UserFormFields({ user, i18n }) {
const [organization, setOrganization] = useState(null); const [organization, setOrganization] = useState(null);
@@ -41,7 +41,7 @@ function UserFormFields({ user, i18n }) {
const organizationFieldArr = useField({ const organizationFieldArr = useField({
name: 'organization', name: 'organization',
validate: required(i18n._(t`Select a value for this field`), i18n), validate: required(i18n._(t`Select a value for this field`), i18n),
}); });
const organizationMeta = organizationFieldArr[1]; const organizationMeta = organizationFieldArr[1];
const organizationHelpers = organizationFieldArr[2]; const organizationHelpers = organizationFieldArr[2];
@@ -99,6 +99,7 @@ function UserFormFields({ user, i18n }) {
type="text" type="text"
/> />
{!user.id && ( {!user.id && (
<<<<<<< HEAD
<OrganizationLookup <OrganizationLookup
helperTextInvalid={organizationMeta.error} helperTextInvalid={organizationMeta.error}
isValid={!organizationMeta.touched || !organizationMeta.error} isValid={!organizationMeta.touched || !organizationMeta.error}
@@ -125,8 +126,36 @@ function UserFormFields({ user, i18n }) {
{...userTypeField} {...userTypeField}
/> />
</FormGroup> </FormGroup>
=======
<OrganizationLookup
helperTextInvalid={organizationMeta.error}
isValid={!organizationMeta.touched || !organizationMeta.error}
onBlur={() => organizationHelpers.setTouched()}
onChange={value => {
organizationHelpers.setValue(value.id);
setOrganization(value);
}}
value={organization}
required
/>
)}
<FormGroup
fieldId="user-type"
helperTextInvalid={userTypeMeta.error}
isRequired
isValid={!userTypeMeta.touched || !userTypeMeta.error}
label={i18n._(t`User Type`)}
>
<AnsibleSelect
isValid={!userTypeMeta.touched || !userTypeMeta.error}
id="user-type"
data={userTypeOptions}
{...userTypeField}
/>
</FormGroup>
>>>>>>> update forms from FormRow to using FormLayout components
</> </>
); );
} }
function UserForm({ user, handleCancel, handleSubmit, submitError, i18n }) { function UserForm({ user, handleCancel, handleSubmit, submitError, i18n }) {
@@ -173,12 +202,14 @@ function UserForm({ user, handleCancel, handleSubmit, submitError, i18n }) {
> >
{formik => ( {formik => (
<Form autoComplete="off" onSubmit={formik.handleSubmit}> <Form autoComplete="off" onSubmit={formik.handleSubmit}>
<FormColumnLayout>
<UserFormFields user={user} i18n={i18n} /> <UserFormFields user={user} i18n={i18n} />
<FormSubmitError error={submitError} /> <FormSubmitError error={submitError} />
<FormActionGroup <FormActionGroup
onCancel={handleCancel} onCancel={handleCancel}
onSubmit={formik.handleSubmit} onSubmit={formik.handleSubmit}
/> />
</FormColumnLayout>
</Form> </Form>
)} )}
</Formik> </Formik>