diff --git a/awx/ui_next/src/components/Lookup/CredentialLookup.jsx b/awx/ui_next/src/components/Lookup/CredentialLookup.jsx
index a31fd8fd7d..43440d0d22 100644
--- a/awx/ui_next/src/components/Lookup/CredentialLookup.jsx
+++ b/awx/ui_next/src/components/Lookup/CredentialLookup.jsx
@@ -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 };
diff --git a/awx/ui_next/src/components/Lookup/CredentialLookup.test.jsx b/awx/ui_next/src/components/Lookup/CredentialLookup.test.jsx
index 6ef8bcb979..d96617bd23 100644
--- a/awx/ui_next/src/components/Lookup/CredentialLookup.test.jsx
+++ b/awx/ui_next/src/components/Lookup/CredentialLookup.test.jsx
@@ -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(
+
+ );
+ });
+ 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(
+
+ );
+ });
+ 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(
+
+ );
+ });
+ expect(onChange).not.toHaveBeenCalled();
+ });
});
diff --git a/awx/ui_next/src/components/Lookup/OrganizationLookup.jsx b/awx/ui_next/src/components/Lookup/OrganizationLookup.jsx
index dfc4e1391f..f958d8e407 100644
--- a/awx/ui_next/src/components/Lookup/OrganizationLookup.jsx
+++ b/awx/ui_next/src/components/Lookup/OrganizationLookup.jsx
@@ -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 };
diff --git a/awx/ui_next/src/components/Lookup/OrganizationLookup.test.jsx b/awx/ui_next/src/components/Lookup/OrganizationLookup.test.jsx
index 999f8cdb4f..81c390a3e3 100644
--- a/awx/ui_next/src/components/Lookup/OrganizationLookup.test.jsx
+++ b/awx/ui_next/src/components/Lookup/OrganizationLookup.test.jsx
@@ -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(
+
+ );
+ });
+ 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();
+ });
+ 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(
+
+ );
+ });
+ expect(onChange).not.toHaveBeenCalled();
+ });
});
diff --git a/awx/ui_next/src/components/Lookup/ProjectLookup.jsx b/awx/ui_next/src/components/Lookup/ProjectLookup.jsx
index 3858c6a7fb..83aa0ac7ce 100644
--- a/awx/ui_next/src/components/Lookup/ProjectLookup.jsx
+++ b/awx/ui_next/src/components/Lookup/ProjectLookup.jsx
@@ -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: () => {},
diff --git a/awx/ui_next/src/components/Lookup/ProjectLookup.test.jsx b/awx/ui_next/src/components/Lookup/ProjectLookup.test.jsx
index 09bb9e5741..04ccad63fe 100644
--- a/awx/ui_next/src/components/Lookup/ProjectLookup.test.jsx
+++ b/awx/ui_next/src/components/Lookup/ProjectLookup.test.jsx
@@ -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('', () => {
- 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(
- {}} />
- );
+ mountWithContexts();
});
- 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();
+ });
+ expect(onChange).not.toHaveBeenCalled();
});
test('should not auto-select project when multiple available', async () => {
@@ -32,13 +42,10 @@ describe('', () => {
count: 2,
},
});
- const autocomplete = jest.fn();
+ const onChange = jest.fn();
await act(async () => {
- mountWithContexts(
- {}} />
- );
+ mountWithContexts();
});
- await sleep(0);
- expect(autocomplete).not.toHaveBeenCalled();
+ expect(onChange).not.toHaveBeenCalled();
});
});
diff --git a/awx/ui_next/src/screens/Application/shared/ApplicationForm.jsx b/awx/ui_next/src/screens/Application/shared/ApplicationForm.jsx
index 8d729ca073..24fd15aadc 100644
--- a/awx/ui_next/src/screens/Application/shared/ApplicationForm.jsx
+++ b/awx/ui_next/src/screens/Application/shared/ApplicationForm.jsx
@@ -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 (
<>
organizationHelpers.setTouched()}
- onChange={value => {
- organizationHelpers.setValue(value);
- }}
+ onChange={onOrganizationChange}
value={organizationField.value}
required
+ autoPopulate={!application?.id}
/>
{
+ setFieldValue('organization', value);
+ },
+ [setFieldValue]
+ );
+
return (
<>
orgHelpers.setTouched()}
- onChange={value => {
- orgHelpers.setValue(value);
- }}
+ onChange={onOrganizationChange}
value={orgField.value}
touched={orgMeta.touched}
error={orgMeta.error}
diff --git a/awx/ui_next/src/screens/Inventory/shared/InventoryForm.jsx b/awx/ui_next/src/screens/Inventory/shared/InventoryForm.jsx
index f0d779afaa..331ecd6e41 100644
--- a/awx/ui_next/src/screens/Inventory/shared/InventoryForm.jsx
+++ b/awx/ui_next/src/screens/Inventory/shared/InventoryForm.jsx
@@ -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 (
<>
organizationHelpers.setTouched()}
- onChange={value => {
- organizationHelpers.setValue(value);
- }}
+ onChange={onOrganizationChange}
value={organizationField.value}
touched={organizationMeta.touched}
error={organizationMeta.error}
required
+ autoPopulate={!inventory?.id}
/>
insightsCredentialHelpers.setValue(value)}
+ onChange={onCredentialChange}
value={insightsCredentialField.value}
/>
(