mirror of
https://github.com/ansible/awx.git
synced 2026-02-28 16:28:43 -03:30
update forms to useField fomik hook
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { string, bool } from 'prop-types';
|
import { string, bool } from 'prop-types';
|
||||||
import { Field, useFormikContext } 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 CodeMirrorInput from './CodeMirrorInput';
|
import CodeMirrorInput from './CodeMirrorInput';
|
||||||
@@ -8,9 +8,8 @@ import YamlJsonToggle from './YamlJsonToggle';
|
|||||||
import { JSON_MODE, YAML_MODE } from './constants';
|
import { JSON_MODE, YAML_MODE } from './constants';
|
||||||
|
|
||||||
function VariablesField({ id, name, label, readOnly }) {
|
function VariablesField({ id, name, label, readOnly }) {
|
||||||
const { values } = useFormikContext();
|
const [field, meta, helpers] = useField(name);
|
||||||
const value = values[name];
|
const [mode, setMode] = useState(isJson(field.value) ? JSON_MODE : YAML_MODE);
|
||||||
const [mode, setMode] = useState(isJson(value) ? JSON_MODE : YAML_MODE);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Field name={name}>
|
<Field name={name}>
|
||||||
@@ -31,10 +30,10 @@ function VariablesField({ id, name, label, readOnly }) {
|
|||||||
newMode === YAML_MODE
|
newMode === YAML_MODE
|
||||||
? jsonToYaml(field.value)
|
? jsonToYaml(field.value)
|
||||||
: yamlToJson(field.value);
|
: yamlToJson(field.value);
|
||||||
form.setFieldValue(name, newVal);
|
helpers.setValue(newVal);
|
||||||
setMode(newMode);
|
setMode(newMode);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
form.setFieldError(name, err.message);
|
helpers.setError(err.message);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -45,16 +44,13 @@ function VariablesField({ id, name, label, readOnly }) {
|
|||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
{...field}
|
{...field}
|
||||||
onChange={newVal => {
|
onChange={newVal => {
|
||||||
form.setFieldValue(name, newVal);
|
helpers.setValue(newVal);
|
||||||
}}
|
}}
|
||||||
hasErrors={!!form.errors[field.name]}
|
hasErrors={!!meta.error}
|
||||||
/>
|
/>
|
||||||
{form.errors[field.name] ? (
|
{meta.error ? (
|
||||||
<div
|
<div className="pf-c-form__helper-text pf-m-error" aria-live="polite">
|
||||||
className="pf-c-form__helper-text pf-m-error"
|
{meta.error}
|
||||||
aria-live="polite"
|
|
||||||
>
|
|
||||||
{form.errors[field.name]}
|
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { string, func } from 'prop-types';
|
import { string, func } from 'prop-types';
|
||||||
import { Field } from 'formik';
|
import { useField } from 'formik';
|
||||||
import { Checkbox, Tooltip } from '@patternfly/react-core';
|
import { Checkbox, Tooltip } from '@patternfly/react-core';
|
||||||
import { QuestionCircleIcon as PFQuestionCircleIcon } from '@patternfly/react-icons';
|
import { QuestionCircleIcon as PFQuestionCircleIcon } from '@patternfly/react-icons';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
@@ -10,32 +10,29 @@ const QuestionCircleIcon = styled(PFQuestionCircleIcon)`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
function CheckboxField({ id, name, label, tooltip, validate, ...rest }) {
|
function CheckboxField({ id, name, label, tooltip, validate, ...rest }) {
|
||||||
|
const [field] = useField({ name, validate });
|
||||||
return (
|
return (
|
||||||
<Field name={name} validate={validate}>
|
<Checkbox
|
||||||
{({ field }) => (
|
aria-label={label}
|
||||||
<Checkbox
|
label={
|
||||||
aria-label={label}
|
<span>
|
||||||
label={
|
{label}
|
||||||
<span>
|
|
||||||
{label}
|
{tooltip && (
|
||||||
|
<Tooltip position="right" content={tooltip}>
|
||||||
{tooltip && (
|
<QuestionCircleIcon />
|
||||||
<Tooltip position="right" content={tooltip}>
|
</Tooltip>
|
||||||
<QuestionCircleIcon />
|
)}
|
||||||
</Tooltip>
|
</span>
|
||||||
)}
|
}
|
||||||
</span>
|
id={id}
|
||||||
}
|
{...rest}
|
||||||
id={id}
|
isChecked={field.value}
|
||||||
{...rest}
|
{...field}
|
||||||
isChecked={field.value}
|
onChange={(value, event) => {
|
||||||
{...field}
|
field.onChange(event);
|
||||||
onChange={(value, event) => {
|
}}
|
||||||
field.onChange(event);
|
/>
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
CheckboxField.propTypes = {
|
CheckboxField.propTypes = {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Field } from 'formik';
|
import { useField } from 'formik';
|
||||||
import { FormGroup, TextInput, Tooltip } from '@patternfly/react-core';
|
|
||||||
import { QuestionCircleIcon as PFQuestionCircleIcon } from '@patternfly/react-icons';
|
import { QuestionCircleIcon as PFQuestionCircleIcon } from '@patternfly/react-icons';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
@@ -21,16 +20,13 @@ function FormField(props) {
|
|||||||
...rest
|
...rest
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
const [field, meta] = useField({ name, validate });
|
||||||
<Field name={name} validate={validate}>
|
const isValid = !(meta.touched && meta.error);
|
||||||
{({ field, form }) => {
|
|
||||||
const isValid =
|
|
||||||
form && (!form.touched[field.name] || !form.errors[field.name]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormGroup
|
<FormGroup
|
||||||
fieldId={id}
|
fieldId={id}
|
||||||
helperTextInvalid={form.errors[field.name]}
|
helperTextInvalid={meta.error}
|
||||||
isRequired={isRequired}
|
isRequired={isRequired}
|
||||||
isValid={isValid}
|
isValid={isValid}
|
||||||
label={label}
|
label={label}
|
||||||
@@ -44,6 +40,8 @@ function FormField(props) {
|
|||||||
<QuestionCircleIcon />
|
<QuestionCircleIcon />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
isValid={isValid}
|
||||||
|
helperTextInvalid={meta.error}
|
||||||
<TextInput
|
<TextInput
|
||||||
id={id}
|
id={id}
|
||||||
isRequired={isRequired}
|
isRequired={isRequired}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useState } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Field } from 'formik';
|
import { useField } from 'formik';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
ButtonVariant,
|
ButtonVariant,
|
||||||
@@ -16,54 +16,46 @@ import { EyeIcon, EyeSlashIcon } from '@patternfly/react-icons';
|
|||||||
function PasswordField(props) {
|
function PasswordField(props) {
|
||||||
const { id, name, label, validate, isRequired, i18n } = props;
|
const { id, name, label, validate, isRequired, i18n } = props;
|
||||||
const [inputType, setInputType] = useState('password');
|
const [inputType, setInputType] = useState('password');
|
||||||
|
const [field, meta] = useField({ name, validate });
|
||||||
|
const isValid = !(meta.touched && meta.error);
|
||||||
|
|
||||||
const handlePasswordToggle = () => {
|
const handlePasswordToggle = () => {
|
||||||
setInputType(inputType === 'text' ? 'password' : 'text');
|
setInputType(inputType === 'text' ? 'password' : 'text');
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Field name={name} validate={validate}>
|
<FormGroup
|
||||||
{({ field, form }) => {
|
fieldId={id}
|
||||||
const isValid =
|
helperTextInvalid={meta.error}
|
||||||
form && (!form.touched[field.name] || !form.errors[field.name]);
|
isRequired={isRequired}
|
||||||
return (
|
isValid={isValid}
|
||||||
<FormGroup
|
label={label}
|
||||||
fieldId={id}
|
>
|
||||||
helperTextInvalid={form.errors[field.name]}
|
<InputGroup>
|
||||||
isRequired={isRequired}
|
<Tooltip
|
||||||
isValid={isValid}
|
content={inputType === 'password' ? i18n._(t`Show`) : i18n._(t`Hide`)}
|
||||||
label={label}
|
>
|
||||||
|
<Button
|
||||||
|
variant={ButtonVariant.control}
|
||||||
|
aria-label={i18n._(t`Toggle Password`)}
|
||||||
|
onClick={handlePasswordToggle}
|
||||||
>
|
>
|
||||||
<InputGroup>
|
{inputType === 'password' && <EyeSlashIcon />}
|
||||||
<Tooltip
|
{inputType === 'text' && <EyeIcon />}
|
||||||
content={
|
</Button>
|
||||||
inputType === 'password' ? i18n._(t`Show`) : i18n._(t`Hide`)
|
</Tooltip>
|
||||||
}
|
<TextInput
|
||||||
>
|
id={id}
|
||||||
<Button
|
isRequired={isRequired}
|
||||||
variant={ButtonVariant.control}
|
isValid={isValid}
|
||||||
aria-label={i18n._(t`Toggle Password`)}
|
type={inputType}
|
||||||
onClick={handlePasswordToggle}
|
{...field}
|
||||||
>
|
onChange={(value, event) => {
|
||||||
{inputType === 'password' && <EyeSlashIcon />}
|
field.onChange(event);
|
||||||
{inputType === 'text' && <EyeIcon />}
|
}}
|
||||||
</Button>
|
/>
|
||||||
</Tooltip>
|
</InputGroup>
|
||||||
<TextInput
|
</FormGroup>
|
||||||
id={id}
|
|
||||||
isRequired={isRequired}
|
|
||||||
isValid={isValid}
|
|
||||||
type={inputType}
|
|
||||||
{...field}
|
|
||||||
onChange={(value, event) => {
|
|
||||||
field.onChange(event);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</InputGroup>
|
|
||||||
</FormGroup>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Field>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useState } from 'react';
|
|||||||
import { func, shape } from 'prop-types';
|
import { func, shape } from 'prop-types';
|
||||||
|
|
||||||
import { useRouteMatch } from 'react-router-dom';
|
import { useRouteMatch } from 'react-router-dom';
|
||||||
import { Formik, Field } from 'formik';
|
import { Formik, useField } from 'formik';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
|
|
||||||
@@ -15,26 +15,21 @@ 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';
|
||||||
|
|
||||||
function HostForm({ handleSubmit, handleCancel, host, submitError, i18n }) {
|
function HostFormFields({ host, i18n }) {
|
||||||
const [inventory, setInventory] = useState(
|
const [inventory, setInventory] = useState(
|
||||||
host ? host.summary_fields.inventory : ''
|
host ? host.summary_fields.inventory : ''
|
||||||
);
|
);
|
||||||
|
|
||||||
const hostAddMatch = useRouteMatch('/hosts/add');
|
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 (
|
return (
|
||||||
<Formik
|
<>
|
||||||
initialValues={{
|
|
||||||
name: host.name,
|
|
||||||
description: host.description,
|
|
||||||
inventory: host.inventory || '',
|
|
||||||
variables: host.variables,
|
|
||||||
}}
|
|
||||||
onSubmit={handleSubmit}
|
|
||||||
>
|
|
||||||
{formik => (
|
|
||||||
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
|
|
||||||
<FormRow>
|
|
||||||
<FormField
|
<FormField
|
||||||
id="host-name"
|
id="host-name"
|
||||||
name="name"
|
name="name"
|
||||||
@@ -50,41 +45,46 @@ function HostForm({ handleSubmit, handleCancel, host, submitError, i18n }) {
|
|||||||
label={i18n._(t`Description`)}
|
label={i18n._(t`Description`)}
|
||||||
/>
|
/>
|
||||||
{hostAddMatch && (
|
{hostAddMatch && (
|
||||||
<Field
|
|
||||||
name="inventory"
|
|
||||||
validate={required(
|
|
||||||
i18n._(t`Select a value for this field`),
|
|
||||||
i18n
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{({ form }) => (
|
|
||||||
<InventoryLookup
|
<InventoryLookup
|
||||||
value={inventory}
|
value={inventory}
|
||||||
onBlur={() => form.setFieldTouched('inventory')}
|
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={!form.touched.inventory || !form.errors.inventory}
|
isValid={!inventoryMeta.touched || !inventoryMeta.error}
|
||||||
helperTextInvalid={form.errors.inventory}
|
helperTextInvalid={inventoryMeta.error}
|
||||||
onChange={value => {
|
onChange={value => {
|
||||||
form.setFieldValue('inventory', value.id);
|
inventoryHelpers.setValuealue(value.id);
|
||||||
setInventory(value);
|
setInventory(value);
|
||||||
}}
|
}}
|
||||||
required
|
required
|
||||||
touched={form.touched.inventory}
|
touched={inventoryMeta.touched}
|
||||||
error={form.errors.inventory}
|
error={inventoryMeta.error}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Field>
|
|
||||||
)}
|
|
||||||
</FormRow>
|
|
||||||
<FormRow>
|
|
||||||
<VariablesField
|
<VariablesField
|
||||||
id="host-variables"
|
id="host-variables"
|
||||||
name="variables"
|
name="variables"
|
||||||
label={i18n._(t`Variables`)}
|
label={i18n._(t`Variables`)}
|
||||||
/>
|
/>
|
||||||
</FormRow>
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function HostForm({ handleSubmit, host, submitError, handleCancel, ...rest }) {
|
||||||
|
return (
|
||||||
|
<Formik
|
||||||
|
initialValues={{
|
||||||
|
name: host.name,
|
||||||
|
description: host.description,
|
||||||
|
inventory: host.inventory || '',
|
||||||
|
variables: host.variables,
|
||||||
|
}}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
>
|
||||||
|
{formik => (
|
||||||
|
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
|
||||||
|
<HostFormFields host={host} {...rest} />
|
||||||
<FormSubmitError error={submitError} />
|
<FormSubmitError error={submitError} />
|
||||||
<FormActionGroup
|
<FormActionGroup
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Formik, Field } from 'formik';
|
import { Formik, useField } from 'formik';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { func, number, shape } from 'prop-types';
|
import { func, number, shape } from 'prop-types';
|
||||||
@@ -8,44 +8,25 @@ import { VariablesField } from '@components/CodeMirrorInput';
|
|||||||
import { Form } from '@patternfly/react-core';
|
import { Form } from '@patternfly/react-core';
|
||||||
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 FormRow from '@components/FormRow';
|
|
||||||
import { required } from '@util/validators';
|
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';
|
||||||
|
|
||||||
function InventoryForm({
|
function InventoryFormFields({ i18n, credentialTypeId }) {
|
||||||
inventory = {},
|
const [organizationField, organizationMeta, organizationHelpers] = useField({
|
||||||
i18n,
|
name: 'organization',
|
||||||
onCancel,
|
validate: required(i18n._(t`Select a value for this field`), i18n),
|
||||||
onSubmit,
|
});
|
||||||
instanceGroups,
|
const instanceGroupsFieldArr = useField('instanceGroups');
|
||||||
credentialTypeId,
|
const instanceGroupsField = instanceGroupsFieldArr[0];
|
||||||
submitError,
|
const instanceGroupsHelpers = instanceGroupsFieldArr[2];
|
||||||
}) {
|
|
||||||
const initialValues = {
|
const insightsCredentialFieldArr = useField('insights_credential');
|
||||||
name: inventory.name || '',
|
const insightsCredentialField = insightsCredentialFieldArr[0];
|
||||||
description: inventory.description || '',
|
const insightsCredentialHelpers = insightsCredentialFieldArr[2];
|
||||||
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 (
|
return (
|
||||||
<Formik
|
<>
|
||||||
initialValues={initialValues}
|
|
||||||
onSubmit={values => {
|
|
||||||
onSubmit(values);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{formik => (
|
|
||||||
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
|
|
||||||
<FormRow>
|
|
||||||
<FormField
|
<FormField
|
||||||
id="inventory-name"
|
id="inventory-name"
|
||||||
label={i18n._(t`Name`)}
|
label={i18n._(t`Name`)}
|
||||||
@@ -60,66 +41,30 @@ function InventoryForm({
|
|||||||
name="description"
|
name="description"
|
||||||
type="text"
|
type="text"
|
||||||
/>
|
/>
|
||||||
<Field
|
|
||||||
id="inventory-organization"
|
|
||||||
label={i18n._(t`Organization`)}
|
|
||||||
name="organization"
|
|
||||||
validate={required(
|
|
||||||
i18n._(t`Select a value for this field`),
|
|
||||||
i18n
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{({ form, field }) => (
|
|
||||||
<OrganizationLookup
|
<OrganizationLookup
|
||||||
helperTextInvalid={form.errors.organization}
|
helperTextInvalid={organizationMeta.error}
|
||||||
isValid={
|
isValid={!organizationMeta.touched || !organizationMeta.error}
|
||||||
!form.touched.organization || !form.errors.organization
|
onBlur={() => organizationHelpers.setTouched()}
|
||||||
}
|
|
||||||
onBlur={() => form.setFieldTouched('organization')}
|
|
||||||
onChange={value => {
|
onChange={value => {
|
||||||
form.setFieldValue('organization', value);
|
organizationHelpers.setValue(value);
|
||||||
}}
|
}}
|
||||||
value={field.value}
|
value={organizationField.value}
|
||||||
touched={form.touched.organization}
|
touched={organizationMeta.touched}
|
||||||
error={form.errors.organization}
|
error={organizationMeta.error}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
<Field
|
|
||||||
id="inventory-insights_credential"
|
|
||||||
label={i18n._(t`Insights Credential`)}
|
|
||||||
name="insights_credential"
|
|
||||||
>
|
|
||||||
{({ field, form }) => (
|
|
||||||
<CredentialLookup
|
<CredentialLookup
|
||||||
label={i18n._(t`Insights Credential`)}
|
label={i18n._(t`Insights Credential`)}
|
||||||
credentialTypeId={credentialTypeId}
|
credentialTypeId={credentialTypeId}
|
||||||
onChange={value =>
|
onChange={value => insightsCredentialHelpers.setValue(value)}
|
||||||
form.setFieldValue('insights_credential', value)
|
value={insightsCredentialField.value}
|
||||||
}
|
|
||||||
value={field.value}
|
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
</FormRow>
|
|
||||||
<FormRow>
|
|
||||||
<Field
|
|
||||||
id="inventory-instanceGroups"
|
|
||||||
label={i18n._(t`Instance Groups`)}
|
|
||||||
name="instanceGroups"
|
|
||||||
>
|
|
||||||
{({ field, form }) => (
|
|
||||||
<InstanceGroupsLookup
|
<InstanceGroupsLookup
|
||||||
value={field.value}
|
value={instanceGroupsField.value}
|
||||||
onChange={value => {
|
onChange={value => {
|
||||||
form.setFieldValue('instanceGroups', value);
|
instanceGroupsHelpers.setValue(value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
</FormRow>
|
|
||||||
<FormRow>
|
|
||||||
<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`
|
||||||
@@ -128,8 +73,42 @@ function InventoryForm({
|
|||||||
name="variables"
|
name="variables"
|
||||||
label={i18n._(t`Variables`)}
|
label={i18n._(t`Variables`)}
|
||||||
/>
|
/>
|
||||||
</FormRow>
|
</>
|
||||||
<FormRow>
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Formik
|
||||||
|
initialValues={initialValues}
|
||||||
|
onSubmit={values => {
|
||||||
|
onSubmit(values);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{formik => (
|
||||||
|
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
|
||||||
|
<InventoryFormFields {...rest} />
|
||||||
<FormSubmitError error={submitError} />
|
<FormSubmitError error={submitError} />
|
||||||
<FormActionGroup
|
<FormActionGroup
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { withRouter } from 'react-router-dom';
|
import { withRouter } from 'react-router-dom';
|
||||||
import { Formik, Field } from 'formik';
|
import { Formik, useField } from 'formik';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Form, FormGroup } from '@patternfly/react-core';
|
import { Form, FormGroup } from '@patternfly/react-core';
|
||||||
@@ -18,20 +18,86 @@ 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';
|
||||||
|
|
||||||
function OrganizationForm({
|
function OrganizationFormFields({
|
||||||
organization,
|
|
||||||
i18n,
|
i18n,
|
||||||
me,
|
me,
|
||||||
onCancel,
|
instanceGroups,
|
||||||
onSubmit,
|
setInstanceGroups,
|
||||||
submitError,
|
|
||||||
}) {
|
}) {
|
||||||
|
const [venvField] = useField('custom_virtualenv');
|
||||||
|
|
||||||
const defaultVenv = {
|
const defaultVenv = {
|
||||||
label: i18n._(t`Use Default Ansible Environment`),
|
label: i18n._(t`Use Default Ansible Environment`),
|
||||||
value: '/venv/ansible/',
|
value: '/venv/ansible/',
|
||||||
key: 'default',
|
key: 'default',
|
||||||
};
|
};
|
||||||
const { custom_virtualenvs } = useContext(ConfigContext);
|
const { custom_virtualenvs } = useContext(ConfigContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FormField
|
||||||
|
id="org-name"
|
||||||
|
name="name"
|
||||||
|
type="text"
|
||||||
|
label={i18n._(t`Name`)}
|
||||||
|
validate={required(null, i18n)}
|
||||||
|
isRequired
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
id="org-description"
|
||||||
|
name="description"
|
||||||
|
type="text"
|
||||||
|
label={i18n._(t`Description`)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
id="org-max_hosts"
|
||||||
|
name="max_hosts"
|
||||||
|
type="number"
|
||||||
|
label={i18n._(t`Max Hosts`)}
|
||||||
|
tooltip={i18n._(
|
||||||
|
t`The maximum number of hosts allowed to be managed by this organization.
|
||||||
|
Value defaults to 0 which means no limit. Refer to the Ansible
|
||||||
|
documentation for more details.`
|
||||||
|
)}
|
||||||
|
validate={minMaxValue(0, Number.MAX_SAFE_INTEGER, i18n)}
|
||||||
|
me={me || {}}
|
||||||
|
isDisabled={!me.is_superuser}
|
||||||
|
/>
|
||||||
|
{custom_virtualenvs && custom_virtualenvs.length > 1 && (
|
||||||
|
<FormGroup
|
||||||
|
fieldId="org-custom-virtualenv"
|
||||||
|
label={i18n._(t`Ansible Environment`)}
|
||||||
|
>
|
||||||
|
<AnsibleSelect
|
||||||
|
id="org-custom-virtualenv"
|
||||||
|
data={[
|
||||||
|
defaultVenv,
|
||||||
|
...custom_virtualenvs
|
||||||
|
.filter(value => value !== defaultVenv.value)
|
||||||
|
.map(value => ({ value, label: value, key: value })),
|
||||||
|
]}
|
||||||
|
{...venvField}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
<InstanceGroupsLookup
|
||||||
|
value={instanceGroups}
|
||||||
|
onChange={setInstanceGroups}
|
||||||
|
tooltip={i18n._(
|
||||||
|
t`Select the Instance Groups for this Organization to run on.`
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function OrganizationForm({
|
||||||
|
organization,
|
||||||
|
onCancel,
|
||||||
|
onSubmit,
|
||||||
|
submitError,
|
||||||
|
...rest
|
||||||
|
}) {
|
||||||
const [contentError, setContentError] = useState(null);
|
const [contentError, setContentError] = useState(null);
|
||||||
const [hasContentLoading, setHasContentLoading] = useState(true);
|
const [hasContentLoading, setHasContentLoading] = useState(true);
|
||||||
const [initialInstanceGroups, setInitialInstanceGroups] = useState([]);
|
const [initialInstanceGroups, setInitialInstanceGroups] = useState([]);
|
||||||
@@ -100,64 +166,11 @@ function OrganizationForm({
|
|||||||
>
|
>
|
||||||
{formik => (
|
{formik => (
|
||||||
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
|
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
|
||||||
<FormRow>
|
<OrganizationFormFields
|
||||||
<FormField
|
instanceGroups={instanceGroups}
|
||||||
id="org-name"
|
setInstanceGroups={setInstanceGroups}
|
||||||
name="name"
|
{...rest}
|
||||||
type="text"
|
|
||||||
label={i18n._(t`Name`)}
|
|
||||||
validate={required(null, i18n)}
|
|
||||||
isRequired
|
|
||||||
/>
|
/>
|
||||||
<FormField
|
|
||||||
id="org-description"
|
|
||||||
name="description"
|
|
||||||
type="text"
|
|
||||||
label={i18n._(t`Description`)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
id="org-max_hosts"
|
|
||||||
name="max_hosts"
|
|
||||||
type="number"
|
|
||||||
label={i18n._(t`Max Hosts`)}
|
|
||||||
tooltip={i18n._(
|
|
||||||
t`The maximum number of hosts allowed to be managed by this organization.
|
|
||||||
Value defaults to 0 which means no limit. Refer to the Ansible
|
|
||||||
documentation for more details.`
|
|
||||||
)}
|
|
||||||
validate={minMaxValue(0, Number.MAX_SAFE_INTEGER, i18n)}
|
|
||||||
me={me || {}}
|
|
||||||
isDisabled={!me.is_superuser}
|
|
||||||
/>
|
|
||||||
{custom_virtualenvs && custom_virtualenvs.length > 1 && (
|
|
||||||
<Field name="custom_virtualenv">
|
|
||||||
{({ field }) => (
|
|
||||||
<FormGroup
|
|
||||||
fieldId="org-custom-virtualenv"
|
|
||||||
label={i18n._(t`Ansible Environment`)}
|
|
||||||
>
|
|
||||||
<AnsibleSelect
|
|
||||||
id="org-custom-virtualenv"
|
|
||||||
data={[
|
|
||||||
defaultVenv,
|
|
||||||
...custom_virtualenvs
|
|
||||||
.filter(value => value !== defaultVenv.value)
|
|
||||||
.map(value => ({ value, label: value, key: value })),
|
|
||||||
]}
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
)}
|
|
||||||
</FormRow>
|
|
||||||
<InstanceGroupsLookup
|
|
||||||
value={instanceGroups}
|
|
||||||
onChange={setInstanceGroups}
|
|
||||||
tooltip={i18n._(
|
|
||||||
t`Select the Instance Groups for this Organization to run on.`
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormSubmitError error={submitError} />
|
<FormSubmitError error={submitError} />
|
||||||
<FormActionGroup
|
<FormActionGroup
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
|
|||||||
@@ -121,6 +121,11 @@ describe('<OrganizationForm />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('changing inputs and saving triggers expected callback', async () => {
|
test('changing inputs and saving triggers expected callback', async () => {
|
||||||
|
OrganizationsAPI.readInstanceGroups.mockReturnValue({
|
||||||
|
data: {
|
||||||
|
results: mockInstanceGroups,
|
||||||
|
},
|
||||||
|
});
|
||||||
let wrapper;
|
let wrapper;
|
||||||
const onSubmit = jest.fn();
|
const onSubmit = jest.fn();
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Formik, Field } from 'formik';
|
import { Formik, useField } from 'formik';
|
||||||
import { Config } from '@contexts/Config';
|
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 AnsibleSelect from '@components/AnsibleSelect';
|
||||||
import ContentError from '@components/ContentError';
|
import ContentError from '@components/ContentError';
|
||||||
import ContentLoading from '@components/ContentLoading';
|
import ContentLoading from '@components/ContentLoading';
|
||||||
@@ -24,17 +24,9 @@ import {
|
|||||||
HgSubForm,
|
HgSubForm,
|
||||||
SvnSubForm,
|
SvnSubForm,
|
||||||
InsightsSubForm,
|
InsightsSubForm,
|
||||||
SubFormTitle,
|
|
||||||
ManualSubForm,
|
ManualSubForm,
|
||||||
} from './ProjectSubForms';
|
} from './ProjectSubForms';
|
||||||
|
|
||||||
const ScmTypeFormRow = styled(FormRow)`
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
margin: 0 -24px;
|
|
||||||
padding: 24px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const fetchCredentials = async credential => {
|
const fetchCredentials = async credential => {
|
||||||
const [
|
const [
|
||||||
{
|
{
|
||||||
@@ -73,14 +65,238 @@ const fetchCredentials = async credential => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
function ProjectForm({ project, submitError, ...props }) {
|
function ProjectFormFields({
|
||||||
const { i18n, handleCancel, handleSubmit } = props;
|
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 (
|
||||||
|
<>
|
||||||
|
<FormField
|
||||||
|
id="project-name"
|
||||||
|
label={i18n._(t`Name`)}
|
||||||
|
name="name"
|
||||||
|
type="text"
|
||||||
|
validate={required(null, i18n)}
|
||||||
|
isRequired
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
id="project-description"
|
||||||
|
label={i18n._(t`Description`)}
|
||||||
|
name="description"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
<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="project-scm-type"
|
||||||
|
helperTextInvalid={scmTypeMeta.error}
|
||||||
|
isRequired
|
||||||
|
isValid={!scmTypeMeta.touched || !scmTypeMeta.error}
|
||||||
|
label={i18n._(t`SCM Type`)}
|
||||||
|
>
|
||||||
|
<AnsibleSelect
|
||||||
|
{...scmTypeField}
|
||||||
|
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);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
{formik.values.scm_type !== '' && (
|
||||||
|
<ScmTypeFormRow>
|
||||||
|
<SubFormTitle size="md">
|
||||||
|
{i18n._(t`Type Details`)}
|
||||||
|
</SubFormTitle>
|
||||||
|
{
|
||||||
|
{
|
||||||
|
manual: (
|
||||||
|
<ManualSubForm
|
||||||
|
localPath={formik.initialValues.local_path}
|
||||||
|
project_base_dir={project_base_dir}
|
||||||
|
project_local_paths={project_local_paths}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
git: (
|
||||||
|
<GitSubForm
|
||||||
|
credential={credentials.scm}
|
||||||
|
onCredentialSelection={handleCredentialSelection}
|
||||||
|
scmUpdateOnLaunch={formik.values.scm_update_on_launch}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
hg: (
|
||||||
|
<HgSubForm
|
||||||
|
credential={credentials.scm}
|
||||||
|
onCredentialSelection={handleCredentialSelection}
|
||||||
|
scmUpdateOnLaunch={formik.values.scm_update_on_launch}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
svn: (
|
||||||
|
<SvnSubForm
|
||||||
|
credential={credentials.scm}
|
||||||
|
onCredentialSelection={handleCredentialSelection}
|
||||||
|
scmUpdateOnLaunch={formik.values.scm_update_on_launch}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
insights: (
|
||||||
|
<InsightsSubForm
|
||||||
|
credential={credentials.insights}
|
||||||
|
onCredentialSelection={handleCredentialSelection}
|
||||||
|
scmUpdateOnLaunch={formik.values.scm_update_on_launch}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}[formik.values.scm_type]
|
||||||
|
}
|
||||||
|
</ScmTypeFormRow>
|
||||||
|
)}
|
||||||
|
<Config>
|
||||||
|
{({ custom_virtualenvs }) =>
|
||||||
|
custom_virtualenvs &&
|
||||||
|
custom_virtualenvs.length > 1 && (
|
||||||
|
<Field name="custom_virtualenv">
|
||||||
|
{({ field }) => (
|
||||||
|
<FormGroup
|
||||||
|
fieldId="project-custom-virtualenv"
|
||||||
|
label={i18n._(t`Ansible Environment`)}
|
||||||
|
>
|
||||||
|
<FieldTooltip
|
||||||
|
content={i18n._(t`Select the playbook to be executed by
|
||||||
|
this job.`)}
|
||||||
|
/>
|
||||||
|
<AnsibleSelect
|
||||||
|
id="project-custom-virtualenv"
|
||||||
|
data={[
|
||||||
|
{
|
||||||
|
label: i18n._(t`Use Default Ansible Environment`),
|
||||||
|
value: '/venv/ansible/',
|
||||||
|
key: 'default',
|
||||||
|
},
|
||||||
|
...custom_virtualenvs
|
||||||
|
.filter(datum => datum !== '/venv/ansible/')
|
||||||
|
.map(datum => ({
|
||||||
|
label: datum,
|
||||||
|
value: datum,
|
||||||
|
key: datum,
|
||||||
|
})),
|
||||||
|
]}
|
||||||
|
{...venvField}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</Config>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ProjectForm({ i18n, project, submitError, ...props }) {
|
||||||
|
const { handleCancel, handleSubmit } = props;
|
||||||
const { summary_fields = {} } = project;
|
const { summary_fields = {} } = project;
|
||||||
const [contentError, setContentError] = useState(null);
|
const [contentError, setContentError] = useState(null);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [organization, setOrganization] = useState(
|
const [organization, setOrganization] = useState(null);
|
||||||
summary_fields.organization || null
|
|
||||||
);
|
|
||||||
const [scmSubFormState, setScmSubFormState] = useState(null);
|
const [scmSubFormState, setScmSubFormState] = useState(null);
|
||||||
const [scmTypeOptions, setScmTypeOptions] = useState(null);
|
const [scmTypeOptions, setScmTypeOptions] = useState(null);
|
||||||
const [credentials, setCredentials] = useState({
|
const [credentials, setCredentials] = useState({
|
||||||
@@ -114,60 +330,6 @@ function ProjectForm({ project, submitError, ...props }) {
|
|||||||
fetchData();
|
fetchData();
|
||||||
}, [summary_fields.credential]);
|
}, [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) {
|
if (isLoading) {
|
||||||
return <ContentLoading />;
|
return <ContentLoading />;
|
||||||
}
|
}
|
||||||
@@ -211,183 +373,19 @@ function ProjectForm({ project, submitError, ...props }) {
|
|||||||
onSubmit={formik.handleSubmit}
|
onSubmit={formik.handleSubmit}
|
||||||
css="padding: 0 24px"
|
css="padding: 0 24px"
|
||||||
>
|
>
|
||||||
<FormRow>
|
<ProjectFormFields
|
||||||
<FormField
|
project_base_dir={project_base_dir}
|
||||||
id="project-name"
|
project_local_paths={project_local_paths}
|
||||||
label={i18n._(t`Name`)}
|
formik={formik}
|
||||||
name="name"
|
i18n={i18n}
|
||||||
type="text"
|
setCredentials={setCredentials}
|
||||||
validate={required(null, i18n)}
|
credentials={credentials}
|
||||||
isRequired
|
scmTypeOptions={scmTypeOptions}
|
||||||
|
setScmSubFormState={setScmSubFormState}
|
||||||
|
scmSubFormState={scmSubFormState}
|
||||||
|
setOrganization={setOrganization}
|
||||||
|
organization={organization}
|
||||||
/>
|
/>
|
||||||
<FormField
|
|
||||||
id="project-description"
|
|
||||||
label={i18n._(t`Description`)}
|
|
||||||
name="description"
|
|
||||||
type="text"
|
|
||||||
/>
|
|
||||||
<Field
|
|
||||||
name="organization"
|
|
||||||
validate={required(
|
|
||||||
i18n._(t`Select a value for this field`),
|
|
||||||
i18n
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{({ form }) => (
|
|
||||||
<OrganizationLookup
|
|
||||||
helperTextInvalid={form.errors.organization}
|
|
||||||
isValid={
|
|
||||||
!form.touched.organization || !form.errors.organization
|
|
||||||
}
|
|
||||||
onBlur={() => form.setFieldTouched('organization')}
|
|
||||||
onChange={value => {
|
|
||||||
form.setFieldValue('organization', value.id);
|
|
||||||
setOrganization(value);
|
|
||||||
}}
|
|
||||||
value={organization}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
<Field
|
|
||||||
name="scm_type"
|
|
||||||
validate={required(
|
|
||||||
i18n._(t`Select a value for this field`),
|
|
||||||
i18n
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{({ field, form }) => (
|
|
||||||
<FormGroup
|
|
||||||
fieldId="project-scm-type"
|
|
||||||
helperTextInvalid={form.errors.scm_type}
|
|
||||||
isRequired
|
|
||||||
isValid={!form.touched.scm_type || !form.errors.scm_type}
|
|
||||||
label={i18n._(t`SCM Type`)}
|
|
||||||
>
|
|
||||||
<AnsibleSelect
|
|
||||||
{...field}
|
|
||||||
id="scm_type"
|
|
||||||
data={[
|
|
||||||
{
|
|
||||||
value: '',
|
|
||||||
key: '',
|
|
||||||
label: i18n._(t`Choose an SCM Type`),
|
|
||||||
isDisabled: true,
|
|
||||||
},
|
|
||||||
...scmTypeOptions.map(([value, label]) => {
|
|
||||||
if (label === 'Manual') {
|
|
||||||
value = 'manual';
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
label,
|
|
||||||
value,
|
|
||||||
key: value,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
]}
|
|
||||||
onChange={(event, value) => {
|
|
||||||
form.setFieldValue('scm_type', value);
|
|
||||||
resetScmTypeFields(value, form);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
{formik.values.scm_type !== '' && (
|
|
||||||
<ScmTypeFormRow>
|
|
||||||
<SubFormTitle size="md">
|
|
||||||
{i18n._(t`Type Details`)}
|
|
||||||
</SubFormTitle>
|
|
||||||
{
|
|
||||||
{
|
|
||||||
manual: (
|
|
||||||
<ManualSubForm
|
|
||||||
localPath={formik.initialValues.local_path}
|
|
||||||
project_base_dir={project_base_dir}
|
|
||||||
project_local_paths={project_local_paths}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
git: (
|
|
||||||
<GitSubForm
|
|
||||||
credential={credentials.scm}
|
|
||||||
onCredentialSelection={handleCredentialSelection}
|
|
||||||
scmUpdateOnLaunch={
|
|
||||||
formik.values.scm_update_on_launch
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
hg: (
|
|
||||||
<HgSubForm
|
|
||||||
credential={credentials.scm}
|
|
||||||
onCredentialSelection={handleCredentialSelection}
|
|
||||||
scmUpdateOnLaunch={
|
|
||||||
formik.values.scm_update_on_launch
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
svn: (
|
|
||||||
<SvnSubForm
|
|
||||||
credential={credentials.scm}
|
|
||||||
onCredentialSelection={handleCredentialSelection}
|
|
||||||
scmUpdateOnLaunch={
|
|
||||||
formik.values.scm_update_on_launch
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
insights: (
|
|
||||||
<InsightsSubForm
|
|
||||||
credential={credentials.insights}
|
|
||||||
onCredentialSelection={handleCredentialSelection}
|
|
||||||
scmUpdateOnLaunch={
|
|
||||||
formik.values.scm_update_on_launch
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
}[formik.values.scm_type]
|
|
||||||
}
|
|
||||||
</ScmTypeFormRow>
|
|
||||||
)}
|
|
||||||
<Config>
|
|
||||||
{({ custom_virtualenvs }) =>
|
|
||||||
custom_virtualenvs &&
|
|
||||||
custom_virtualenvs.length > 1 && (
|
|
||||||
<Field name="custom_virtualenv">
|
|
||||||
{({ field }) => (
|
|
||||||
<FormGroup
|
|
||||||
fieldId="project-custom-virtualenv"
|
|
||||||
label={i18n._(t`Ansible Environment`)}
|
|
||||||
>
|
|
||||||
<FieldTooltip
|
|
||||||
content={i18n._(t`Select the playbook to be executed by
|
|
||||||
this job.`)}
|
|
||||||
/>
|
|
||||||
<AnsibleSelect
|
|
||||||
id="project-custom-virtualenv"
|
|
||||||
data={[
|
|
||||||
{
|
|
||||||
label: i18n._(
|
|
||||||
t`Use Default Ansible Environment`
|
|
||||||
),
|
|
||||||
value: '/venv/ansible/',
|
|
||||||
key: 'default',
|
|
||||||
},
|
|
||||||
...custom_virtualenvs
|
|
||||||
.filter(datum => datum !== '/venv/ansible/')
|
|
||||||
.map(datum => ({
|
|
||||||
label: datum,
|
|
||||||
value: datum,
|
|
||||||
key: datum,
|
|
||||||
})),
|
|
||||||
]}
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</Config>
|
|
||||||
</FormRow>
|
|
||||||
<FormSubmitError error={submitError} />
|
<FormSubmitError error={submitError} />
|
||||||
<FormActionGroup
|
<FormActionGroup
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Field } from 'formik';
|
import { useField } from 'formik';
|
||||||
import CredentialLookup from '@components/Lookup/CredentialLookup';
|
import CredentialLookup from '@components/Lookup/CredentialLookup';
|
||||||
import { required } from '@util/validators';
|
import { required } from '@util/validators';
|
||||||
import { ScmTypeOptions } from './SharedFields';
|
import { ScmTypeOptions } from './SharedFields';
|
||||||
@@ -11,30 +11,32 @@ const InsightsSubForm = ({
|
|||||||
credential,
|
credential,
|
||||||
onCredentialSelection,
|
onCredentialSelection,
|
||||||
scmUpdateOnLaunch,
|
scmUpdateOnLaunch,
|
||||||
}) => (
|
}) => {
|
||||||
<>
|
const credFieldArr = useField({
|
||||||
<Field
|
name: 'credential',
|
||||||
name="credential"
|
validate: required(i18n._(t`Select a value for this field`), i18n),
|
||||||
validate={required(i18n._(t`Select a value for this field`), i18n)}
|
});
|
||||||
>
|
const credMeta = credFieldArr[1];
|
||||||
{({ form }) => (
|
const credHelpers = credFieldArr[2];
|
||||||
<CredentialLookup
|
|
||||||
credentialTypeId={credential.typeId}
|
return (
|
||||||
label={i18n._(t`Insights Credential`)}
|
<>
|
||||||
helperTextInvalid={form.errors.credential}
|
<CredentialLookup
|
||||||
isValid={!form.touched.credential || !form.errors.credential}
|
credentialTypeId={credential.typeId}
|
||||||
onBlur={() => form.setFieldTouched('credential')}
|
label={i18n._(t`Insights Credential`)}
|
||||||
onChange={value => {
|
helperTextInvalid={credMeta.error}
|
||||||
onCredentialSelection('insights', value);
|
isValid={!credMeta.touched || !credMeta.error}
|
||||||
form.setFieldValue('credential', value.id);
|
onBlur={() => credHelpers.setTouched()}
|
||||||
}}
|
onChange={value => {
|
||||||
value={credential.value}
|
onCredentialSelection('insights', value);
|
||||||
required
|
credHelpers.setValue(value.id);
|
||||||
/>
|
}}
|
||||||
)}
|
value={credential.value}
|
||||||
</Field>
|
required
|
||||||
<ScmTypeOptions hideAllowOverride scmUpdateOnLaunch={scmUpdateOnLaunch} />
|
/>
|
||||||
</>
|
<ScmTypeOptions hideAllowOverride scmUpdateOnLaunch={scmUpdateOnLaunch} />
|
||||||
);
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default withI18n()(InsightsSubForm);
|
export default withI18n()(InsightsSubForm);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Field } from 'formik';
|
import { useField } from 'formik';
|
||||||
import { required } from '@util/validators';
|
import { required } from '@util/validators';
|
||||||
import AnsibleSelect from '@components/AnsibleSelect';
|
import AnsibleSelect from '@components/AnsibleSelect';
|
||||||
import FormField, { FieldTooltip } from '@components/FormField';
|
import FormField, { FieldTooltip } from '@components/FormField';
|
||||||
@@ -34,6 +34,10 @@ const ManualSubForm = ({
|
|||||||
label: path,
|
label: path,
|
||||||
})),
|
})),
|
||||||
];
|
];
|
||||||
|
const [pathField, pathMeta, pathHelpers] = useField({
|
||||||
|
name: 'local_path',
|
||||||
|
validate: required(i18n._(t`Select a value for this field`), i18n),
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -72,35 +76,27 @@ const ManualSubForm = ({
|
|||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{options.length !== 1 && (
|
<FormGroup
|
||||||
<Field
|
fieldId="project-local-path"
|
||||||
name="local_path"
|
helperTextInvalid={pathMeta.error}
|
||||||
validate={required(i18n._(t`Select a value for this field`), i18n)}
|
isRequired
|
||||||
>
|
isValid={!pathMeta.touched || !pathMeta.error}
|
||||||
{({ field, form }) => (
|
label={i18n._(t`Playbook Directory`)}
|
||||||
<FormGroup
|
>
|
||||||
fieldId="project-local-path"
|
<FieldTooltip
|
||||||
helperTextInvalid={form.errors.local_path}
|
content={i18n._(t`Select from the list of directories found in
|
||||||
isRequired
|
the Project Base Path. Together the base path and the playbook
|
||||||
isValid={!form.touched.local_path || !form.errors.local_path}
|
directory provide the full path used to locate playbooks.`)}
|
||||||
label={i18n._(t`Playbook Directory`)}
|
/>
|
||||||
>
|
<AnsibleSelect
|
||||||
<FieldTooltip
|
{...pathField}
|
||||||
content={i18n._(t`Select from the list of directories found in
|
id="local_path"
|
||||||
the Project Base Path. Together the base path and the playbook
|
data={options}
|
||||||
directory provide the full path used to locate playbooks.`)}
|
onChange={(event, value) => {
|
||||||
/>
|
pathHelpers.setValue(value);
|
||||||
<AnsibleSelect
|
}}
|
||||||
{...field}
|
/>
|
||||||
id="local_path"
|
</FormGroup>
|
||||||
data={options}
|
|
||||||
onChange={(event, value) => {
|
|
||||||
form.setFieldValue('local_path', value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Field } from 'formik';
|
import { useField } from 'formik';
|
||||||
import CredentialLookup from '@components/Lookup/CredentialLookup';
|
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 FormRow from '@components/FormRow';
|
|
||||||
import { FormGroup, Title } from '@patternfly/react-core';
|
import { FormGroup, Title } from '@patternfly/react-core';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
@@ -41,21 +40,21 @@ export const BranchFormField = withI18n()(({ i18n, label }) => (
|
|||||||
));
|
));
|
||||||
|
|
||||||
export const ScmCredentialFormField = withI18n()(
|
export const ScmCredentialFormField = withI18n()(
|
||||||
({ i18n, credential, onCredentialSelection }) => (
|
({ i18n, credential, onCredentialSelection }) => {
|
||||||
<Field name="credential">
|
const credHelpers = useField('credential')[2];
|
||||||
{({ form }) => (
|
|
||||||
|
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);
|
||||||
form.setFieldValue('credential', value ? value.id : '');
|
credHelpers.setValue(value ? value.id : '');
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
);
|
||||||
</Field>
|
}
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export const ScmTypeOptions = withI18n()(
|
export const ScmTypeOptions = withI18n()(
|
||||||
|
|||||||
@@ -2,19 +2,58 @@ import React, { useState } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Formik, Field } from 'formik';
|
import { Formik, useField } from 'formik';
|
||||||
import { Form } from '@patternfly/react-core';
|
import { Form } from '@patternfly/react-core';
|
||||||
import FormActionGroup from '@components/FormActionGroup/FormActionGroup';
|
import FormActionGroup from '@components/FormActionGroup/FormActionGroup';
|
||||||
import FormField, { FormSubmitError } from '@components/FormField';
|
import FormField, { FormSubmitError } from '@components/FormField';
|
||||||
import FormRow from '@components/FormRow';
|
|
||||||
import OrganizationLookup from '@components/Lookup/OrganizationLookup';
|
import OrganizationLookup from '@components/Lookup/OrganizationLookup';
|
||||||
import { required } from '@util/validators';
|
import { required } from '@util/validators';
|
||||||
|
|
||||||
function TeamForm(props) {
|
function TeamFormFields(props) {
|
||||||
const { team, handleCancel, handleSubmit, submitError, i18n } = props;
|
const { team, i18n } = props;
|
||||||
const [organization, setOrganization] = useState(
|
const [organization, setOrganization] = useState(
|
||||||
team.summary_fields ? team.summary_fields.organization : null
|
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 (
|
||||||
|
<>
|
||||||
|
<FormField
|
||||||
|
id="team-name"
|
||||||
|
label={i18n._(t`Name`)}
|
||||||
|
name="name"
|
||||||
|
type="text"
|
||||||
|
validate={required(null, i18n)}
|
||||||
|
isRequired
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
id="team-description"
|
||||||
|
label={i18n._(t`Description`)}
|
||||||
|
name="description"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
<OrganizationLookup
|
||||||
|
helperTextInvalid={orgMeta.error}
|
||||||
|
isValid={!orgMeta.touched || !orgMeta.error}
|
||||||
|
onBlur={() => 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 (
|
return (
|
||||||
<Formik
|
<Formik
|
||||||
@@ -31,45 +70,7 @@ function TeamForm(props) {
|
|||||||
onSubmit={formik.handleSubmit}
|
onSubmit={formik.handleSubmit}
|
||||||
css="padding: 0 24px"
|
css="padding: 0 24px"
|
||||||
>
|
>
|
||||||
<FormRow>
|
<TeamFormFields team={team} {...rest} />
|
||||||
<FormField
|
|
||||||
id="team-name"
|
|
||||||
label={i18n._(t`Name`)}
|
|
||||||
name="name"
|
|
||||||
type="text"
|
|
||||||
validate={required(null, i18n)}
|
|
||||||
isRequired
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
id="team-description"
|
|
||||||
label={i18n._(t`Description`)}
|
|
||||||
name="description"
|
|
||||||
type="text"
|
|
||||||
/>
|
|
||||||
<Field
|
|
||||||
name="organization"
|
|
||||||
validate={required(
|
|
||||||
i18n._(t`Select a value for this field`),
|
|
||||||
i18n
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{({ form }) => (
|
|
||||||
<OrganizationLookup
|
|
||||||
helperTextInvalid={form.errors.organization}
|
|
||||||
isValid={
|
|
||||||
!form.touched.organization || !form.errors.organization
|
|
||||||
}
|
|
||||||
onBlur={() => form.setFieldTouched('organization')}
|
|
||||||
onChange={value => {
|
|
||||||
form.setFieldValue('organization', value.id);
|
|
||||||
setOrganization(value);
|
|
||||||
}}
|
|
||||||
value={organization}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
</FormRow>
|
|
||||||
<FormSubmitError error={submitError} />
|
<FormSubmitError error={submitError} />
|
||||||
<FormActionGroup
|
<FormActionGroup
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useState } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Formik, Field } from 'formik';
|
import { Formik, useField } from 'formik';
|
||||||
import { Form, FormGroup } from '@patternfly/react-core';
|
import { Form, FormGroup } from '@patternfly/react-core';
|
||||||
import AnsibleSelect from '@components/AnsibleSelect';
|
import AnsibleSelect from '@components/AnsibleSelect';
|
||||||
import FormActionGroup from '@components/FormActionGroup/FormActionGroup';
|
import FormActionGroup from '@components/FormActionGroup/FormActionGroup';
|
||||||
@@ -14,7 +14,7 @@ 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';
|
||||||
|
|
||||||
function UserForm({ user, handleCancel, handleSubmit, submitError, i18n }) {
|
function UserFormFields({ user, i18n }) {
|
||||||
const [organization, setOrganization] = useState(null);
|
const [organization, setOrganization] = useState(null);
|
||||||
|
|
||||||
const userTypeOptions = [
|
const userTypeOptions = [
|
||||||
@@ -38,6 +38,98 @@ function UserForm({ user, handleCancel, handleSubmit, submitError, 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];
|
||||||
|
|
||||||
|
const [userTypeField, userTypeMeta] = useField('user_type');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FormField
|
||||||
|
id="user-username"
|
||||||
|
label={i18n._(t`Username`)}
|
||||||
|
name="username"
|
||||||
|
type="text"
|
||||||
|
validate={required(null, i18n)}
|
||||||
|
isRequired
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
id="user-email"
|
||||||
|
label={i18n._(t`Email`)}
|
||||||
|
name="email"
|
||||||
|
validate={requiredEmail(i18n)}
|
||||||
|
isRequired
|
||||||
|
/>
|
||||||
|
<PasswordField
|
||||||
|
id="user-password"
|
||||||
|
label={i18n._(t`Password`)}
|
||||||
|
name="password"
|
||||||
|
validate={
|
||||||
|
!user.id
|
||||||
|
? required(i18n._(t`This field must not be blank`), i18n)
|
||||||
|
: () => undefined
|
||||||
|
}
|
||||||
|
isRequired={!user.id}
|
||||||
|
/>
|
||||||
|
<PasswordField
|
||||||
|
id="user-confirm-password"
|
||||||
|
label={i18n._(t`Confirm Password`)}
|
||||||
|
name="confirm_password"
|
||||||
|
validate={
|
||||||
|
!user.id
|
||||||
|
? required(i18n._(t`This field must not be blank`), i18n)
|
||||||
|
: () => undefined
|
||||||
|
}
|
||||||
|
isRequired={!user.id}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
id="user-first-name"
|
||||||
|
label={i18n._(t`First Name`)}
|
||||||
|
name="first_name"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
id="user-last-name"
|
||||||
|
label={i18n._(t`Last Name`)}
|
||||||
|
name="last_name"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
{!user.id && (
|
||||||
|
<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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function UserForm({ user, handleCancel, handleSubmit, submitError, i18n }) {
|
||||||
const handleValidateAndSubmit = (values, { setErrors }) => {
|
const handleValidateAndSubmit = (values, { setErrors }) => {
|
||||||
if (values.password !== values.confirm_password) {
|
if (values.password !== values.confirm_password) {
|
||||||
setErrors({
|
setErrors({
|
||||||
@@ -81,106 +173,7 @@ function UserForm({ user, handleCancel, handleSubmit, submitError, i18n }) {
|
|||||||
>
|
>
|
||||||
{formik => (
|
{formik => (
|
||||||
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
|
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
|
||||||
<FormRow>
|
<UserFormFields user={user} i18n={i18n} />
|
||||||
<FormField
|
|
||||||
id="user-username"
|
|
||||||
label={i18n._(t`Username`)}
|
|
||||||
name="username"
|
|
||||||
type="text"
|
|
||||||
validate={required(null, i18n)}
|
|
||||||
isRequired
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
id="user-email"
|
|
||||||
label={i18n._(t`Email`)}
|
|
||||||
name="email"
|
|
||||||
validate={requiredEmail(i18n)}
|
|
||||||
isRequired
|
|
||||||
/>
|
|
||||||
<PasswordField
|
|
||||||
id="user-password"
|
|
||||||
label={i18n._(t`Password`)}
|
|
||||||
name="password"
|
|
||||||
validate={
|
|
||||||
!user.id
|
|
||||||
? required(i18n._(t`This field must not be blank`), i18n)
|
|
||||||
: () => undefined
|
|
||||||
}
|
|
||||||
isRequired={!user.id}
|
|
||||||
/>
|
|
||||||
<PasswordField
|
|
||||||
id="user-confirm-password"
|
|
||||||
label={i18n._(t`Confirm Password`)}
|
|
||||||
name="confirm_password"
|
|
||||||
validate={
|
|
||||||
!user.id
|
|
||||||
? required(i18n._(t`This field must not be blank`), i18n)
|
|
||||||
: () => undefined
|
|
||||||
}
|
|
||||||
isRequired={!user.id}
|
|
||||||
/>
|
|
||||||
</FormRow>
|
|
||||||
<FormRow>
|
|
||||||
<FormField
|
|
||||||
id="user-first-name"
|
|
||||||
label={i18n._(t`First Name`)}
|
|
||||||
name="first_name"
|
|
||||||
type="text"
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
id="user-last-name"
|
|
||||||
label={i18n._(t`Last Name`)}
|
|
||||||
name="last_name"
|
|
||||||
type="text"
|
|
||||||
/>
|
|
||||||
{!user.id && (
|
|
||||||
<Field
|
|
||||||
name="organization"
|
|
||||||
validate={required(
|
|
||||||
i18n._(t`Select a value for this field`),
|
|
||||||
i18n
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{({ form }) => (
|
|
||||||
<OrganizationLookup
|
|
||||||
helperTextInvalid={form.errors.organization}
|
|
||||||
isValid={
|
|
||||||
!form.touched.organization || !form.errors.organization
|
|
||||||
}
|
|
||||||
onBlur={() => form.setFieldTouched('organization')}
|
|
||||||
onChange={value => {
|
|
||||||
form.setFieldValue('organization', value.id);
|
|
||||||
setOrganization(value);
|
|
||||||
}}
|
|
||||||
value={organization}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
)}
|
|
||||||
<Field name="user_type">
|
|
||||||
{({ form, field }) => {
|
|
||||||
const isValid =
|
|
||||||
!form.touched.user_type || !form.errors.user_type;
|
|
||||||
return (
|
|
||||||
<FormGroup
|
|
||||||
fieldId="user-type"
|
|
||||||
helperTextInvalid={form.errors.user_type}
|
|
||||||
isRequired
|
|
||||||
isValid={isValid}
|
|
||||||
label={i18n._(t`User Type`)}
|
|
||||||
>
|
|
||||||
<AnsibleSelect
|
|
||||||
isValid={isValid}
|
|
||||||
id="user-type"
|
|
||||||
data={userTypeOptions}
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Field>
|
|
||||||
</FormRow>
|
|
||||||
<FormSubmitError error={submitError} />
|
<FormSubmitError error={submitError} />
|
||||||
<FormActionGroup
|
<FormActionGroup
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
|
|||||||
Reference in New Issue
Block a user