diff --git a/awx/ui_next/src/components/Lookup/ExecutionEnvironmentLookup.jsx b/awx/ui_next/src/components/Lookup/ExecutionEnvironmentLookup.jsx index 86f659c4309a..4647d5809e25 100644 --- a/awx/ui_next/src/components/Lookup/ExecutionEnvironmentLookup.jsx +++ b/awx/ui_next/src/components/Lookup/ExecutionEnvironmentLookup.jsx @@ -1,11 +1,11 @@ import React, { useCallback, useEffect } from 'react'; -import { string, func, bool } from 'prop-types'; +import { string, func, bool, oneOfType, number } from 'prop-types'; import { useLocation } from 'react-router-dom'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; import { FormGroup, Tooltip } from '@patternfly/react-core'; -import { ExecutionEnvironmentsAPI } from '../../api'; +import { ExecutionEnvironmentsAPI, ProjectsAPI } from '../../api'; import { ExecutionEnvironment } from '../../types'; import { getQSConfig, parseQueryString, mergeParams } from '../../util/qs'; import Popover from '../Popover'; @@ -26,15 +26,38 @@ function ExecutionEnvironmentLookup({ i18n, isDefaultEnvironment, isDisabled, + onBlur, onChange, organizationId, popoverContent, + projectId, tooltip, value, - onBlur, }) { const location = useLocation(); + const { + request: fetchProject, + error: fetchProjectError, + isLoading: fetchProjectLoading, + result: project, + } = useRequest( + useCallback(async () => { + if (!projectId) { + return {}; + } + const { data } = await ProjectsAPI.readDetail(projectId); + return data; + }, [projectId]), + { + project: null, + } + ); + + useEffect(() => { + fetchProject(); + }, [fetchProject]); + const { result: { executionEnvironments, @@ -51,9 +74,10 @@ function ExecutionEnvironmentLookup({ const globallyAvailableParams = globallyAvailable ? { or__organization__isnull: 'True' } : {}; - const organizationIdParams = organizationId - ? { or__organization__id: organizationId } - : {}; + const organizationIdParams = + organizationId || project?.organization + ? { or__organization__id: organizationId } + : {}; const [{ data }, actionsResponse] = await Promise.all([ ExecutionEnvironmentsAPI.read( mergeParams(params, { @@ -73,7 +97,7 @@ function ExecutionEnvironmentLookup({ actionsResponse.data.actions?.GET || {} ).filter(key => actionsResponse.data.actions?.GET[key].filterable), }; - }, [location, globallyAvailable, organizationId]), + }, [location, globallyAvailable, organizationId, project]), { executionEnvironments: [], count: 0, @@ -95,7 +119,7 @@ function ExecutionEnvironmentLookup({ onBlur={onBlur} onChange={onChange} qsConfig={QS_CONFIG} - isLoading={isLoading} + isLoading={isLoading || fetchProjectLoading} isDisabled={isDisabled} renderOptionsList={({ state, dispatch, canDelete }) => ( + ); } @@ -156,12 +180,16 @@ ExecutionEnvironmentLookup.propTypes = { popoverContent: string, onChange: func.isRequired, isDefaultEnvironment: bool, + projectId: oneOfType([number, string]), + organizationId: oneOfType([number, string]), }; ExecutionEnvironmentLookup.defaultProps = { popoverContent: '', isDefaultEnvironment: false, value: null, + projectId: null, + organizationId: null, }; export default withI18n()(ExecutionEnvironmentLookup); diff --git a/awx/ui_next/src/components/Lookup/ExecutionEnvironmentLookup.test.jsx b/awx/ui_next/src/components/Lookup/ExecutionEnvironmentLookup.test.jsx index 783d43707b47..709385436677 100644 --- a/awx/ui_next/src/components/Lookup/ExecutionEnvironmentLookup.test.jsx +++ b/awx/ui_next/src/components/Lookup/ExecutionEnvironmentLookup.test.jsx @@ -2,7 +2,7 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import { mountWithContexts } from '../../../testUtils/enzymeHelpers'; import ExecutionEnvironmentLookup from './ExecutionEnvironmentLookup'; -import { ExecutionEnvironmentsAPI } from '../../api'; +import { ExecutionEnvironmentsAPI, ProjectsAPI } from '../../api'; jest.mock('../../api'); @@ -32,6 +32,17 @@ describe('ExecutionEnvironmentLookup', () => { ExecutionEnvironmentsAPI.read.mockResolvedValue( mockedExecutionEnvironments ); + ProjectsAPI.read.mockResolvedValue({ + data: { + count: 1, + results: [ + { + id: 1, + name: 'Fuz', + }, + ], + }, + }); }); afterEach(() => { @@ -52,14 +63,21 @@ describe('ExecutionEnvironmentLookup', () => { await act(async () => { wrapper = mountWithContexts( {}} /> ); }); wrapper.update(); - expect(ExecutionEnvironmentsAPI.read).toHaveBeenCalledTimes(1); + expect(ExecutionEnvironmentsAPI.read).toHaveBeenCalledTimes(2); expect(wrapper.find('ExecutionEnvironmentLookup')).toHaveLength(1); + expect( + wrapper.find('FormGroup[label="Default Execution Environment"]').length + ).toBe(1); + expect( + wrapper.find('FormGroup[label="Execution Environment"]').length + ).toBe(0); }); test('should fetch execution environments', async () => { @@ -71,6 +89,12 @@ describe('ExecutionEnvironmentLookup', () => { /> ); }); - expect(ExecutionEnvironmentsAPI.read).toHaveBeenCalledTimes(1); + expect(ExecutionEnvironmentsAPI.read).toHaveBeenCalledTimes(2); + expect( + wrapper.find('FormGroup[label="Default Execution Environment"]').length + ).toBe(0); + expect( + wrapper.find('FormGroup[label="Execution Environment"]').length + ).toBe(1); }); }); diff --git a/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.test.jsx b/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.test.jsx index 9860d968c54c..b93b56ef5303 100644 --- a/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.test.jsx +++ b/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.test.jsx @@ -58,7 +58,7 @@ const jobTemplateData = { timeout: 0, use_fact_cache: false, verbosity: '0', - execution_environment: { id: 1, name: 'Foo' }, + execution_environment: { id: 1, name: 'Foo', image: 'localhost.com' }, }; describe('', () => { @@ -139,7 +139,7 @@ describe('', () => { wrapper = mountWithContexts(); }); await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0); - await act(() => { + await act(async () => { wrapper.find('input#template-name').simulate('change', { target: { value: 'Bar', name: 'name' }, }); diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateAdd/WorkflowJobTemplateAdd.test.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateAdd/WorkflowJobTemplateAdd.test.jsx index c6ac734bfaa5..2cfd5e00932b 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateAdd/WorkflowJobTemplateAdd.test.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateAdd/WorkflowJobTemplateAdd.test.jsx @@ -6,6 +6,7 @@ import { WorkflowJobTemplatesAPI, OrganizationsAPI, LabelsAPI, + ExecutionEnvironmentsAPI, } from '../../../api'; import { mountWithContexts } from '../../../../testUtils/enzymeHelpers'; @@ -15,6 +16,7 @@ jest.mock('../../../api/models/WorkflowJobTemplates'); jest.mock('../../../api/models/Organizations'); jest.mock('../../../api/models/Labels'); jest.mock('../../../api/models/Inventories'); +jest.mock('../../../api/models/ExecutionEnvironments'); describe('', () => { let wrapper; @@ -34,6 +36,10 @@ describe('', () => { }, }); + ExecutionEnvironmentsAPI.read.mockResolvedValue({ + data: { results: [{ id: 1, name: 'Foo', image: 'localhost.com' }] }, + }); + await act(async () => { history = createMemoryHistory({ initialEntries: ['/templates/workflow_job_template/add'], @@ -82,6 +88,11 @@ describe('', () => { .find('LabelSelect') .find('SelectToggle') .simulate('click'); + + wrapper.find('ExecutionEnvironmentLookup').invoke('onChange')({ + id: 1, + name: 'Foo', + }); }); wrapper.update(); @@ -113,6 +124,7 @@ describe('', () => { webhook_credential: undefined, webhook_service: '', webhook_url: '', + execution_environment: 1, }); expect(WorkflowJobTemplatesAPI.associateLabel).toHaveBeenCalledTimes(1); diff --git a/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx b/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx index c267e157bb21..b1ba6aac3da7 100644 --- a/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx +++ b/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx @@ -40,7 +40,7 @@ import { ExecutionEnvironmentLookup, } from '../../../components/Lookup'; import Popover from '../../../components/Popover'; -import { JobTemplatesAPI, ProjectsAPI } from '../../../api'; +import { JobTemplatesAPI } from '../../../api'; import LabelSelect from './LabelSelect'; import PlaybookSelect from './PlaybookSelect'; import WebhookSubForm from './WebhookSubForm'; @@ -108,30 +108,6 @@ function JobTemplateForm({ executionEnvironmentHelpers, ] = useField({ name: 'execution_environment' }); - const projectId = projectField.value?.id; - - const { - request: fetchProject, - error: fetchProjectError, - isLoading: fetchProjectLoading, - result: projectData, - } = useRequest( - useCallback(async () => { - if (!projectId) { - return {}; - } - const { data } = await ProjectsAPI.readDetail(projectId); - return data; - }, [projectId]), - { - projectData: null, - } - ); - - useEffect(() => { - fetchProject(); - }, [fetchProject]); - const { request: loadRelatedInstanceGroups, error: instanceGroupError, @@ -213,16 +189,12 @@ function JobTemplateForm({ callbackUrl = `${origin}${path}`; } - if (instanceGroupLoading || fetchProjectLoading) { + if (instanceGroupLoading) { return ; } - if (contentError || instanceGroupError || fetchProjectError) { - return ( - - ); + if (contentError || instanceGroupError) { + return ; } return ( @@ -323,7 +295,7 @@ function JobTemplateForm({ )} globallyAvailable isDisabled={!projectField.value} - organizationId={projectData?.organization} + projectId={projectField.value?.id} /> {projectField.value?.allow_override && (