mirror of
https://github.com/ansible/awx.git
synced 2024-10-27 00:55:06 +03:00
Merge pull request #8104 from mabashian/4254-auto-pop-lookup
Auto populate various required lookups on various forms Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
commit
31cd36b768
@ -10,6 +10,7 @@ import { getQSConfig, parseQueryString, mergeParams } from '../../util/qs';
|
||||
import { FieldTooltip } from '../FormField';
|
||||
import Lookup from './Lookup';
|
||||
import OptionsList from '../OptionsList';
|
||||
import useAutoPopulateLookup from '../../util/useAutoPopulateLookup';
|
||||
import useRequest from '../../util/useRequest';
|
||||
import LookupErrorMessage from './shared/LookupErrorMessage';
|
||||
|
||||
@ -34,7 +35,9 @@ function CredentialLookup({
|
||||
i18n,
|
||||
tooltip,
|
||||
isDisabled,
|
||||
autoPopulate,
|
||||
}) {
|
||||
const autoPopulateLookup = useAutoPopulateLookup(onChange);
|
||||
const {
|
||||
result: { count, credentials, relatedSearchableKeys, searchableKeys },
|
||||
error,
|
||||
@ -62,6 +65,11 @@ function CredentialLookup({
|
||||
),
|
||||
CredentialsAPI.readOptions,
|
||||
]);
|
||||
|
||||
if (autoPopulate) {
|
||||
autoPopulateLookup(data.results);
|
||||
}
|
||||
|
||||
return {
|
||||
count: data.count,
|
||||
credentials: data.results,
|
||||
@ -73,6 +81,8 @@ function CredentialLookup({
|
||||
).filter(key => actionsResponse.data?.actions?.GET[key]?.filterable),
|
||||
};
|
||||
}, [
|
||||
autoPopulate,
|
||||
autoPopulateLookup,
|
||||
credentialTypeId,
|
||||
credentialTypeKind,
|
||||
credentialTypeNamespace,
|
||||
@ -182,6 +192,8 @@ CredentialLookup.propTypes = {
|
||||
onChange: func.isRequired,
|
||||
required: bool,
|
||||
value: Credential,
|
||||
isDisabled: bool,
|
||||
autoPopulate: bool,
|
||||
};
|
||||
|
||||
CredentialLookup.defaultProps = {
|
||||
@ -192,6 +204,8 @@ CredentialLookup.defaultProps = {
|
||||
onBlur: () => {},
|
||||
required: false,
|
||||
value: null,
|
||||
isDisabled: false,
|
||||
autoPopulate: false,
|
||||
};
|
||||
|
||||
export { CredentialLookup as _CredentialLookup };
|
||||
|
@ -88,4 +88,66 @@ describe('CredentialLookup', () => {
|
||||
expect(_CredentialLookup.defaultProps.onBlur).toBeInstanceOf(Function);
|
||||
expect(_CredentialLookup.defaultProps.onBlur).not.toThrow();
|
||||
});
|
||||
|
||||
test('should auto-select credential when only one available and autoPopulate prop is true', async () => {
|
||||
CredentialsAPI.read.mockReturnValue({
|
||||
data: {
|
||||
results: [{ id: 1 }],
|
||||
count: 1,
|
||||
},
|
||||
});
|
||||
const onChange = jest.fn();
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<CredentialLookup
|
||||
autoPopulate
|
||||
credentialTypeId={1}
|
||||
label="Foo"
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
});
|
||||
expect(onChange).toHaveBeenCalledWith({ id: 1 });
|
||||
});
|
||||
|
||||
test('should not auto-select credential when autoPopulate prop is false', async () => {
|
||||
CredentialsAPI.read.mockReturnValue({
|
||||
data: {
|
||||
results: [{ id: 1 }],
|
||||
count: 1,
|
||||
},
|
||||
});
|
||||
const onChange = jest.fn();
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<CredentialLookup
|
||||
credentialTypeId={1}
|
||||
label="Foo"
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
});
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should not auto-select credential when multiple available', async () => {
|
||||
CredentialsAPI.read.mockReturnValue({
|
||||
data: {
|
||||
results: [{ id: 1 }, { id: 2 }],
|
||||
count: 2,
|
||||
},
|
||||
});
|
||||
const onChange = jest.fn();
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<CredentialLookup
|
||||
credentialTypeId={1}
|
||||
label="Foo"
|
||||
autoPopulate
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
});
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
@ -8,6 +8,7 @@ import { OrganizationsAPI } from '../../api';
|
||||
import { Organization } from '../../types';
|
||||
import { getQSConfig, parseQueryString } from '../../util/qs';
|
||||
import useRequest from '../../util/useRequest';
|
||||
import useAutoPopulateLookup from '../../util/useAutoPopulateLookup';
|
||||
import OptionsList from '../OptionsList';
|
||||
import Lookup from './Lookup';
|
||||
import LookupErrorMessage from './shared/LookupErrorMessage';
|
||||
@ -27,7 +28,10 @@ function OrganizationLookup({
|
||||
required,
|
||||
value,
|
||||
history,
|
||||
autoPopulate,
|
||||
}) {
|
||||
const autoPopulateLookup = useAutoPopulateLookup(onChange);
|
||||
|
||||
const {
|
||||
result: { itemCount, organizations, relatedSearchableKeys, searchableKeys },
|
||||
error: contentError,
|
||||
@ -39,6 +43,11 @@ function OrganizationLookup({
|
||||
OrganizationsAPI.read(params),
|
||||
OrganizationsAPI.readOptions(),
|
||||
]);
|
||||
|
||||
if (autoPopulate) {
|
||||
autoPopulateLookup(response.data.results);
|
||||
}
|
||||
|
||||
return {
|
||||
organizations: response.data.results,
|
||||
itemCount: response.data.count,
|
||||
@ -49,7 +58,7 @@ function OrganizationLookup({
|
||||
actionsResponse.data.actions?.GET || {}
|
||||
).filter(key => actionsResponse.data.actions?.GET[key].filterable),
|
||||
};
|
||||
}, [history.location.search]),
|
||||
}, [autoPopulate, autoPopulateLookup, history.location.search]),
|
||||
{
|
||||
organizations: [],
|
||||
itemCount: 0,
|
||||
@ -129,6 +138,7 @@ OrganizationLookup.propTypes = {
|
||||
onChange: func.isRequired,
|
||||
required: bool,
|
||||
value: Organization,
|
||||
autoPopulate: bool,
|
||||
};
|
||||
|
||||
OrganizationLookup.defaultProps = {
|
||||
@ -137,6 +147,7 @@ OrganizationLookup.defaultProps = {
|
||||
onBlur: () => {},
|
||||
required: false,
|
||||
value: null,
|
||||
autoPopulate: false,
|
||||
};
|
||||
|
||||
export { OrganizationLookup as _OrganizationLookup };
|
||||
|
@ -48,4 +48,50 @@ describe('OrganizationLookup', () => {
|
||||
expect(_OrganizationLookup.defaultProps.onBlur).toBeInstanceOf(Function);
|
||||
expect(_OrganizationLookup.defaultProps.onBlur).not.toThrow();
|
||||
});
|
||||
|
||||
test('should auto-select organization when only one available and autoPopulate prop is true', async () => {
|
||||
OrganizationsAPI.read.mockReturnValue({
|
||||
data: {
|
||||
results: [{ id: 1 }],
|
||||
count: 1,
|
||||
},
|
||||
});
|
||||
const onChange = jest.fn();
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<OrganizationLookup autoPopulate onChange={onChange} />
|
||||
);
|
||||
});
|
||||
expect(onChange).toHaveBeenCalledWith({ id: 1 });
|
||||
});
|
||||
|
||||
test('should not auto-select organization when autoPopulate prop is false', async () => {
|
||||
OrganizationsAPI.read.mockReturnValue({
|
||||
data: {
|
||||
results: [{ id: 1 }],
|
||||
count: 1,
|
||||
},
|
||||
});
|
||||
const onChange = jest.fn();
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(<OrganizationLookup onChange={onChange} />);
|
||||
});
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should not auto-select organization when multiple available', async () => {
|
||||
OrganizationsAPI.read.mockReturnValue({
|
||||
data: {
|
||||
results: [{ id: 1 }, { id: 2 }],
|
||||
count: 2,
|
||||
},
|
||||
});
|
||||
const onChange = jest.fn();
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<OrganizationLookup autoPopulate onChange={onChange} />
|
||||
);
|
||||
});
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
@ -8,6 +8,7 @@ import { ProjectsAPI } from '../../api';
|
||||
import { Project } from '../../types';
|
||||
import { FieldTooltip } from '../FormField';
|
||||
import OptionsList from '../OptionsList';
|
||||
import useAutoPopulateLookup from '../../util/useAutoPopulateLookup';
|
||||
import useRequest from '../../util/useRequest';
|
||||
import { getQSConfig, parseQueryString } from '../../util/qs';
|
||||
import Lookup from './Lookup';
|
||||
@ -21,7 +22,7 @@ const QS_CONFIG = getQSConfig('project', {
|
||||
|
||||
function ProjectLookup({
|
||||
helperTextInvalid,
|
||||
autocomplete,
|
||||
autoPopulate,
|
||||
i18n,
|
||||
isValid,
|
||||
onChange,
|
||||
@ -31,6 +32,7 @@ function ProjectLookup({
|
||||
onBlur,
|
||||
history,
|
||||
}) {
|
||||
const autoPopulateLookup = useAutoPopulateLookup(onChange);
|
||||
const {
|
||||
result: { projects, count, relatedSearchableKeys, searchableKeys, canEdit },
|
||||
request: fetchProjects,
|
||||
@ -43,8 +45,8 @@ function ProjectLookup({
|
||||
ProjectsAPI.read(params),
|
||||
ProjectsAPI.readOptions(),
|
||||
]);
|
||||
if (data.count === 1 && autocomplete) {
|
||||
autocomplete(data.results[0]);
|
||||
if (autoPopulate) {
|
||||
autoPopulateLookup(data.results);
|
||||
}
|
||||
return {
|
||||
count: data.count,
|
||||
@ -57,7 +59,7 @@ function ProjectLookup({
|
||||
).filter(key => actionsResponse.data.actions?.GET[key].filterable),
|
||||
canEdit: Boolean(actionsResponse.data.actions.POST),
|
||||
};
|
||||
}, [history.location.search, autocomplete]),
|
||||
}, [autoPopulate, autoPopulateLookup, history.location.search]),
|
||||
{
|
||||
count: 0,
|
||||
projects: [],
|
||||
@ -151,7 +153,7 @@ function ProjectLookup({
|
||||
}
|
||||
|
||||
ProjectLookup.propTypes = {
|
||||
autocomplete: func,
|
||||
autoPopulate: bool,
|
||||
helperTextInvalid: node,
|
||||
isValid: bool,
|
||||
onBlur: func,
|
||||
@ -162,7 +164,7 @@ ProjectLookup.propTypes = {
|
||||
};
|
||||
|
||||
ProjectLookup.defaultProps = {
|
||||
autocomplete: () => {},
|
||||
autoPopulate: false,
|
||||
helperTextInvalid: '',
|
||||
isValid: true,
|
||||
onBlur: () => {},
|
||||
|
@ -1,28 +1,38 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
|
||||
import { sleep } from '../../../testUtils/testUtils';
|
||||
import { ProjectsAPI } from '../../api';
|
||||
import ProjectLookup from './ProjectLookup';
|
||||
|
||||
jest.mock('../../api');
|
||||
|
||||
describe('<ProjectLookup />', () => {
|
||||
test('should auto-select project when only one available', async () => {
|
||||
test('should auto-select project when only one available and autoPopulate prop is true', async () => {
|
||||
ProjectsAPI.read.mockReturnValue({
|
||||
data: {
|
||||
results: [{ id: 1 }],
|
||||
count: 1,
|
||||
},
|
||||
});
|
||||
const autocomplete = jest.fn();
|
||||
const onChange = jest.fn();
|
||||
await act(async () => {
|
||||
mountWithContexts(
|
||||
<ProjectLookup autocomplete={autocomplete} onChange={() => {}} />
|
||||
);
|
||||
mountWithContexts(<ProjectLookup autoPopulate onChange={onChange} />);
|
||||
});
|
||||
await sleep(0);
|
||||
expect(autocomplete).toHaveBeenCalledWith({ id: 1 });
|
||||
expect(onChange).toHaveBeenCalledWith({ id: 1 });
|
||||
});
|
||||
|
||||
test('should not auto-select project when autoPopulate prop is false', async () => {
|
||||
ProjectsAPI.read.mockReturnValue({
|
||||
data: {
|
||||
results: [{ id: 1 }],
|
||||
count: 1,
|
||||
},
|
||||
});
|
||||
const onChange = jest.fn();
|
||||
await act(async () => {
|
||||
mountWithContexts(<ProjectLookup onChange={onChange} />);
|
||||
});
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should not auto-select project when multiple available', async () => {
|
||||
@ -32,13 +42,10 @@ describe('<ProjectLookup />', () => {
|
||||
count: 2,
|
||||
},
|
||||
});
|
||||
const autocomplete = jest.fn();
|
||||
const onChange = jest.fn();
|
||||
await act(async () => {
|
||||
mountWithContexts(
|
||||
<ProjectLookup autocomplete={autocomplete} onChange={() => {}} />
|
||||
);
|
||||
mountWithContexts(<ProjectLookup autoPopulate onChange={onChange} />);
|
||||
});
|
||||
await sleep(0);
|
||||
expect(autocomplete).not.toHaveBeenCalled();
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useRouteMatch } from 'react-router-dom';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { Formik, useField } from 'formik';
|
||||
import { Formik, useField, useFormikContext } from 'formik';
|
||||
import { Form, FormGroup } from '@patternfly/react-core';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
@ -18,10 +18,12 @@ import AnsibleSelect from '../../../components/AnsibleSelect';
|
||||
|
||||
function ApplicationFormFields({
|
||||
i18n,
|
||||
application,
|
||||
authorizationOptions,
|
||||
clientTypeOptions,
|
||||
}) {
|
||||
const match = useRouteMatch();
|
||||
const { setFieldValue } = useFormikContext();
|
||||
const [organizationField, organizationMeta, organizationHelpers] = useField({
|
||||
name: 'organization',
|
||||
validate: required(null, i18n),
|
||||
@ -40,6 +42,13 @@ function ApplicationFormFields({
|
||||
validate: required(null, i18n),
|
||||
});
|
||||
|
||||
const onOrganizationChange = useCallback(
|
||||
value => {
|
||||
setFieldValue('organization', value);
|
||||
},
|
||||
[setFieldValue]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormField
|
||||
@ -60,11 +69,10 @@ function ApplicationFormFields({
|
||||
helperTextInvalid={organizationMeta.error}
|
||||
isValid={!organizationMeta.touched || !organizationMeta.error}
|
||||
onBlur={() => organizationHelpers.setTouched()}
|
||||
onChange={value => {
|
||||
organizationHelpers.setValue(value);
|
||||
}}
|
||||
onChange={onOrganizationChange}
|
||||
value={organizationField.value}
|
||||
required
|
||||
autoPopulate={!application?.id}
|
||||
/>
|
||||
<FormGroup
|
||||
fieldId="authType"
|
||||
@ -166,6 +174,7 @@ function ApplicationForm({
|
||||
<FormColumnLayout>
|
||||
<ApplicationFormFields
|
||||
formik={formik}
|
||||
application={application}
|
||||
authorizationOptions={authorizationOptions}
|
||||
clientTypeOptions={clientTypeOptions}
|
||||
i18n={i18n}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Formik, useField } from 'formik';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { Formik, useField, useFormikContext } from 'formik';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { arrayOf, func, object, shape } from 'prop-types';
|
||||
@ -21,6 +21,7 @@ function CredentialFormFields({
|
||||
formik,
|
||||
initialValues,
|
||||
}) {
|
||||
const { setFieldValue } = useFormikContext();
|
||||
const [orgField, orgMeta, orgHelpers] = useField('organization');
|
||||
const [credTypeField, credTypeMeta, credTypeHelpers] = useField({
|
||||
name: 'credential_type',
|
||||
@ -76,6 +77,13 @@ function CredentialFormFields({
|
||||
);
|
||||
};
|
||||
|
||||
const onOrganizationChange = useCallback(
|
||||
value => {
|
||||
setFieldValue('organization', value);
|
||||
},
|
||||
[setFieldValue]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormField
|
||||
@ -96,9 +104,7 @@ function CredentialFormFields({
|
||||
helperTextInvalid={orgMeta.error}
|
||||
isValid={!orgMeta.touched || !orgMeta.error}
|
||||
onBlur={() => orgHelpers.setTouched()}
|
||||
onChange={value => {
|
||||
orgHelpers.setValue(value);
|
||||
}}
|
||||
onChange={onOrganizationChange}
|
||||
value={orgField.value}
|
||||
touched={orgMeta.touched}
|
||||
error={orgMeta.error}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Formik, useField } from 'formik';
|
||||
import React, { useCallback } from 'react';
|
||||
import { Formik, useField, useFormikContext } from 'formik';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { func, number, shape } from 'prop-types';
|
||||
@ -17,18 +17,29 @@ import {
|
||||
FormFullWidthLayout,
|
||||
} from '../../../components/FormLayout';
|
||||
|
||||
function InventoryFormFields({ i18n, credentialTypeId }) {
|
||||
function InventoryFormFields({ i18n, credentialTypeId, inventory }) {
|
||||
const { setFieldValue } = useFormikContext();
|
||||
const [organizationField, organizationMeta, organizationHelpers] = useField({
|
||||
name: 'organization',
|
||||
validate: required(i18n._(t`Select a value for this field`), i18n),
|
||||
});
|
||||
const instanceGroupsFieldArr = useField('instanceGroups');
|
||||
const instanceGroupsField = instanceGroupsFieldArr[0];
|
||||
const instanceGroupsHelpers = instanceGroupsFieldArr[2];
|
||||
const [instanceGroupsField, , instanceGroupsHelpers] = useField(
|
||||
'instanceGroups'
|
||||
);
|
||||
const [insightsCredentialField] = useField('insights_credential');
|
||||
const onOrganizationChange = useCallback(
|
||||
value => {
|
||||
setFieldValue('organization', value);
|
||||
},
|
||||
[setFieldValue]
|
||||
);
|
||||
const onCredentialChange = useCallback(
|
||||
value => {
|
||||
setFieldValue('insights_credential', value);
|
||||
},
|
||||
[setFieldValue]
|
||||
);
|
||||
|
||||
const insightsCredentialFieldArr = useField('insights_credential');
|
||||
const insightsCredentialField = insightsCredentialFieldArr[0];
|
||||
const insightsCredentialHelpers = insightsCredentialFieldArr[2];
|
||||
return (
|
||||
<>
|
||||
<FormField
|
||||
@ -49,18 +60,17 @@ function InventoryFormFields({ i18n, credentialTypeId }) {
|
||||
helperTextInvalid={organizationMeta.error}
|
||||
isValid={!organizationMeta.touched || !organizationMeta.error}
|
||||
onBlur={() => organizationHelpers.setTouched()}
|
||||
onChange={value => {
|
||||
organizationHelpers.setValue(value);
|
||||
}}
|
||||
onChange={onOrganizationChange}
|
||||
value={organizationField.value}
|
||||
touched={organizationMeta.touched}
|
||||
error={organizationMeta.error}
|
||||
required
|
||||
autoPopulate={!inventory?.id}
|
||||
/>
|
||||
<CredentialLookup
|
||||
label={i18n._(t`Insights Credential`)}
|
||||
credentialTypeId={credentialTypeId}
|
||||
onChange={value => insightsCredentialHelpers.setValue(value)}
|
||||
onChange={onCredentialChange}
|
||||
value={insightsCredentialField.value}
|
||||
/>
|
||||
<InstanceGroupsLookup
|
||||
@ -115,7 +125,7 @@ function InventoryForm({
|
||||
{formik => (
|
||||
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
|
||||
<FormColumnLayout>
|
||||
<InventoryFormFields {...rest} />
|
||||
<InventoryFormFields {...rest} inventory={inventory} />
|
||||
<FormSubmitError error={submitError} />
|
||||
<FormActionGroup
|
||||
onCancel={onCancel}
|
||||
|
@ -42,7 +42,7 @@ const buildSourceChoiceOptions = options => {
|
||||
return sourceChoices.filter(({ key }) => key !== 'file');
|
||||
};
|
||||
|
||||
const InventorySourceFormFields = ({ sourceOptions, i18n }) => {
|
||||
const InventorySourceFormFields = ({ source, sourceOptions, i18n }) => {
|
||||
const {
|
||||
values,
|
||||
initialValues,
|
||||
@ -170,16 +170,67 @@ const InventorySourceFormFields = ({ sourceOptions, i18n }) => {
|
||||
<FormColumnLayout>
|
||||
{
|
||||
{
|
||||
azure_rm: <AzureSubForm sourceOptions={sourceOptions} />,
|
||||
azure_rm: (
|
||||
<AzureSubForm
|
||||
autoPopulateCredential={
|
||||
!source?.id || source?.source !== 'azure_rm'
|
||||
}
|
||||
sourceOptions={sourceOptions}
|
||||
/>
|
||||
),
|
||||
cloudforms: <CloudFormsSubForm />,
|
||||
ec2: <EC2SubForm sourceOptions={sourceOptions} />,
|
||||
gce: <GCESubForm sourceOptions={sourceOptions} />,
|
||||
openstack: <OpenStackSubForm />,
|
||||
rhv: <VirtualizationSubForm />,
|
||||
satellite6: <SatelliteSubForm />,
|
||||
scm: <SCMSubForm />,
|
||||
tower: <TowerSubForm />,
|
||||
vmware: <VMwareSubForm sourceOptions={sourceOptions} />,
|
||||
gce: (
|
||||
<GCESubForm
|
||||
autoPopulateCredential={
|
||||
!source?.id || source?.source !== 'gce'
|
||||
}
|
||||
sourceOptions={sourceOptions}
|
||||
/>
|
||||
),
|
||||
openstack: (
|
||||
<OpenStackSubForm
|
||||
autoPopulateCredential={
|
||||
!source?.id || source?.source !== 'openstack'
|
||||
}
|
||||
/>
|
||||
),
|
||||
rhv: (
|
||||
<VirtualizationSubForm
|
||||
autoPopulateCredential={
|
||||
!source?.id || source?.source !== 'rhv'
|
||||
}
|
||||
/>
|
||||
),
|
||||
satellite6: (
|
||||
<SatelliteSubForm
|
||||
autoPopulateCredential={
|
||||
!source?.id || source?.source !== 'satellite6'
|
||||
}
|
||||
/>
|
||||
),
|
||||
scm: (
|
||||
<SCMSubForm
|
||||
autoPopulateProject={
|
||||
!source?.id || source?.source !== 'scm'
|
||||
}
|
||||
/>
|
||||
),
|
||||
tower: (
|
||||
<TowerSubForm
|
||||
autoPopulateCredential={
|
||||
!source?.id || source?.source !== 'tower'
|
||||
}
|
||||
/>
|
||||
),
|
||||
vmware: (
|
||||
<VMwareSubForm
|
||||
autoPopulateCredential={
|
||||
!source?.id || source?.source !== 'vmware'
|
||||
}
|
||||
sourceOptions={sourceOptions}
|
||||
/>
|
||||
),
|
||||
}[sourceField.value]
|
||||
}
|
||||
</FormColumnLayout>
|
||||
@ -255,6 +306,7 @@ const InventorySourceForm = ({
|
||||
<InventorySourceFormFields
|
||||
formik={formik}
|
||||
i18n={i18n}
|
||||
source={source}
|
||||
sourceOptions={sourceOptions}
|
||||
/>
|
||||
{submitError && <FormSubmitError error={submitError} />}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useField } from 'formik';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useField, useFormikContext } from 'formik';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import CredentialLookup from '../../../../components/Lookup/CredentialLookup';
|
||||
@ -12,11 +12,19 @@ import {
|
||||
HostFilterField,
|
||||
} from './SharedFields';
|
||||
|
||||
const AzureSubForm = ({ i18n }) => {
|
||||
const AzureSubForm = ({ autoPopulateCredential, i18n }) => {
|
||||
const { setFieldValue } = useFormikContext();
|
||||
const [credentialField, credentialMeta, credentialHelpers] = useField(
|
||||
'credential'
|
||||
);
|
||||
|
||||
const handleCredentialUpdate = useCallback(
|
||||
value => {
|
||||
setFieldValue('credential', value);
|
||||
},
|
||||
[setFieldValue]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CredentialLookup
|
||||
@ -25,11 +33,10 @@ const AzureSubForm = ({ i18n }) => {
|
||||
helperTextInvalid={credentialMeta.error}
|
||||
isValid={!credentialMeta.touched || !credentialMeta.error}
|
||||
onBlur={() => credentialHelpers.setTouched()}
|
||||
onChange={value => {
|
||||
credentialHelpers.setValue(value);
|
||||
}}
|
||||
onChange={handleCredentialUpdate}
|
||||
value={credentialField.value}
|
||||
required
|
||||
autoPopulate={autoPopulateCredential}
|
||||
/>
|
||||
<VerbosityField />
|
||||
<HostFilterField />
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useField } from 'formik';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useField, useFormikContext } from 'formik';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import CredentialLookup from '../../../../components/Lookup/CredentialLookup';
|
||||
@ -13,10 +13,18 @@ import {
|
||||
} from './SharedFields';
|
||||
|
||||
const CloudFormsSubForm = ({ i18n }) => {
|
||||
const { setFieldValue } = useFormikContext();
|
||||
const [credentialField, credentialMeta, credentialHelpers] = useField(
|
||||
'credential'
|
||||
);
|
||||
|
||||
const handleCredentialUpdate = useCallback(
|
||||
value => {
|
||||
setFieldValue('credential', value);
|
||||
},
|
||||
[setFieldValue]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CredentialLookup
|
||||
@ -25,9 +33,7 @@ const CloudFormsSubForm = ({ i18n }) => {
|
||||
helperTextInvalid={credentialMeta.error}
|
||||
isValid={!credentialMeta.touched || !credentialMeta.error}
|
||||
onBlur={() => credentialHelpers.setTouched()}
|
||||
onChange={value => {
|
||||
credentialHelpers.setValue(value);
|
||||
}}
|
||||
onChange={handleCredentialUpdate}
|
||||
value={credentialField.value}
|
||||
required
|
||||
/>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useField } from 'formik';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useField, useFormikContext } from 'formik';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import CredentialLookup from '../../../../components/Lookup/CredentialLookup';
|
||||
@ -13,16 +13,23 @@ import {
|
||||
} from './SharedFields';
|
||||
|
||||
const EC2SubForm = ({ i18n }) => {
|
||||
const [credentialField, , credentialHelpers] = useField('credential');
|
||||
const { setFieldValue } = useFormikContext();
|
||||
const [credentialField] = useField('credential');
|
||||
|
||||
const handleCredentialUpdate = useCallback(
|
||||
value => {
|
||||
setFieldValue('credential', value);
|
||||
},
|
||||
[setFieldValue]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CredentialLookup
|
||||
credentialTypeNamespace="aws"
|
||||
label={i18n._(t`Credential`)}
|
||||
value={credentialField.value}
|
||||
onChange={value => {
|
||||
credentialHelpers.setValue(value);
|
||||
}}
|
||||
onChange={handleCredentialUpdate}
|
||||
/>
|
||||
<VerbosityField />
|
||||
<HostFilterField />
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useField } from 'formik';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useField, useFormikContext } from 'formik';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import CredentialLookup from '../../../../components/Lookup/CredentialLookup';
|
||||
@ -11,11 +11,19 @@ import {
|
||||
HostFilterField,
|
||||
} from './SharedFields';
|
||||
|
||||
const GCESubForm = ({ i18n }) => {
|
||||
const GCESubForm = ({ autoPopulateCredential, i18n }) => {
|
||||
const { setFieldValue } = useFormikContext();
|
||||
const [credentialField, credentialMeta, credentialHelpers] = useField(
|
||||
'credential'
|
||||
);
|
||||
|
||||
const handleCredentialUpdate = useCallback(
|
||||
value => {
|
||||
setFieldValue('credential', value);
|
||||
},
|
||||
[setFieldValue]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CredentialLookup
|
||||
@ -24,11 +32,10 @@ const GCESubForm = ({ i18n }) => {
|
||||
helperTextInvalid={credentialMeta.error}
|
||||
isValid={!credentialMeta.touched || !credentialMeta.error}
|
||||
onBlur={() => credentialHelpers.setTouched()}
|
||||
onChange={value => {
|
||||
credentialHelpers.setValue(value);
|
||||
}}
|
||||
onChange={handleCredentialUpdate}
|
||||
value={credentialField.value}
|
||||
required
|
||||
autoPopulate={autoPopulateCredential}
|
||||
/>
|
||||
<VerbosityField />
|
||||
<HostFilterField />
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useField } from 'formik';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useField, useFormikContext } from 'formik';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import CredentialLookup from '../../../../components/Lookup/CredentialLookup';
|
||||
@ -12,11 +12,19 @@ import {
|
||||
HostFilterField,
|
||||
} from './SharedFields';
|
||||
|
||||
const OpenStackSubForm = ({ i18n }) => {
|
||||
const OpenStackSubForm = ({ autoPopulateCredential, i18n }) => {
|
||||
const { setFieldValue } = useFormikContext();
|
||||
const [credentialField, credentialMeta, credentialHelpers] = useField(
|
||||
'credential'
|
||||
);
|
||||
|
||||
const handleCredentialUpdate = useCallback(
|
||||
value => {
|
||||
setFieldValue('credential', value);
|
||||
},
|
||||
[setFieldValue]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CredentialLookup
|
||||
@ -25,11 +33,10 @@ const OpenStackSubForm = ({ i18n }) => {
|
||||
helperTextInvalid={credentialMeta.error}
|
||||
isValid={!credentialMeta.touched || !credentialMeta.error}
|
||||
onBlur={() => credentialHelpers.setTouched()}
|
||||
onChange={value => {
|
||||
credentialHelpers.setValue(value);
|
||||
}}
|
||||
onChange={handleCredentialUpdate}
|
||||
value={credentialField.value}
|
||||
required
|
||||
autoPopulate={autoPopulateCredential}
|
||||
/>
|
||||
<VerbosityField />
|
||||
<HostFilterField />
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { useField } from 'formik';
|
||||
import { useField, useFormikContext } from 'formik';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { FormGroup } from '@patternfly/react-core';
|
||||
@ -20,8 +20,9 @@ import {
|
||||
HostFilterField,
|
||||
} from './SharedFields';
|
||||
|
||||
const SCMSubForm = ({ i18n }) => {
|
||||
const [credentialField, , credentialHelpers] = useField('credential');
|
||||
const SCMSubForm = ({ autoPopulateProject, i18n }) => {
|
||||
const { setFieldValue } = useFormikContext();
|
||||
const [credentialField] = useField('credential');
|
||||
const [projectField, projectMeta, projectHelpers] = useField({
|
||||
name: 'source_project',
|
||||
validate: required(i18n._(t`Select a value for this field`), i18n),
|
||||
@ -51,21 +52,18 @@ const SCMSubForm = ({ i18n }) => {
|
||||
|
||||
const handleProjectUpdate = useCallback(
|
||||
value => {
|
||||
sourcePathHelpers.setValue('');
|
||||
projectHelpers.setValue(value);
|
||||
setFieldValue('source_path', '');
|
||||
setFieldValue('source_project', value);
|
||||
fetchSourcePath(value.id);
|
||||
},
|
||||
[] // eslint-disable-line react-hooks/exhaustive-deps
|
||||
[fetchSourcePath, setFieldValue]
|
||||
);
|
||||
|
||||
const handleProjectAutocomplete = useCallback(
|
||||
val => {
|
||||
projectHelpers.setValue(val);
|
||||
if (!projectMeta.initialValue) {
|
||||
fetchSourcePath(val.id);
|
||||
}
|
||||
const handleCredentialUpdate = useCallback(
|
||||
value => {
|
||||
setFieldValue('credential', value);
|
||||
},
|
||||
[] // eslint-disable-line react-hooks/exhaustive-deps
|
||||
[setFieldValue]
|
||||
);
|
||||
|
||||
return (
|
||||
@ -74,18 +72,16 @@ const SCMSubForm = ({ i18n }) => {
|
||||
credentialTypeKind="cloud"
|
||||
label={i18n._(t`Credential`)}
|
||||
value={credentialField.value}
|
||||
onChange={value => {
|
||||
credentialHelpers.setValue(value);
|
||||
}}
|
||||
onChange={handleCredentialUpdate}
|
||||
/>
|
||||
<ProjectLookup
|
||||
autocomplete={handleProjectAutocomplete}
|
||||
value={projectField.value}
|
||||
isValid={!projectMeta.touched || !projectMeta.error}
|
||||
helperTextInvalid={projectMeta.error}
|
||||
onBlur={() => projectHelpers.setTouched()}
|
||||
onChange={handleProjectUpdate}
|
||||
required
|
||||
autoPopulate={autoPopulateProject}
|
||||
/>
|
||||
<FormGroup
|
||||
fieldId="source_path"
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useField } from 'formik';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useField, useFormikContext } from 'formik';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import CredentialLookup from '../../../../components/Lookup/CredentialLookup';
|
||||
@ -12,11 +12,19 @@ import {
|
||||
HostFilterField,
|
||||
} from './SharedFields';
|
||||
|
||||
const SatelliteSubForm = ({ i18n }) => {
|
||||
const SatelliteSubForm = ({ autoPopulateCredential, i18n }) => {
|
||||
const { setFieldValue } = useFormikContext();
|
||||
const [credentialField, credentialMeta, credentialHelpers] = useField(
|
||||
'credential'
|
||||
);
|
||||
|
||||
const handleCredentialUpdate = useCallback(
|
||||
value => {
|
||||
setFieldValue('credential', value);
|
||||
},
|
||||
[setFieldValue]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CredentialLookup
|
||||
@ -25,11 +33,10 @@ const SatelliteSubForm = ({ i18n }) => {
|
||||
helperTextInvalid={credentialMeta.error}
|
||||
isValid={!credentialMeta.touched || !credentialMeta.error}
|
||||
onBlur={() => credentialHelpers.setTouched()}
|
||||
onChange={value => {
|
||||
credentialHelpers.setValue(value);
|
||||
}}
|
||||
onChange={handleCredentialUpdate}
|
||||
value={credentialField.value}
|
||||
required
|
||||
autoPopulate={autoPopulateCredential}
|
||||
/>
|
||||
<VerbosityField />
|
||||
<HostFilterField />
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useField } from 'formik';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useField, useFormikContext } from 'formik';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import CredentialLookup from '../../../../components/Lookup/CredentialLookup';
|
||||
@ -11,11 +11,19 @@ import {
|
||||
HostFilterField,
|
||||
} from './SharedFields';
|
||||
|
||||
const TowerSubForm = ({ i18n }) => {
|
||||
const TowerSubForm = ({ autoPopulateCredential, i18n }) => {
|
||||
const { setFieldValue } = useFormikContext();
|
||||
const [credentialField, credentialMeta, credentialHelpers] = useField(
|
||||
'credential'
|
||||
);
|
||||
|
||||
const handleCredentialUpdate = useCallback(
|
||||
value => {
|
||||
setFieldValue('credential', value);
|
||||
},
|
||||
[setFieldValue]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CredentialLookup
|
||||
@ -24,11 +32,10 @@ const TowerSubForm = ({ i18n }) => {
|
||||
helperTextInvalid={credentialMeta.error}
|
||||
isValid={!credentialMeta.touched || !credentialMeta.error}
|
||||
onBlur={() => credentialHelpers.setTouched()}
|
||||
onChange={value => {
|
||||
credentialHelpers.setValue(value);
|
||||
}}
|
||||
onChange={handleCredentialUpdate}
|
||||
value={credentialField.value}
|
||||
required
|
||||
autoPopulate={autoPopulateCredential}
|
||||
/>
|
||||
<VerbosityField />
|
||||
<HostFilterField />
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useField } from 'formik';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useField, useFormikContext } from 'formik';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import CredentialLookup from '../../../../components/Lookup/CredentialLookup';
|
||||
@ -12,11 +12,19 @@ import {
|
||||
HostFilterField,
|
||||
} from './SharedFields';
|
||||
|
||||
const VMwareSubForm = ({ i18n }) => {
|
||||
const VMwareSubForm = ({ autoPopulateCredential, i18n }) => {
|
||||
const { setFieldValue } = useFormikContext();
|
||||
const [credentialField, credentialMeta, credentialHelpers] = useField(
|
||||
'credential'
|
||||
);
|
||||
|
||||
const handleCredentialUpdate = useCallback(
|
||||
value => {
|
||||
setFieldValue('credential', value);
|
||||
},
|
||||
[setFieldValue]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CredentialLookup
|
||||
@ -25,11 +33,10 @@ const VMwareSubForm = ({ i18n }) => {
|
||||
helperTextInvalid={credentialMeta.error}
|
||||
isValid={!credentialMeta.touched || !credentialMeta.error}
|
||||
onBlur={() => credentialHelpers.setTouched()}
|
||||
onChange={value => {
|
||||
credentialHelpers.setValue(value);
|
||||
}}
|
||||
onChange={handleCredentialUpdate}
|
||||
value={credentialField.value}
|
||||
required
|
||||
autoPopulate={autoPopulateCredential}
|
||||
/>
|
||||
<VerbosityField />
|
||||
<HostFilterField />
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useField } from 'formik';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useField, useFormikContext } from 'formik';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import CredentialLookup from '../../../../components/Lookup/CredentialLookup';
|
||||
@ -11,11 +11,19 @@ import {
|
||||
HostFilterField,
|
||||
} from './SharedFields';
|
||||
|
||||
const VirtualizationSubForm = ({ i18n }) => {
|
||||
const VirtualizationSubForm = ({ autoPopulateCredential, i18n }) => {
|
||||
const { setFieldValue } = useFormikContext();
|
||||
const [credentialField, credentialMeta, credentialHelpers] = useField(
|
||||
'credential'
|
||||
);
|
||||
|
||||
const handleCredentialUpdate = useCallback(
|
||||
value => {
|
||||
setFieldValue('credential', value);
|
||||
},
|
||||
[setFieldValue]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CredentialLookup
|
||||
@ -24,11 +32,10 @@ const VirtualizationSubForm = ({ i18n }) => {
|
||||
helperTextInvalid={credentialMeta.error}
|
||||
isValid={!credentialMeta.touched || !credentialMeta.error}
|
||||
onBlur={() => credentialHelpers.setTouched()}
|
||||
onChange={value => {
|
||||
credentialHelpers.setValue(value);
|
||||
}}
|
||||
onChange={handleCredentialUpdate}
|
||||
value={credentialField.value}
|
||||
required
|
||||
autoPopulate={autoPopulateCredential}
|
||||
/>
|
||||
<VerbosityField />
|
||||
<HostFilterField />
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useCallback } from 'react';
|
||||
import { Formik, useField } from 'formik';
|
||||
import { Formik, useField, useFormikContext } from 'formik';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { func, shape, object, arrayOf } from 'prop-types';
|
||||
@ -20,7 +20,8 @@ import useRequest from '../../../util/useRequest';
|
||||
import { required } from '../../../util/validators';
|
||||
import { InventoriesAPI } from '../../../api';
|
||||
|
||||
const SmartInventoryFormFields = withI18n()(({ i18n }) => {
|
||||
const SmartInventoryFormFields = withI18n()(({ i18n, inventory }) => {
|
||||
const { setFieldValue } = useFormikContext();
|
||||
const [organizationField, organizationMeta, organizationHelpers] = useField({
|
||||
name: 'organization',
|
||||
validate: required(i18n._(t`Select a value for this field`), i18n),
|
||||
@ -32,6 +33,12 @@ const SmartInventoryFormFields = withI18n()(({ i18n }) => {
|
||||
name: 'host_filter',
|
||||
validate: required(null, i18n),
|
||||
});
|
||||
const onOrganizationChange = useCallback(
|
||||
value => {
|
||||
setFieldValue('organization', value);
|
||||
},
|
||||
[setFieldValue]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -53,11 +60,10 @@ const SmartInventoryFormFields = withI18n()(({ i18n }) => {
|
||||
helperTextInvalid={organizationMeta.error}
|
||||
isValid={!organizationMeta.touched || !organizationMeta.error}
|
||||
onBlur={() => organizationHelpers.setTouched()}
|
||||
onChange={value => {
|
||||
organizationHelpers.setValue(value);
|
||||
}}
|
||||
onChange={onOrganizationChange}
|
||||
value={organizationField.value}
|
||||
required
|
||||
autoPopulate={!inventory?.id}
|
||||
/>
|
||||
<HostFilterLookup
|
||||
value={hostFilterField.value}
|
||||
@ -144,7 +150,7 @@ function SmartInventoryForm({
|
||||
{formik => (
|
||||
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
|
||||
<FormColumnLayout>
|
||||
<SmartInventoryFormFields />
|
||||
<SmartInventoryFormFields inventory={inventory} />
|
||||
{submitError && <FormSubmitError error={submitError} />}
|
||||
<FormActionGroup
|
||||
onCancel={onCancel}
|
||||
|
@ -1,9 +1,9 @@
|
||||
/* eslint no-nested-ternary: 0 */
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useCallback, useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { Formik, useField } from 'formik';
|
||||
import { Formik, useField, useFormikContext } from 'formik';
|
||||
import { Form, FormGroup, Title } from '@patternfly/react-core';
|
||||
import { Config } from '../../../contexts/Config';
|
||||
import AnsibleSelect from '../../../components/AnsibleSelect';
|
||||
@ -69,6 +69,7 @@ const fetchCredentials = async credential => {
|
||||
};
|
||||
|
||||
function ProjectFormFields({
|
||||
project,
|
||||
project_base_dir,
|
||||
project_local_paths,
|
||||
formik,
|
||||
@ -91,6 +92,8 @@ function ProjectFormFields({
|
||||
scm_update_cache_timeout: 0,
|
||||
};
|
||||
|
||||
const { setFieldValue } = useFormikContext();
|
||||
|
||||
const [scmTypeField, scmTypeMeta, scmTypeHelpers] = useField({
|
||||
name: 'scm_type',
|
||||
validate: required(i18n._(t`Set a value for this field`), i18n),
|
||||
@ -133,15 +136,25 @@ function ProjectFormFields({
|
||||
});
|
||||
};
|
||||
|
||||
const handleCredentialSelection = (type, value) => {
|
||||
setCredentials({
|
||||
...credentials,
|
||||
[type]: {
|
||||
...credentials[type],
|
||||
value,
|
||||
},
|
||||
});
|
||||
};
|
||||
const handleCredentialSelection = useCallback(
|
||||
(type, value) => {
|
||||
setCredentials({
|
||||
...credentials,
|
||||
[type]: {
|
||||
...credentials[type],
|
||||
value,
|
||||
},
|
||||
});
|
||||
},
|
||||
[credentials, setCredentials]
|
||||
);
|
||||
|
||||
const onOrganizationChange = useCallback(
|
||||
value => {
|
||||
setFieldValue('organization', value);
|
||||
},
|
||||
[setFieldValue]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -163,11 +176,10 @@ function ProjectFormFields({
|
||||
helperTextInvalid={organizationMeta.error}
|
||||
isValid={!organizationMeta.touched || !organizationMeta.error}
|
||||
onBlur={() => organizationHelpers.setTouched()}
|
||||
onChange={value => {
|
||||
organizationHelpers.setValue(value);
|
||||
}}
|
||||
onChange={onOrganizationChange}
|
||||
value={organizationField.value}
|
||||
required
|
||||
autoPopulate={!project?.id}
|
||||
/>
|
||||
<FormGroup
|
||||
fieldId="project-scm-type"
|
||||
@ -253,6 +265,9 @@ function ProjectFormFields({
|
||||
credential={credentials.insights}
|
||||
onCredentialSelection={handleCredentialSelection}
|
||||
scmUpdateOnLaunch={formik.values.scm_update_on_launch}
|
||||
autoPopulateCredential={
|
||||
!project?.id || project?.scm_type !== 'insights'
|
||||
}
|
||||
/>
|
||||
),
|
||||
}[formik.values.scm_type]
|
||||
@ -379,6 +394,7 @@ function ProjectForm({ i18n, project, submitError, ...props }) {
|
||||
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
|
||||
<FormColumnLayout>
|
||||
<ProjectFormFields
|
||||
project={project}
|
||||
project_base_dir={project_base_dir}
|
||||
project_local_paths={project_local_paths}
|
||||
formik={formik}
|
||||
|
@ -173,7 +173,7 @@ describe('<ProjectForm />', () => {
|
||||
);
|
||||
});
|
||||
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
wrapper.find('OrganizationLookup').invoke('onBlur')();
|
||||
wrapper.find('OrganizationLookup').invoke('onChange')({
|
||||
id: 1,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { useField } from 'formik';
|
||||
import { useField, useFormikContext } from 'formik';
|
||||
import CredentialLookup from '../../../../components/Lookup/CredentialLookup';
|
||||
import { required } from '../../../../util/validators';
|
||||
import { ScmTypeOptions } from './SharedFields';
|
||||
@ -11,13 +11,21 @@ const InsightsSubForm = ({
|
||||
credential,
|
||||
onCredentialSelection,
|
||||
scmUpdateOnLaunch,
|
||||
autoPopulateCredential,
|
||||
}) => {
|
||||
const credFieldArr = useField({
|
||||
const { setFieldValue } = useFormikContext();
|
||||
const [, credMeta, credHelpers] = useField({
|
||||
name: 'credential',
|
||||
validate: required(i18n._(t`Select a value for this field`), i18n),
|
||||
});
|
||||
const credMeta = credFieldArr[1];
|
||||
const credHelpers = credFieldArr[2];
|
||||
|
||||
const onCredentialChange = useCallback(
|
||||
value => {
|
||||
onCredentialSelection('insights', value);
|
||||
setFieldValue('credential', value.id);
|
||||
},
|
||||
[onCredentialSelection, setFieldValue]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -27,12 +35,10 @@ const InsightsSubForm = ({
|
||||
helperTextInvalid={credMeta.error}
|
||||
isValid={!credMeta.touched || !credMeta.error}
|
||||
onBlur={() => credHelpers.setTouched()}
|
||||
onChange={value => {
|
||||
onCredentialSelection('insights', value);
|
||||
credHelpers.setValue(value.id);
|
||||
}}
|
||||
onChange={onCredentialChange}
|
||||
value={credential.value}
|
||||
required
|
||||
autoPopulate={autoPopulateCredential}
|
||||
/>
|
||||
<ScmTypeOptions hideAllowOverride scmUpdateOnLaunch={scmUpdateOnLaunch} />
|
||||
</>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { useField } from 'formik';
|
||||
import { useFormikContext } from 'formik';
|
||||
import { FormGroup, Title } from '@patternfly/react-core';
|
||||
import CredentialLookup from '../../../../components/Lookup/CredentialLookup';
|
||||
import FormField, { CheckboxField } from '../../../../components/FormField';
|
||||
@ -39,17 +39,22 @@ export const BranchFormField = withI18n()(({ i18n, label }) => (
|
||||
|
||||
export const ScmCredentialFormField = withI18n()(
|
||||
({ i18n, credential, onCredentialSelection }) => {
|
||||
const credHelpers = useField('credential')[2];
|
||||
const { setFieldValue } = useFormikContext();
|
||||
|
||||
const onCredentialChange = useCallback(
|
||||
value => {
|
||||
onCredentialSelection('scm', value);
|
||||
setFieldValue('credential', value ? value.id : '');
|
||||
},
|
||||
[onCredentialSelection, setFieldValue]
|
||||
);
|
||||
|
||||
return (
|
||||
<CredentialLookup
|
||||
credentialTypeId={credential.typeId}
|
||||
label={i18n._(t`Source Control Credential`)}
|
||||
value={credential.value}
|
||||
onChange={value => {
|
||||
onCredentialSelection('scm', value);
|
||||
credHelpers.setValue(value ? value.id : '');
|
||||
}}
|
||||
onChange={onCredentialChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { Formik, useField } from 'formik';
|
||||
import { Formik, useField, useFormikContext } from 'formik';
|
||||
import { Form } from '@patternfly/react-core';
|
||||
import FormActionGroup from '../../../components/FormActionGroup/FormActionGroup';
|
||||
import FormField, { FormSubmitError } from '../../../components/FormField';
|
||||
@ -10,17 +10,23 @@ import OrganizationLookup from '../../../components/Lookup/OrganizationLookup';
|
||||
import { required } from '../../../util/validators';
|
||||
import { FormColumnLayout } from '../../../components/FormLayout';
|
||||
|
||||
function TeamFormFields(props) {
|
||||
const { team, i18n } = props;
|
||||
function TeamFormFields({ team, i18n }) {
|
||||
const { setFieldValue } = useFormikContext();
|
||||
const [organization, setOrganization] = useState(
|
||||
team.summary_fields ? team.summary_fields.organization : null
|
||||
);
|
||||
const orgFieldArr = useField({
|
||||
const [, orgMeta, orgHelpers] = useField({
|
||||
name: 'organization',
|
||||
validate: required(i18n._(t`Select a value for this field`), i18n),
|
||||
});
|
||||
const orgMeta = orgFieldArr[1];
|
||||
const orgHelpers = orgFieldArr[2];
|
||||
|
||||
const onOrganizationChange = useCallback(
|
||||
value => {
|
||||
setFieldValue('organization', value.id);
|
||||
setOrganization(value);
|
||||
},
|
||||
[setFieldValue]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -42,12 +48,10 @@ function TeamFormFields(props) {
|
||||
helperTextInvalid={orgMeta.error}
|
||||
isValid={!orgMeta.touched || !orgMeta.error}
|
||||
onBlur={() => orgHelpers.setTouched('organization')}
|
||||
onChange={value => {
|
||||
orgHelpers.setValue(value.id);
|
||||
setOrganization(value);
|
||||
}}
|
||||
onChange={onOrganizationChange}
|
||||
value={organization}
|
||||
required
|
||||
autoPopulate={!team?.id}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
@ -74,91 +74,119 @@ describe('<WorkflowJobTemplate/>', () => {
|
||||
data: { results: [{ id: 1, name: 'Org Foo' }] },
|
||||
});
|
||||
});
|
||||
beforeEach(() => {
|
||||
WorkflowJobTemplatesAPI.readWorkflowJobTemplateOptions.mockResolvedValue({
|
||||
data: { actions: { PUT: {} } },
|
||||
});
|
||||
history = createMemoryHistory({
|
||||
initialEntries: ['/templates/workflow_job_template/1/details'],
|
||||
});
|
||||
act(() => {
|
||||
wrapper = mountWithContexts(
|
||||
<Route
|
||||
path="/templates/workflow_job_template/:id/details"
|
||||
component={() => (
|
||||
<WorkflowJobTemplate setBreadcrumb={() => {}} me={mockMe} />
|
||||
)}
|
||||
/>,
|
||||
{
|
||||
context: {
|
||||
router: {
|
||||
history,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
wrapper.unmount();
|
||||
});
|
||||
test('calls api to get workflow job template data', async () => {
|
||||
expect(wrapper.find('WorkflowJobTemplate').length).toBe(1);
|
||||
expect(WorkflowJobTemplatesAPI.readDetail).toBeCalledWith('1');
|
||||
wrapper.update();
|
||||
await sleep(0);
|
||||
expect(WorkflowJobTemplatesAPI.readWebhookKey).toBeCalledWith('1');
|
||||
expect(WorkflowJobTemplatesAPI.readWorkflowJobTemplateOptions).toBeCalled();
|
||||
describe('User can PUT', () => {
|
||||
beforeEach(async () => {
|
||||
WorkflowJobTemplatesAPI.readWorkflowJobTemplateOptions.mockResolvedValue({
|
||||
data: { actions: { PUT: {} } },
|
||||
});
|
||||
history = createMemoryHistory({
|
||||
initialEntries: ['/templates/workflow_job_template/1/details'],
|
||||
});
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<Route
|
||||
path="/templates/workflow_job_template/:id/details"
|
||||
component={() => (
|
||||
<WorkflowJobTemplate setBreadcrumb={() => {}} me={mockMe} />
|
||||
)}
|
||||
/>,
|
||||
{
|
||||
context: {
|
||||
router: {
|
||||
history,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
test('calls api to get workflow job template data', async () => {
|
||||
expect(wrapper.find('WorkflowJobTemplate').length).toBe(1);
|
||||
expect(WorkflowJobTemplatesAPI.readDetail).toBeCalledWith('1');
|
||||
wrapper.update();
|
||||
await sleep(0);
|
||||
expect(WorkflowJobTemplatesAPI.readWebhookKey).toBeCalledWith('1');
|
||||
expect(
|
||||
WorkflowJobTemplatesAPI.readWorkflowJobTemplateOptions
|
||||
).toBeCalled();
|
||||
|
||||
expect(CredentialsAPI.readDetail).toBeCalledWith(1234567);
|
||||
expect(OrganizationsAPI.read).toBeCalledWith({
|
||||
page_size: 1,
|
||||
role_level: 'notification_admin_role',
|
||||
expect(CredentialsAPI.readDetail).toBeCalledWith(1234567);
|
||||
expect(OrganizationsAPI.read).toBeCalledWith({
|
||||
page_size: 1,
|
||||
role_level: 'notification_admin_role',
|
||||
});
|
||||
});
|
||||
|
||||
test('renders proper tabs', async () => {
|
||||
const tabs = [
|
||||
'Details',
|
||||
'Access',
|
||||
'Notifications',
|
||||
'Schedules',
|
||||
'Visualizer',
|
||||
'Completed Jobs',
|
||||
'Survey',
|
||||
];
|
||||
waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0);
|
||||
wrapper.update();
|
||||
wrapper.find('TabContainer').forEach(tc => {
|
||||
tabs.forEach(t => expect(tc.prop(`aria-label=[${t}]`)));
|
||||
});
|
||||
});
|
||||
|
||||
test('Does not render Notifications tab', async () => {
|
||||
OrganizationsAPI.read.mockResolvedValue({
|
||||
data: { results: [] },
|
||||
});
|
||||
const tabs = [
|
||||
'Details',
|
||||
'Access',
|
||||
'Schedules',
|
||||
'Visualizer',
|
||||
'Completed Jobs',
|
||||
'Survey',
|
||||
];
|
||||
waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0);
|
||||
wrapper.update();
|
||||
wrapper.find('TabContainer').forEach(tc => {
|
||||
tabs.forEach(t => expect(tc.prop(`aria-label=[${t}]`)));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('renders proper tabs', async () => {
|
||||
const tabs = [
|
||||
'Details',
|
||||
'Access',
|
||||
'Notifications',
|
||||
'Schedules',
|
||||
'Visualizer',
|
||||
'Completed Jobs',
|
||||
'Survey',
|
||||
];
|
||||
waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0);
|
||||
wrapper.update();
|
||||
wrapper.find('TabContainer').forEach(tc => {
|
||||
tabs.forEach(t => expect(tc.prop(`aria-label=[${t}]`)));
|
||||
describe('User cannot PUT', () => {
|
||||
beforeEach(async () => {
|
||||
WorkflowJobTemplatesAPI.readWorkflowJobTemplateOptions.mockResolvedValueOnce(
|
||||
{
|
||||
data: { actions: {} },
|
||||
}
|
||||
);
|
||||
history = createMemoryHistory({
|
||||
initialEntries: ['/templates/workflow_job_template/1/details'],
|
||||
});
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<Route
|
||||
path="/templates/workflow_job_template/:id/details"
|
||||
component={() => (
|
||||
<WorkflowJobTemplate setBreadcrumb={() => {}} me={mockMe} />
|
||||
)}
|
||||
/>,
|
||||
{
|
||||
context: {
|
||||
router: {
|
||||
history,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('Does not render Notifications tab', async () => {
|
||||
OrganizationsAPI.read.mockResolvedValue({
|
||||
data: { results: [] },
|
||||
test('should not call for webhook key', async () => {
|
||||
expect(WorkflowJobTemplatesAPI.readWebhookKey).not.toBeCalled();
|
||||
});
|
||||
const tabs = [
|
||||
'Details',
|
||||
'Access',
|
||||
'Schedules',
|
||||
'Visualizer',
|
||||
'Completed Jobs',
|
||||
'Survey',
|
||||
];
|
||||
waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0);
|
||||
wrapper.update();
|
||||
wrapper.find('TabContainer').forEach(tc => {
|
||||
tabs.forEach(t => expect(tc.prop(`aria-label=[${t}]`)));
|
||||
});
|
||||
});
|
||||
test('should not call for webhook key', async () => {
|
||||
WorkflowJobTemplatesAPI.readWorkflowJobTemplateOptions.mockResolvedValueOnce(
|
||||
{
|
||||
data: { actions: {} },
|
||||
}
|
||||
);
|
||||
expect(WorkflowJobTemplatesAPI.readWebhookKey).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
@ -146,18 +146,11 @@ function JobTemplateForm({
|
||||
|
||||
const handleProjectUpdate = useCallback(
|
||||
value => {
|
||||
playbookHelpers.setValue(0);
|
||||
scmHelpers.setValue('');
|
||||
projectHelpers.setValue(value);
|
||||
setFieldValue('playbook', 0);
|
||||
setFieldValue('scm_branch', '');
|
||||
setFieldValue('project', value);
|
||||
},
|
||||
[] // eslint-disable-line react-hooks/exhaustive-deps
|
||||
);
|
||||
|
||||
const handleProjectAutocomplete = useCallback(
|
||||
val => {
|
||||
projectHelpers.setValue(val);
|
||||
},
|
||||
[] // eslint-disable-line react-hooks/exhaustive-deps
|
||||
[setFieldValue]
|
||||
);
|
||||
|
||||
const jobTypeOptions = [
|
||||
@ -270,8 +263,8 @@ function JobTemplateForm({
|
||||
isValid={!projectMeta.touched || !projectMeta.error}
|
||||
helperTextInvalid={projectMeta.error}
|
||||
onChange={handleProjectUpdate}
|
||||
autocomplete={handleProjectAutocomplete}
|
||||
required
|
||||
autoPopulate={!template?.id}
|
||||
/>
|
||||
{projectField.value?.allow_override && (
|
||||
<FieldWithPrompt
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
InputGroup,
|
||||
Button,
|
||||
} from '@patternfly/react-core';
|
||||
import { useField } from 'formik';
|
||||
import { useField, useFormikContext } from 'formik';
|
||||
import ContentError from '../../../components/ContentError';
|
||||
import ContentLoading from '../../../components/ContentLoading';
|
||||
import useRequest from '../../../util/useRequest';
|
||||
@ -24,6 +24,7 @@ import {
|
||||
} from '../../../api';
|
||||
|
||||
function WebhookSubForm({ i18n, templateType }) {
|
||||
const { setFieldValue } = useFormikContext();
|
||||
const { id } = useParams();
|
||||
const { pathname } = useLocation();
|
||||
const { origin } = document.location;
|
||||
@ -82,6 +83,14 @@ function WebhookSubForm({ i18n, templateType }) {
|
||||
const changeWebhookKey = async () => {
|
||||
await fetchWebhookKey();
|
||||
};
|
||||
|
||||
const onCredentialChange = useCallback(
|
||||
value => {
|
||||
setFieldValue('webhook_credential', value || null);
|
||||
},
|
||||
[setFieldValue]
|
||||
);
|
||||
|
||||
const isUpdateKeyDisabled =
|
||||
pathname.endsWith('/add') ||
|
||||
webhookKeyMeta.initialValue ===
|
||||
@ -211,9 +220,7 @@ function WebhookSubForm({ i18n, templateType }) {
|
||||
t`Optionally select the credential to use to send status updates back to the webhook service.`
|
||||
)}
|
||||
credentialTypeId={credTypeId}
|
||||
onChange={value => {
|
||||
webhookCredentialHelpers.setValue(value || null);
|
||||
}}
|
||||
onChange={onCredentialChange}
|
||||
isValid={!webhookCredentialMeta.error}
|
||||
helperTextInvalid={webhookCredentialMeta.error}
|
||||
value={webhookCredentialField.value}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { t } from '@lingui/macro';
|
||||
import PropTypes, { shape } from 'prop-types';
|
||||
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { useField, withFormik } from 'formik';
|
||||
import { useField, useFormikContext, withFormik } from 'formik';
|
||||
import {
|
||||
Form,
|
||||
FormGroup,
|
||||
@ -43,6 +43,7 @@ function WorkflowJobTemplateForm({
|
||||
i18n,
|
||||
submitError,
|
||||
}) {
|
||||
const { setFieldValue } = useFormikContext();
|
||||
const [enableWebhooks, setEnableWebhooks] = useState(
|
||||
Boolean(template.webhook_service)
|
||||
);
|
||||
@ -53,9 +54,7 @@ function WorkflowJobTemplateForm({
|
||||
);
|
||||
const [labelsField, , labelsHelpers] = useField('labels');
|
||||
const [limitField, limitMeta, limitHelpers] = useField('limit');
|
||||
const [organizationField, organizationMeta, organizationHelpers] = useField(
|
||||
'organization'
|
||||
);
|
||||
const [organizationField, organizationMeta] = useField('organization');
|
||||
const [scmField, , scmHelpers] = useField('scm_branch');
|
||||
const [, webhookServiceMeta, webhookServiceHelpers] = useField(
|
||||
'webhook_service'
|
||||
@ -81,6 +80,13 @@ function WorkflowJobTemplateForm({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [enableWebhooks]);
|
||||
|
||||
const onOrganizationChange = useCallback(
|
||||
value => {
|
||||
setFieldValue('organization', value);
|
||||
},
|
||||
[setFieldValue]
|
||||
);
|
||||
|
||||
if (hasContentError) {
|
||||
return <ContentError error={hasContentError} />;
|
||||
}
|
||||
@ -104,9 +110,7 @@ function WorkflowJobTemplateForm({
|
||||
/>
|
||||
<OrganizationLookup
|
||||
helperTextInvalid={organizationMeta.error}
|
||||
onChange={value => {
|
||||
organizationHelpers.setValue(value || null);
|
||||
}}
|
||||
onChange={onOrganizationChange}
|
||||
value={organizationField.value}
|
||||
isValid={!organizationMeta.error}
|
||||
/>
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { Formik, useField } from 'formik';
|
||||
import { Formik, useField, useFormikContext } from 'formik';
|
||||
import { Form, FormGroup } from '@patternfly/react-core';
|
||||
import AnsibleSelect from '../../../components/AnsibleSelect';
|
||||
import FormActionGroup from '../../../components/FormActionGroup/FormActionGroup';
|
||||
@ -16,6 +16,7 @@ import { FormColumnLayout } from '../../../components/FormLayout';
|
||||
|
||||
function UserFormFields({ user, i18n }) {
|
||||
const [organization, setOrganization] = useState(null);
|
||||
const { setFieldValue } = useFormikContext();
|
||||
|
||||
const userTypeOptions = [
|
||||
{
|
||||
@ -38,17 +39,23 @@ function UserFormFields({ user, i18n }) {
|
||||
},
|
||||
];
|
||||
|
||||
const organizationFieldArr = useField({
|
||||
const [, organizationMeta, organizationHelpers] = useField({
|
||||
name: 'organization',
|
||||
validate: !user.id
|
||||
? required(i18n._(t`Select a value for this field`), i18n)
|
||||
: () => undefined,
|
||||
});
|
||||
const organizationMeta = organizationFieldArr[1];
|
||||
const organizationHelpers = organizationFieldArr[2];
|
||||
|
||||
const [userTypeField, userTypeMeta] = useField('user_type');
|
||||
|
||||
const onOrganizationChange = useCallback(
|
||||
value => {
|
||||
setFieldValue('organization', value.id);
|
||||
setOrganization(value);
|
||||
},
|
||||
[setFieldValue]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormField
|
||||
@ -105,12 +112,10 @@ function UserFormFields({ user, i18n }) {
|
||||
helperTextInvalid={organizationMeta.error}
|
||||
isValid={!organizationMeta.touched || !organizationMeta.error}
|
||||
onBlur={() => organizationHelpers.setTouched()}
|
||||
onChange={value => {
|
||||
organizationHelpers.setValue(value.id);
|
||||
setOrganization(value);
|
||||
}}
|
||||
onChange={onOrganizationChange}
|
||||
value={organization}
|
||||
required
|
||||
autoPopulate={!user?.id}
|
||||
/>
|
||||
)}
|
||||
<FormGroup
|
||||
|
24
awx/ui_next/src/util/useAutoPopulateLookup.jsx
Normal file
24
awx/ui_next/src/util/useAutoPopulateLookup.jsx
Normal file
@ -0,0 +1,24 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
|
||||
/**
|
||||
* useAutoPopulateLookup hook [... insert description]
|
||||
* Param: [... insert params]
|
||||
* Returns: {
|
||||
* [... insert returns]
|
||||
* }
|
||||
*/
|
||||
|
||||
export default function useAutoPopulateLookup(populateLookupField) {
|
||||
const isFirst = useRef(true);
|
||||
|
||||
return useCallback(
|
||||
results => {
|
||||
if (isFirst.current && results.length === 1) {
|
||||
populateLookupField(results[0]);
|
||||
}
|
||||
|
||||
isFirst.current = false;
|
||||
},
|
||||
[populateLookupField]
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user