From d946103961076ba196c657baa5f8ec5842d5ea5f Mon Sep 17 00:00:00 2001 From: Elijah DeLee Date: Thu, 10 Jun 2021 11:23:31 -0400 Subject: [PATCH 01/28] log perf of requests at debug level --- awx/main/middleware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/main/middleware.py b/awx/main/middleware.py index 05e03777fb..71b9ff33d2 100644 --- a/awx/main/middleware.py +++ b/awx/main/middleware.py @@ -46,7 +46,7 @@ class TimingMiddleware(threading.local, MiddlewareMixin): response['X-API-Total-Time'] = '%0.3fs' % total_time if settings.AWX_REQUEST_PROFILE: response['X-API-Profile-File'] = self.prof.stop() - perf_logger.info( + perf_logger.debug( f'request: {request}, response_time: {response["X-API-Total-Time"]}', extra=dict(python_objects=dict(request=request, response=response, X_API_TOTAL_TIME=response["X-API-Total-Time"])), ) From f0bcfc6024a0d6a05c7fee3bdd61f76e54d56739 Mon Sep 17 00:00:00 2001 From: Elijah DeLee Date: Fri, 11 Jun 2021 14:06:27 -0400 Subject: [PATCH 02/28] add EE information to assert_status This should help debugging to know what the EE was when an unexpected status was found. --- awxkit/awxkit/api/mixins/has_status.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/awxkit/awxkit/api/mixins/has_status.py b/awxkit/awxkit/api/mixins/has_status.py index e4bd603327..057b349672 100644 --- a/awxkit/awxkit/api/mixins/has_status.py +++ b/awxkit/awxkit/api/mixins/has_status.py @@ -67,6 +67,14 @@ class HasStatus(object): msg += '{0}-{1} has status of {2}, which is not in {3}.'.format(self.type.title(), self.id, self.status, status_list) if getattr(self, 'job_explanation', ''): msg += '\njob_explanation: {}'.format(bytes_to_str(self.job_explanation)) + if getattr(self, 'execution_environment', ''): + msg += '\nexecution_environment: {}'.format(bytes_to_str(self.execution_environment)) + if getattr(self, 'related', False): + ee = self.related.execution_environment.get() + msg += f'\nee_image: {ee.image}' + msg += f'\nee_credential: {ee.credential}' + msg += f'\nee_pull_option: {ee.pull}' + msg += f'\nee_summary_fields: {ee.summary_fields}' if getattr(self, 'result_traceback', ''): msg += '\nresult_traceback:\n{}'.format(bytes_to_str(self.result_traceback)) From 9af16c18c968d57c1c7a693db938361c858bb1d5 Mon Sep 17 00:00:00 2001 From: mabashian Date: Tue, 8 Jun 2021 16:08:39 -0400 Subject: [PATCH 03/28] Dont reconnect job output socket if it closes cleanly From 0eff3e6bacc90ed68564d0f6802427e1ddb5e2b6 Mon Sep 17 00:00:00 2001 From: mabashian Date: Tue, 8 Jun 2021 13:33:10 -0400 Subject: [PATCH 04/28] Pull brand name from default.strings.json From 666acb9756913e3670e2384ea9b1770fd18c62c6 Mon Sep 17 00:00:00 2001 From: mabashian Date: Wed, 9 Jun 2021 16:13:20 -0400 Subject: [PATCH 05/28] Extract strings From 276a18b3392b8687927bf809d7d25551e65fc340 Mon Sep 17 00:00:00 2001 From: Alex Corey Date: Thu, 10 Jun 2021 11:21:57 -0400 Subject: [PATCH 06/28] fixes next button and internationalizes nav buttons on wizard From 21e6e5701ec9509e9b60211ef8f6cc5a1d39a756 Mon Sep 17 00:00:00 2001 From: mabashian Date: Wed, 9 Jun 2021 13:19:14 -0400 Subject: [PATCH 07/28] Addresses bug where advanced search by groups wasn't working on host list From 372baa12a5abb454ab0e00a7c7a211ed9104ad5e Mon Sep 17 00:00:00 2001 From: Bill Nottingham Date: Fri, 11 Jun 2021 15:03:51 -0400 Subject: [PATCH 08/28] Update runner version. From 04f6fe6cd29f44a06c70b60b2978891d9a9dead5 Mon Sep 17 00:00:00 2001 From: nixocio Date: Thu, 10 Jun 2021 16:27:23 -0400 Subject: [PATCH 09/28] Disable inventory field when editing host Disable inventory field when editing host See: https://github.com/ansible/awx/issues/10229 --- .../src/components/HostForm/HostForm.jsx | 48 ++++++++++++------- .../src/components/HostForm/HostForm.test.jsx | 15 ++++++ .../src/components/Lookup/InventoryLookup.jsx | 9 ++-- awx/ui_next/src/components/Lookup/Lookup.jsx | 2 + .../src/screens/Host/HostEdit/HostEdit.jsx | 1 + 5 files changed, 56 insertions(+), 19 deletions(-) diff --git a/awx/ui_next/src/components/HostForm/HostForm.jsx b/awx/ui_next/src/components/HostForm/HostForm.jsx index a9e1162909..6df19164d5 100644 --- a/awx/ui_next/src/components/HostForm/HostForm.jsx +++ b/awx/ui_next/src/components/HostForm/HostForm.jsx @@ -2,7 +2,7 @@ import React, { useCallback } from 'react'; import { bool, func, shape } from 'prop-types'; import { Formik, useField, useFormikContext } from 'formik'; import { t } from '@lingui/macro'; -import { Form, FormGroup } from '@patternfly/react-core'; +import { Form, FormGroup, Tooltip } from '@patternfly/react-core'; import FormField, { FormSubmitError } from '../FormField'; import FormActionGroup from '../FormActionGroup/FormActionGroup'; import { VariablesField } from '../CodeEditor'; @@ -11,7 +11,7 @@ import { FormColumnLayout, FormFullWidthLayout } from '../FormLayout'; import Popover from '../Popover'; import { required } from '../../util/validators'; -const InventoryLookupField = () => { +const InventoryLookupField = ({ isDisabled }) => { const { setFieldValue, setFieldTouched } = useFormikContext(); const [inventoryField, inventoryMeta, inventoryHelpers] = useField( 'inventory' @@ -25,6 +25,23 @@ const InventoryLookupField = () => { [setFieldValue, setFieldTouched] ); + const renderInventoryLookup = ( + inventoryHelpers.setTouched()} + tooltip={t`Select the inventory that this host will belong to.`} + isValid={!inventoryMeta.touched || !inventoryMeta.error} + helperTextInvalid={inventoryMeta.error} + onChange={handleInventoryUpdate} + required + touched={inventoryMeta.touched} + error={inventoryMeta.error} + validate={required(t`Select a value for this field`)} + isDisabled={isDisabled} + /> + ); + return ( { } helperTextInvalid={inventoryMeta.error} > - inventoryHelpers.setTouched()} - tooltip={t`Select the inventory that this host will belong to.`} - isValid={!inventoryMeta.touched || !inventoryMeta.error} - helperTextInvalid={inventoryMeta.error} - onChange={handleInventoryUpdate} - required - touched={inventoryMeta.touched} - error={inventoryMeta.error} - validate={required(t`Select a value for this field`)} - /> + {isDisabled ? ( + + {renderInventoryLookup} + + ) : ( + renderInventoryLookup + )} ); }; @@ -63,6 +74,7 @@ const HostForm = ({ host, isInventoryVisible, submitError, + disableInventoryLookup, }) => { return ( - {isInventoryVisible && } + {isInventoryVisible && ( + + )} ', () => { expect(wrapper.find('input#host-description').prop('value')).toEqual( 'new bar' ); + expect(wrapper.find('InventoryLookup').prop('isDisabled')).toEqual(false); }); test('calls handleSubmit when form submitted', async () => { @@ -84,4 +85,18 @@ describe('', () => { }); expect(wrapper.find('InventoryLookupField').length).toBe(0); }); + + test('inventory lookup field should be disabled', async () => { + await act(async () => { + wrapper = mountWithContexts( + + ); + }); + expect(wrapper.find('InventoryLookup').prop('isDisabled')).toEqual(true); + }); }); diff --git a/awx/ui_next/src/components/Lookup/InventoryLookup.jsx b/awx/ui_next/src/components/Lookup/InventoryLookup.jsx index 66a7bd841f..f4653321e9 100644 --- a/awx/ui_next/src/components/Lookup/InventoryLookup.jsx +++ b/awx/ui_next/src/components/Lookup/InventoryLookup.jsx @@ -31,6 +31,7 @@ function InventoryLookup({ isOverrideDisabled, validate, fieldName, + isDisabled, }) { const { result: { @@ -105,7 +106,7 @@ function InventoryLookup({ label={t`Inventory`} promptId={promptId} promptName={promptName} - isDisabled={!canEdit} + isDisabled={!canEdit || isDisabled} tooltip={t`Select the inventory containing the hosts you want this job to manage.`} > @@ -120,7 +121,7 @@ function InventoryLookup({ fieldName={fieldName} validate={validate} isLoading={isLoading} - isDisabled={!canEdit} + isDisabled={!canEdit || isDisabled} qsConfig={QS_CONFIG} renderOptionsList={({ state, dispatch, canDelete }) => ( ( {}, fieldName: 'inventory', + isDisabled: false, }; export default withRouter(InventoryLookup); diff --git a/awx/ui_next/src/components/Lookup/Lookup.jsx b/awx/ui_next/src/components/Lookup/Lookup.jsx index 04842332ad..0be5b61b85 100644 --- a/awx/ui_next/src/components/Lookup/Lookup.jsx +++ b/awx/ui_next/src/components/Lookup/Lookup.jsx @@ -215,6 +215,7 @@ Lookup.propTypes = { fieldName: string.isRequired, validate: func, onDebounce: func, + isDisabled: bool, }; Lookup.defaultProps = { @@ -235,6 +236,7 @@ Lookup.defaultProps = { ), validate: () => undefined, onDebounce: () => undefined, + isDisabled: false, }; export { Lookup as _Lookup }; diff --git a/awx/ui_next/src/screens/Host/HostEdit/HostEdit.jsx b/awx/ui_next/src/screens/Host/HostEdit/HostEdit.jsx index 8e90b6d535..f5680562f7 100644 --- a/awx/ui_next/src/screens/Host/HostEdit/HostEdit.jsx +++ b/awx/ui_next/src/screens/Host/HostEdit/HostEdit.jsx @@ -34,6 +34,7 @@ function HostEdit({ host }) { handleSubmit={handleSubmit} handleCancel={handleCancel} submitError={formError} + disableInventoryLookup /> ); From 4d2c64ebb459c799a3e8fed43711439fcc3f7d76 Mon Sep 17 00:00:00 2001 From: nixocio Date: Thu, 20 May 2021 15:54:55 -0400 Subject: [PATCH 10/28] Add RBAC rules to the side-nav Add RBAC rules to the side-nav System Admin System Auditor Org Admin Notification Admin Execution Environment Admin Normal User Those are the user profiles taken in consideration when displaying the side-nav. See: https://github.com/ansible/awx/issues/4426 --- awx/ui_next/src/App.jsx | 21 +- .../AppContainer/NavExpandableGroup.jsx | 2 +- awx/ui_next/src/contexts/Config.jsx | 40 ++- awx/ui_next/src/routeConfig.jsx | 27 +- awx/ui_next/src/routeConfig.test.jsx | 248 ++++++++++++++++++ 5 files changed, 328 insertions(+), 10 deletions(-) create mode 100644 awx/ui_next/src/routeConfig.test.jsx diff --git a/awx/ui_next/src/App.jsx b/awx/ui_next/src/App.jsx index 88c91406c0..59a8e28cd9 100644 --- a/awx/ui_next/src/App.jsx +++ b/awx/ui_next/src/App.jsx @@ -12,7 +12,11 @@ import { ErrorBoundary } from 'react-error-boundary'; import { I18nProvider } from '@lingui/react'; import { i18n } from '@lingui/core'; import { Card, PageSection } from '@patternfly/react-core'; -import { ConfigProvider, useAuthorizedPath } from './contexts/Config'; +import { + ConfigProvider, + useAuthorizedPath, + useUserProfile, +} from './contexts/Config'; import { SessionProvider, useSession } from './contexts/Session'; import AppContainer from './components/AppContainer'; import Background from './components/Background'; @@ -38,6 +42,17 @@ function ErrorFallback({ error }) { ); } +const RenderAppContainer = () => { + const userProfile = useUserProfile(); + const navRouteConfig = getRouteConfig(userProfile); + + return ( + + + + ); +}; + const AuthorizedRoutes = ({ routeConfig }) => { const isAuthorized = useAuthorizedPath(); const match = useRouteMatch(); @@ -150,9 +165,7 @@ function App() { - - - + diff --git a/awx/ui_next/src/components/AppContainer/NavExpandableGroup.jsx b/awx/ui_next/src/components/AppContainer/NavExpandableGroup.jsx index 80d7c2dc2c..bfd5636b33 100644 --- a/awx/ui_next/src/components/AppContainer/NavExpandableGroup.jsx +++ b/awx/ui_next/src/components/AppContainer/NavExpandableGroup.jsx @@ -27,7 +27,7 @@ class NavExpandableGroup extends Component { render() { const { groupId, groupTitle, routes } = this.props; - if (routes.length === 1) { + if (routes.length === 1 && groupId === 'settings') { const [{ path }] = routes; return ( diff --git a/awx/ui_next/src/contexts/Config.jsx b/awx/ui_next/src/contexts/Config.jsx index e87c2a24f2..5580bd9192 100644 --- a/awx/ui_next/src/contexts/Config.jsx +++ b/awx/ui_next/src/contexts/Config.jsx @@ -3,7 +3,7 @@ import { useRouteMatch } from 'react-router-dom'; import { t } from '@lingui/macro'; -import { ConfigAPI, MeAPI } from '../api'; +import { ConfigAPI, MeAPI, UsersAPI, OrganizationsAPI } from '../api'; import useRequest, { useDismissableError } from '../util/useRequest'; import AlertModal from '../components/AlertModal'; import ErrorDetail from '../components/ErrorDetail'; @@ -35,9 +35,32 @@ export const ConfigProvider = ({ children }) => { }, }, ] = await Promise.all([ConfigAPI.read(), MeAPI.read()]); - return { ...data, me }; + + const [ + { + data: { count: adminOrgCount }, + }, + { + data: { count: notifAdminCount }, + }, + { + data: { count: execEnvAdminCount }, + }, + ] = await Promise.all([ + UsersAPI.readAdminOfOrganizations(me?.id), + OrganizationsAPI.read({ + page_size: 1, + role_level: 'notification_admin_role', + }), + OrganizationsAPI.read({ + page_size: 1, + role_level: 'execution_environment_admin_role', + }), + ]); + + return { ...data, me, adminOrgCount, notifAdminCount, execEnvAdminCount }; }, []), - {} + { adminOrgCount: 0, notifAdminCount: 0, execEnvAdminCount: 0 } ); const { error, dismissError } = useDismissableError(configError); @@ -77,6 +100,17 @@ export const ConfigProvider = ({ children }) => { ); }; +export const useUserProfile = () => { + const config = useConfig(); + return { + isSuperUser: !!config.me?.is_superuser, + isSystemAuditor: !!config.me?.is_system_auditor, + isOrgAdmin: config.adminOrgCount, + isNotificationAdmin: config.notifAdminCount, + isExecEnvAdmin: config.execEnvAdminCount, + }; +}; + export const useAuthorizedPath = () => { const config = useConfig(); const subscriptionMgmtRoute = useRouteMatch({ diff --git a/awx/ui_next/src/routeConfig.jsx b/awx/ui_next/src/routeConfig.jsx index fba640a2d8..5d29072fcd 100644 --- a/awx/ui_next/src/routeConfig.jsx +++ b/awx/ui_next/src/routeConfig.jsx @@ -22,8 +22,8 @@ import Users from './screens/User'; import WorkflowApprovals from './screens/WorkflowApproval'; import { Jobs } from './screens/Job'; -function getRouteConfig() { - return [ +function getRouteConfig(userProfile = {}) { + let routeConfig = [ { groupTitle: Views, groupId: 'views_group', @@ -155,6 +155,29 @@ function getRouteConfig() { ], }, ]; + + const deleteRoute = name => { + routeConfig.forEach(group => { + group.routes = group.routes.filter(({ path }) => !path.includes(name)); + }); + routeConfig = routeConfig.filter(groups => groups.routes.length); + }; + + const deleteRouteGroup = name => { + routeConfig = routeConfig.filter(({ groupId }) => !groupId.includes(name)); + }; + + if (userProfile?.isSuperUser || userProfile?.isSystemAuditor) + return routeConfig; + deleteRouteGroup('settings'); + deleteRoute('management_jobs'); + deleteRoute('credential_types'); + if (userProfile?.isOrgAdmin) return routeConfig; + deleteRoute('applications'); + deleteRoute('instance_groups'); + if (!userProfile?.isNotificationAdmin) deleteRoute('notification_templates'); + + return routeConfig; } export default getRouteConfig; diff --git a/awx/ui_next/src/routeConfig.test.jsx b/awx/ui_next/src/routeConfig.test.jsx new file mode 100644 index 0000000000..b4382bbcd0 --- /dev/null +++ b/awx/ui_next/src/routeConfig.test.jsx @@ -0,0 +1,248 @@ +import getRouteConfig from './routeConfig'; + +const userProfile = { + isSuperUser: false, + isSystemAuditor: false, + isOrgAdmin: false, + isNotificationAdmin: false, + isExecEnvAdmin: false, +}; + +const filterPaths = sidebar => { + const visibleRoutes = []; + sidebar.forEach(({ routes }) => { + routes.forEach(route => { + visibleRoutes.push(route.path); + }); + }); + + return visibleRoutes; +}; +describe('getRouteConfig', () => { + test('routes for system admin', () => { + const sidebar = getRouteConfig({ ...userProfile, isSuperUser: true }); + const filteredPaths = filterPaths(sidebar); + expect(filteredPaths).toEqual([ + '/home', + '/jobs', + '/schedules', + '/activity_stream', + '/workflow_approvals', + '/templates', + '/credentials', + '/projects', + '/inventories', + '/hosts', + '/organizations', + '/users', + '/teams', + '/credential_types', + '/notification_templates', + '/management_jobs', + '/instance_groups', + '/applications', + '/execution_environments', + '/settings', + ]); + }); + + test('routes for system auditor', () => { + const sidebar = getRouteConfig({ ...userProfile, isSystemAuditor: true }); + const filteredPaths = filterPaths(sidebar); + expect(filteredPaths).toEqual([ + '/home', + '/jobs', + '/schedules', + '/activity_stream', + '/workflow_approvals', + '/templates', + '/credentials', + '/projects', + '/inventories', + '/hosts', + '/organizations', + '/users', + '/teams', + '/credential_types', + '/notification_templates', + '/management_jobs', + '/instance_groups', + '/applications', + '/execution_environments', + '/settings', + ]); + }); + + test('routes for org admin', () => { + const sidebar = getRouteConfig({ ...userProfile, isOrgAdmin: true }); + const filteredPaths = filterPaths(sidebar); + expect(filteredPaths).toEqual([ + '/home', + '/jobs', + '/schedules', + '/activity_stream', + '/workflow_approvals', + '/templates', + '/credentials', + '/projects', + '/inventories', + '/hosts', + '/organizations', + '/users', + '/teams', + '/notification_templates', + '/instance_groups', + '/applications', + '/execution_environments', + ]); + }); + + test('routes for notifications admin', () => { + const sidebar = getRouteConfig({ + ...userProfile, + isNotificationAdmin: true, + }); + const filteredPaths = filterPaths(sidebar); + expect(filteredPaths).toEqual([ + '/home', + '/jobs', + '/schedules', + '/activity_stream', + '/workflow_approvals', + '/templates', + '/credentials', + '/projects', + '/inventories', + '/hosts', + '/organizations', + '/users', + '/teams', + '/notification_templates', + '/execution_environments', + ]); + }); + + test('routes for execution environments admin', () => { + const sidebar = getRouteConfig({ ...userProfile, isExecEnvAdmin: true }); + const filteredPaths = filterPaths(sidebar); + expect(filteredPaths).toEqual([ + '/home', + '/jobs', + '/schedules', + '/activity_stream', + '/workflow_approvals', + '/templates', + '/credentials', + '/projects', + '/inventories', + '/hosts', + '/organizations', + '/users', + '/teams', + '/execution_environments', + ]); + }); + + test('routes for regular users', () => { + const sidebar = getRouteConfig(userProfile); + const filteredPaths = filterPaths(sidebar); + expect(filteredPaths).toEqual([ + '/home', + '/jobs', + '/schedules', + '/activity_stream', + '/workflow_approvals', + '/templates', + '/credentials', + '/projects', + '/inventories', + '/hosts', + '/organizations', + '/users', + '/teams', + '/execution_environments', + ]); + }); + + test('routes for execution environment admins and notification admin', () => { + const sidebar = getRouteConfig({ + ...userProfile, + isExecEnvAdmin: true, + isNotificationAdmin: true, + }); + const filteredPaths = filterPaths(sidebar); + expect(filteredPaths).toEqual([ + '/home', + '/jobs', + '/schedules', + '/activity_stream', + '/workflow_approvals', + '/templates', + '/credentials', + '/projects', + '/inventories', + '/hosts', + '/organizations', + '/users', + '/teams', + '/notification_templates', + '/execution_environments', + ]); + }); + + test('routes for execution environment admins and organization admins', () => { + const sidebar = getRouteConfig({ + ...userProfile, + isExecEnvAdmin: true, + isOrgAdmin: true, + }); + const filteredPaths = filterPaths(sidebar); + expect(filteredPaths).toEqual([ + '/home', + '/jobs', + '/schedules', + '/activity_stream', + '/workflow_approvals', + '/templates', + '/credentials', + '/projects', + '/inventories', + '/hosts', + '/organizations', + '/users', + '/teams', + '/notification_templates', + '/instance_groups', + '/applications', + '/execution_environments', + ]); + }); + + test('routes for notification admins and organization admins', () => { + const sidebar = getRouteConfig({ + ...userProfile, + isNotificationAdmin: true, + isOrgAdmin: true, + }); + const filteredPaths = filterPaths(sidebar); + expect(filteredPaths).toEqual([ + '/home', + '/jobs', + '/schedules', + '/activity_stream', + '/workflow_approvals', + '/templates', + '/credentials', + '/projects', + '/inventories', + '/hosts', + '/organizations', + '/users', + '/teams', + '/notification_templates', + '/instance_groups', + '/applications', + '/execution_environments', + ]); + }); +}); From 2309feb6bd831fec62c8d31748937251ae333bc1 Mon Sep 17 00:00:00 2001 From: Alan Rominger Date: Fri, 11 Jun 2021 13:32:46 -0400 Subject: [PATCH 11/28] Make ExecutionEnvironment awxkit class able to handle null values in payload From da951714d1c3f29ed30e6cb00c20d70cb0418232 Mon Sep 17 00:00:00 2001 From: Alex Corey Date: Fri, 11 Jun 2021 11:48:51 -0400 Subject: [PATCH 12/28] resolves iinfinite loop --- .../src/components/AssociateModal/AssociateModal.test.jsx | 1 + .../src/screens/InstanceGroup/Instances/InstanceList.jsx | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/awx/ui_next/src/components/AssociateModal/AssociateModal.test.jsx b/awx/ui_next/src/components/AssociateModal/AssociateModal.test.jsx index d624f683d2..18174f8b08 100644 --- a/awx/ui_next/src/components/AssociateModal/AssociateModal.test.jsx +++ b/awx/ui_next/src/components/AssociateModal/AssociateModal.test.jsx @@ -54,6 +54,7 @@ describe('', () => { test('should fetch and render list items', () => { expect(fetchRequest).toHaveBeenCalledTimes(1); + expect(optionsRequest).toHaveBeenCalledTimes(1); expect(wrapper.find('CheckboxListItem').length).toBe(3); }); diff --git a/awx/ui_next/src/screens/InstanceGroup/Instances/InstanceList.jsx b/awx/ui_next/src/screens/InstanceGroup/Instances/InstanceList.jsx index e66a13457e..6caa945f25 100644 --- a/awx/ui_next/src/screens/InstanceGroup/Instances/InstanceList.jsx +++ b/awx/ui_next/src/screens/InstanceGroup/Instances/InstanceList.jsx @@ -141,8 +141,10 @@ function InstanceList() { [instanceGroupId] ); - const readInstancesOptions = () => - InstanceGroupsAPI.readInstanceOptions(instanceGroupId); + const readInstancesOptions = useCallback( + () => InstanceGroupsAPI.readInstanceOptions(instanceGroupId), + [instanceGroupId] + ); return ( <> From 8c1cd9ee7148a6e78f8827452da5f9d08f343539 Mon Sep 17 00:00:00 2001 From: mabashian Date: Thu, 10 Jun 2021 15:06:13 -0400 Subject: [PATCH 13/28] Fixes bug where source list page would crash if first sync was running --- .../screens/Inventory/InventorySources/InventorySourceList.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/ui_next/src/screens/Inventory/InventorySources/InventorySourceList.jsx b/awx/ui_next/src/screens/Inventory/InventorySources/InventorySourceList.jsx index ee05909ba4..e3274c19a2 100644 --- a/awx/ui_next/src/screens/Inventory/InventorySources/InventorySourceList.jsx +++ b/awx/ui_next/src/screens/Inventory/InventorySources/InventorySourceList.jsx @@ -25,7 +25,7 @@ import InventorySourceListItem from './InventorySourceListItem'; import useWsInventorySources from './useWsInventorySources'; import { relatedResourceDeleteRequests } from '../../../util/getRelatedResourceDeleteDetails'; -const QS_CONFIG = getQSConfig('inventory-sources', { +const QS_CONFIG = getQSConfig('inventory_sources', { page: 1, page_size: 20, order_by: 'name', From 32cee852f0351e1a0f5c5ba2b3a7450b482a225a Mon Sep 17 00:00:00 2001 From: mabashian Date: Thu, 10 Jun 2021 16:42:03 -0400 Subject: [PATCH 14/28] Swap underscore for hyphen --- .../screens/Inventory/InventorySources/InventorySourceList.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/ui_next/src/screens/Inventory/InventorySources/InventorySourceList.jsx b/awx/ui_next/src/screens/Inventory/InventorySources/InventorySourceList.jsx index e3274c19a2..ee05909ba4 100644 --- a/awx/ui_next/src/screens/Inventory/InventorySources/InventorySourceList.jsx +++ b/awx/ui_next/src/screens/Inventory/InventorySources/InventorySourceList.jsx @@ -25,7 +25,7 @@ import InventorySourceListItem from './InventorySourceListItem'; import useWsInventorySources from './useWsInventorySources'; import { relatedResourceDeleteRequests } from '../../../util/getRelatedResourceDeleteDetails'; -const QS_CONFIG = getQSConfig('inventory_sources', { +const QS_CONFIG = getQSConfig('inventory-sources', { page: 1, page_size: 20, order_by: 'name', From 2c5bcf268d35d9b4195242adf5f8ec3ad56c7da7 Mon Sep 17 00:00:00 2001 From: nixocio Date: Wed, 9 Jun 2021 15:01:42 -0400 Subject: [PATCH 15/28] Update UI to reflect new API EE changes * Show resolved EE for job template details. * Do not show EE if a certain job status still running, since EE on the API is not set yet. It was causing the bug to temporarily show `missing resource`. * Fix discrepancy about job types for list and details. See: https://github.com/ansible/awx/issues/10327 Also: https://github.com/ansible/awx/issues/10399 --- .../ExecutionEnvironmentDetail.jsx | 4 ++++ awx/ui_next/src/components/JobList/JobListItem.jsx | 13 ++++++++----- awx/ui_next/src/screens/Job/JobDetail/JobDetail.jsx | 12 +++++++----- .../JobTemplateDetail/JobTemplateDetail.jsx | 6 ++++-- .../screens/Template/shared/data.job_template.json | 6 ++++++ 5 files changed, 29 insertions(+), 12 deletions(-) diff --git a/awx/ui_next/src/components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.jsx b/awx/ui_next/src/components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.jsx index 720595e013..5a123ebe49 100644 --- a/awx/ui_next/src/components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.jsx +++ b/awx/ui_next/src/components/ExecutionEnvironmentDetail/ExecutionEnvironmentDetail.jsx @@ -21,6 +21,7 @@ function ExecutionEnvironmentDetail({ isDefaultEnvironment, virtualEnvironment, verifyMissingVirtualEnv, + helpText, }) { const label = isDefaultEnvironment ? t`Default Execution Environment` @@ -37,6 +38,7 @@ function ExecutionEnvironmentDetail({ {executionEnvironment.name} } + helpText={helpText} dataCy="execution-environment-detail" /> ); @@ -95,6 +97,7 @@ ExecutionEnvironmentDetail.propTypes = { isDefaultEnvironment: bool, virtualEnvironment: string, verifyMissingVirtualEnv: bool, + helpText: string, }; ExecutionEnvironmentDetail.defaultProps = { @@ -102,6 +105,7 @@ ExecutionEnvironmentDetail.defaultProps = { executionEnvironment: null, virtualEnvironment: '', verifyMissingVirtualEnv: true, + helpText: '', }; export default ExecutionEnvironmentDetail; diff --git a/awx/ui_next/src/components/JobList/JobListItem.jsx b/awx/ui_next/src/components/JobList/JobListItem.jsx index ade6f62c20..85d2ade5bd 100644 --- a/awx/ui_next/src/components/JobList/JobListItem.jsx +++ b/awx/ui_next/src/components/JobList/JobListItem.jsx @@ -14,6 +14,7 @@ import ChipGroup from '../ChipGroup'; import CredentialChip from '../CredentialChip'; import ExecutionEnvironmentDetail from '../ExecutionEnvironmentDetail'; import { formatDateString } from '../../util/dates'; +import { isJobRunning } from '../../util/jobs'; import { JOB_TYPE_URL_SEGMENTS } from '../../constants'; import JobCancelButton from '../JobCancelButton'; @@ -32,7 +33,7 @@ function JobListItem({ const jobTypes = { project_update: t`Source Control Update`, inventory_update: t`Inventory Sync`, - job: t`Playbook Run`, + job: job.job_type === 'check' ? t`Playbook Check` : t`Playbook Run`, ad_hoc_command: t`Command`, system_job: t`Management Job`, workflow_job: t`Workflow Job`, @@ -202,10 +203,12 @@ function JobListItem({ dataCy={`job-${job.id}-project`} /> )} - + {job.type !== 'workflow_job' && !isJobRunning(job.status) && ( + + )} {credentials && credentials.length > 0 && ( - + {job.type !== 'workflow_job' && !isJobRunning(job.status) && ( + + )} {instanceGroup && !instanceGroup?.is_container_group && ( ; } - return ( @@ -212,7 +211,10 @@ function JobTemplateDetail({ template }) { )} diff --git a/awx/ui_next/src/screens/Template/shared/data.job_template.json b/awx/ui_next/src/screens/Template/shared/data.job_template.json index fa516d46db..4d4eb77af1 100644 --- a/awx/ui_next/src/screens/Template/shared/data.job_template.json +++ b/awx/ui_next/src/screens/Template/shared/data.job_template.json @@ -139,6 +139,12 @@ "name": "Default EE", "description": "", "image": "quay.io/ansible/awx-ee" + }, + "resolved_environment": { + "id": 1, + "name": "Default EE", + "description": "", + "image": "quay.io/ansible/awx-ee" } }, "created": "2019-09-30T16:18:34.564820Z", From 7a455d08d717df4a6b903945d38231cf29072893 Mon Sep 17 00:00:00 2001 From: mabashian Date: Fri, 11 Jun 2021 11:31:54 -0400 Subject: [PATCH 16/28] Change Options to Enabled Options in various details views --- .../PromptInventorySourceDetail.jsx | 42 +++++++--- .../PromptInventorySourceDetail.test.jsx | 12 +-- .../PromptDetail/PromptJobTemplateDetail.jsx | 44 +++++++--- .../PromptJobTemplateDetail.test.jsx | 11 +-- .../PromptDetail/PromptProjectDetail.jsx | 41 +++++++--- .../PromptDetail/PromptProjectDetail.test.jsx | 15 ++-- .../PromptWFJobTemplateDetail.jsx | 28 +++++-- .../PromptWFJobTemplateDetail.test.jsx | 6 +- .../CredentialDetail/CredentialDetail.jsx | 37 ++++++--- .../CredentialDetail.test.jsx | 2 +- .../InventorySourceDetail.jsx | 80 +++++-------------- .../InventorySourceDetail.test.jsx | 6 +- .../Project/ProjectDetail/ProjectDetail.jsx | 41 +++++++--- .../ProjectDetail/ProjectDetail.test.jsx | 15 ++-- .../JobTemplateDetail/JobTemplateDetail.jsx | 25 ++++-- .../WorkflowJobTemplateDetail.jsx | 10 +-- 16 files changed, 249 insertions(+), 166 deletions(-) diff --git a/awx/ui_next/src/components/PromptDetail/PromptInventorySourceDetail.jsx b/awx/ui_next/src/components/PromptDetail/PromptInventorySourceDetail.jsx index 0dd41a5066..3780de727a 100644 --- a/awx/ui_next/src/components/PromptDetail/PromptInventorySourceDetail.jsx +++ b/awx/ui_next/src/components/PromptDetail/PromptInventorySourceDetail.jsx @@ -1,9 +1,13 @@ import React from 'react'; - import { t } from '@lingui/macro'; import { Link } from 'react-router-dom'; - -import { Chip, List, ListItem } from '@patternfly/react-core'; +import { + Chip, + TextList, + TextListItem, + TextListVariants, + TextListItemVariants, +} from '@patternfly/react-core'; import { Detail, DeletedDetail } from '../DetailList'; import { VariablesDetail } from '../CodeEditor'; import CredentialChip from '../CredentialChip'; @@ -44,14 +48,28 @@ function PromptInventorySourceDetail({ resource }) { update_on_project_update ) { optionsList = ( - - {overwrite && {t`Overwrite`}} - {overwrite_vars && {t`Overwrite Variables`}} - {update_on_launch && {t`Update on Launch`}} - {update_on_project_update && ( - {t`Update on Project Update`} + + {overwrite && ( + + {t`Overwrite local groups and hosts from remote inventory source`} + )} - + {overwrite_vars && ( + + {t`Overwrite local variables from remote inventory source`} + + )} + {update_on_launch && ( + + {t`Update on launch`} + + )} + {update_on_project_update && ( + + {t`Update on project update`} + + )} + ); } @@ -162,7 +180,9 @@ function PromptInventorySourceDetail({ resource }) { } /> )} - {optionsList && } + {optionsList && ( + + )} {source_vars && ( { ); expect( wrapper - .find('Detail[label="Options"]') + .find('Detail[label="Enabled Options"]') .containsAllMatchingElements([ -
  • Overwrite
  • , -
  • Overwrite Variables
  • , -
  • Update on Launch
  • , -
  • Update on Project Update
  • , +
  • + Overwrite local groups and hosts from remote inventory source +
  • , +
  • Overwrite local variables from remote inventory source
  • , +
  • Update on launch
  • , +
  • Update on project update
  • , ]) ).toEqual(true); }); diff --git a/awx/ui_next/src/components/PromptDetail/PromptJobTemplateDetail.jsx b/awx/ui_next/src/components/PromptDetail/PromptJobTemplateDetail.jsx index 73fdb3a48c..00b2cf93c9 100644 --- a/awx/ui_next/src/components/PromptDetail/PromptJobTemplateDetail.jsx +++ b/awx/ui_next/src/components/PromptDetail/PromptJobTemplateDetail.jsx @@ -1,9 +1,13 @@ import React from 'react'; - import { t } from '@lingui/macro'; import { Link } from 'react-router-dom'; - -import { Chip, List, ListItem } from '@patternfly/react-core'; +import { + Chip, + TextList, + TextListItem, + TextListVariants, + TextListItemVariants, +} from '@patternfly/react-core'; import CredentialChip from '../CredentialChip'; import ChipGroup from '../ChipGroup'; import Sparkline from '../Sparkline'; @@ -51,19 +55,37 @@ function PromptJobTemplateDetail({ resource }) { become_enabled || host_config_key || allow_simultaneous || - use_fact_cache + use_fact_cache || + webhook_service ) { optionsList = ( - + {become_enabled && ( - {t`Enable Privilege Escalation`} + + {t`Privilege Escalation`} + )} {host_config_key && ( - {t`Allow Provisioning Callbacks`} + + {t`Provisioning Callbacks`} + )} - {allow_simultaneous && {t`Enable Concurrent Jobs`}} - {use_fact_cache && {t`Use Fact Storage`}} - + {allow_simultaneous && ( + + {t`Concurrent Jobs`} + + )} + {use_fact_cache && ( + + {t`Fact Storage`} + + )} + {webhook_service && ( + + {t`Webhooks`} + + )} + ); } @@ -164,7 +186,7 @@ function PromptJobTemplateDetail({ resource }) { } /> )} - {optionsList && } + {optionsList && } {summary_fields?.credentials?.length > 0 && ( { ).toEqual(true); expect( wrapper - .find('Detail[label="Options"]') + .find('Detail[label="Enabled Options"]') .containsAllMatchingElements([ -
  • Enable Privilege Escalation
  • , -
  • Allow Provisioning Callbacks
  • , -
  • Enable Concurrent Jobs
  • , -
  • Use Fact Storage
  • , +
  • Privilege Escalation
  • , +
  • Provisioning Callbacks
  • , +
  • Concurrent Jobs
  • , +
  • Fact Storage
  • , +
  • Webhooks
  • , ]) ).toEqual(true); expect(wrapper.find('VariablesDetail').prop('value')).toEqual( diff --git a/awx/ui_next/src/components/PromptDetail/PromptProjectDetail.jsx b/awx/ui_next/src/components/PromptDetail/PromptProjectDetail.jsx index dd2fe8b5a9..a779e026dc 100644 --- a/awx/ui_next/src/components/PromptDetail/PromptProjectDetail.jsx +++ b/awx/ui_next/src/components/PromptDetail/PromptProjectDetail.jsx @@ -1,10 +1,13 @@ import React from 'react'; - import { t } from '@lingui/macro'; -import { List, ListItem } from '@patternfly/react-core'; +import { + TextList, + TextListItem, + TextListVariants, + TextListItemVariants, +} from '@patternfly/react-core'; import { Link } from 'react-router-dom'; import { Config } from '../../contexts/Config'; - import { Detail, DeletedDetail } from '../DetailList'; import CredentialChip from '../CredentialChip'; import { toTitleCase } from '../../util/strings'; @@ -36,17 +39,33 @@ function PromptProjectDetail({ resource }) { allow_override ) { optionsList = ( - - {scm_clean && {t`Clean`}} - {scm_delete_on_update && {t`Delete on Update`}} + + {scm_clean && ( + {t`Discard local changes before syncing`} + )} + {scm_delete_on_update && ( + {t`Delete the project before syncing`} + )} {scm_track_submodules && ( - {t`Track submodules latest commit on branch`} + {t`Track submodules latest commit on branch`} )} {scm_update_on_launch && ( - {t`Update Revision on Launch`} + {t`Update revision on job launch`} )} - {allow_override && {t`Allow Branch Override`}} - + {allow_override && ( + {t`Allow branch override`} + )} + ); } @@ -90,7 +109,7 @@ function PromptProjectDetail({ resource }) { } /> )} - {optionsList && } + {optionsList && } { beforeAll(() => { wrapper = mountWithContexts( - , + , { context: { config }, } @@ -54,12 +56,13 @@ describe('PromptProjectDetail', () => { ); expect( wrapper - .find('Detail[label="Options"]') + .find('Detail[label="Enabled Options"]') .containsAllMatchingElements([ -
  • Clean
  • , -
  • Delete on Update
  • , -
  • Update Revision on Launch
  • , -
  • Allow Branch Override
  • , +
  • Discard local changes before syncing
  • , +
  • Delete the project before syncing
  • , +
  • Track submodules latest commit on branch
  • , +
  • Update revision on job launch
  • , +
  • Allow branch override
  • , ]) ).toEqual(true); }); diff --git a/awx/ui_next/src/components/PromptDetail/PromptWFJobTemplateDetail.jsx b/awx/ui_next/src/components/PromptDetail/PromptWFJobTemplateDetail.jsx index 935c187864..4a54fb31d8 100644 --- a/awx/ui_next/src/components/PromptDetail/PromptWFJobTemplateDetail.jsx +++ b/awx/ui_next/src/components/PromptDetail/PromptWFJobTemplateDetail.jsx @@ -1,9 +1,13 @@ import React from 'react'; - import { t } from '@lingui/macro'; import { Link } from 'react-router-dom'; - -import { Chip, List, ListItem } from '@patternfly/react-core'; +import { + Chip, + TextList, + TextListItem, + TextListVariants, + TextListItemVariants, +} from '@patternfly/react-core'; import CredentialChip from '../CredentialChip'; import ChipGroup from '../ChipGroup'; import { Detail } from '../DetailList'; @@ -26,10 +30,18 @@ function PromptWFJobTemplateDetail({ resource }) { let optionsList = ''; if (allow_simultaneous || webhook_service) { optionsList = ( - - {allow_simultaneous && {t`Enable Concurrent Jobs`}} - {webhook_service && {t`Enable Webhooks`}} - + + {allow_simultaneous && ( + + {t`Concurrent Jobs`} + + )} + {webhook_service && ( + + {t`Webhooks`} + + )} + ); } @@ -82,7 +94,7 @@ function PromptWFJobTemplateDetail({ resource }) { value={`${window.location.origin}${related.webhook_receiver}`} /> )} - {optionsList && } + {optionsList && } {summary_fields?.webhook_credential && ( { ); expect( wrapper - .find('Detail[label="Options"]') + .find('Detail[label="Enabled Options"]') .containsAllMatchingElements([ -
  • Enable Concurrent Jobs
  • , -
  • Enable Webhooks
  • , +
  • Concurrent Jobs
  • , +
  • Webhooks
  • , ]) ).toEqual(true); expect( diff --git a/awx/ui_next/src/screens/Credential/CredentialDetail/CredentialDetail.jsx b/awx/ui_next/src/screens/Credential/CredentialDetail/CredentialDetail.jsx index 9ca1c1ed0d..0d32e6ec44 100644 --- a/awx/ui_next/src/screens/Credential/CredentialDetail/CredentialDetail.jsx +++ b/awx/ui_next/src/screens/Credential/CredentialDetail/CredentialDetail.jsx @@ -1,9 +1,14 @@ import React, { Fragment, useEffect, useCallback } from 'react'; import { Link, useHistory } from 'react-router-dom'; - import { t } from '@lingui/macro'; import styled from 'styled-components'; -import { Button, List, ListItem } from '@patternfly/react-core'; +import { + Button, + TextList, + TextListItem, + TextListVariants, + TextListItemVariants, +} from '@patternfly/react-core'; import AlertModal from '../../../components/AlertModal'; import { CardBody, CardActionsRow } from '../../../components/Card'; import ContentError from '../../../components/ContentError'; @@ -134,15 +139,7 @@ function CredentialDetail({ credential }) { } if (type === 'boolean') { - return ( - {inputs[id] && {label}}} - /> - ); + return null; } if (inputs[id] === '$encrypted$') { @@ -189,6 +186,10 @@ function CredentialDetail({ credential }) { credential ); + const enabledBooleanFields = fields.filter( + ({ id, type }) => type === 'boolean' && inputs[id] + ); + if (hasContentLoading) { return ; } @@ -255,6 +256,20 @@ function CredentialDetail({ credential }) { date={modified} user={modified_by} /> + {enabledBooleanFields.length > 0 && ( + + {enabledBooleanFields.map(({ id, label }) => ( + + {label} + + ))} + + } + /> + )}
    {Object.keys(inputSources).length > 0 && ( diff --git a/awx/ui_next/src/screens/Credential/CredentialDetail/CredentialDetail.test.jsx b/awx/ui_next/src/screens/Credential/CredentialDetail/CredentialDetail.test.jsx index 25147bb148..14e48a3569 100644 --- a/awx/ui_next/src/screens/Credential/CredentialDetail/CredentialDetail.test.jsx +++ b/awx/ui_next/src/screens/Credential/CredentialDetail/CredentialDetail.test.jsx @@ -111,7 +111,7 @@ describe('', () => { 'Privilege Escalation Password', 'Prompt on launch' ); - expect(wrapper.find(`Detail[label="Options"] ListItem`).text()).toEqual( + expect(wrapper.find(`Detail[label="Enabled Options"] li`).text()).toEqual( 'Authorize' ); }); diff --git a/awx/ui_next/src/screens/Inventory/InventorySourceDetail/InventorySourceDetail.jsx b/awx/ui_next/src/screens/Inventory/InventorySourceDetail/InventorySourceDetail.jsx index 5f9213328b..bc617d7a68 100644 --- a/awx/ui_next/src/screens/Inventory/InventorySourceDetail/InventorySourceDetail.jsx +++ b/awx/ui_next/src/screens/Inventory/InventorySourceDetail/InventorySourceDetail.jsx @@ -1,9 +1,13 @@ import React, { useCallback, useEffect, useState } from 'react'; import { Link, useHistory } from 'react-router-dom'; - import { t } from '@lingui/macro'; - -import { Button, List, ListItem } from '@patternfly/react-core'; +import { + Button, + TextList, + TextListItem, + TextListVariants, + TextListItemVariants, +} from '@patternfly/react-core'; import AlertModal from '../../../components/AlertModal'; import { CardBody, CardActionsRow } from '../../../components/Card'; import { VariablesDetail } from '../../../components/CodeEditor'; @@ -19,7 +23,6 @@ import { UserDateDetail, } from '../../../components/DetailList'; import ErrorDetail from '../../../components/ErrorDetail'; -import Popover from '../../../components/Popover'; import useRequest from '../../../util/useRequest'; import { InventorySourcesAPI } from '../../../api'; import { relatedResourceDeleteRequests } from '../../../util/getRelatedResourceDeleteDetails'; @@ -112,71 +115,28 @@ function InventorySourceDetail({ inventorySource }) { update_on_project_update ) { optionsList = ( - + {overwrite && ( - - {t`Overwrite`} - - {t`If checked, any hosts and groups that were - previously present on the external source but are now removed - will be removed from the inventory. Hosts and groups - that were not managed by the inventory source will be promoted - to the next manually created group or if there is no manually - created group to promote them into, they will be left in the "all" - default group for the inventory.`} -
    -
    - {t`When not checked, local child - hosts and groups not found on the external source will remain - untouched by the inventory update process.`} - - } - /> -
    + + {t`Overwrite local groups and hosts from remote inventory source`} + )} {overwrite_vars && ( - - {t`Overwrite variables`} - - {t`If checked, all variables for child groups - and hosts will be removed and replaced by those found - on the external source.`} -
    -
    - {t`When not checked, a merge will be performed, - combining local variables with those found on the - external source.`} - - } - /> -
    + + {t`Overwrite local variables from remote inventory source`} + )} {update_on_launch && ( - + {t`Update on launch`} - - + )} {update_on_project_update && ( - + {t`Update on project update`} - - + )} -
    + ); } @@ -242,7 +202,7 @@ function InventorySourceDetail({ inventorySource }) { /> )} {optionsList && ( - + )} {source_vars && ( { expect(wrapper.find('VariablesDetail').prop('value')).toEqual( '---\nfoo: bar' ); - wrapper.find('Detail[label="Options"] li').forEach(option => { + wrapper.find('Detail[label="Enabled Options"] li').forEach(option => { expect([ - 'Overwrite', - 'Overwrite variables', + 'Overwrite local groups and hosts from remote inventory source', + 'Overwrite local variables from remote inventory source', 'Update on launch', 'Update on project update', ]).toContain(option.text()); diff --git a/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx b/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx index 6e1bad5b24..0882456b5f 100644 --- a/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx +++ b/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx @@ -5,8 +5,10 @@ import styled from 'styled-components'; import { Button, ClipboardCopy, - List, - ListItem, + TextList, + TextListItem, + TextListVariants, + TextListItemVariants, Tooltip, } from '@patternfly/react-core'; import { Project } from '../../../types'; @@ -78,17 +80,33 @@ function ProjectDetail({ project }) { allow_override ) { optionsList = ( - - {scm_clean && {t`Clean`}} - {scm_delete_on_update && {t`Delete on Update`}} + + {scm_clean && ( + {t`Discard local changes before syncing`} + )} + {scm_delete_on_update && ( + {t`Delete the project before syncing`} + )} {scm_track_submodules && ( - {t`Track submodules latest commit on branch`} + {t`Track submodules latest commit on branch`} )} {scm_update_on_launch && ( - {t`Update Revision on Launch`} + {t`Update revision on job launch`} )} - {allow_override && {t`Allow Branch Override`}} - + {allow_override && ( + {t`Allow branch override`} + )} + ); } @@ -196,7 +214,6 @@ function ProjectDetail({ project }) { } /> )} - {optionsList && } - + {optionsList && ( + + )} {summary_fields.user_capabilities?.edit && ( diff --git a/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.test.jsx b/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.test.jsx index 34ef0d6beb..fa627ff09f 100644 --- a/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.test.jsx +++ b/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.test.jsx @@ -70,7 +70,7 @@ describe('', () => { scm_refspec: 'refs/remotes/*', scm_clean: true, scm_delete_on_update: true, - scm_track_submodules: false, + scm_track_submodules: true, credential: 100, status: 'successful', organization: 10, @@ -127,12 +127,13 @@ describe('', () => { ); expect( wrapper - .find('Detail[label="Options"]') + .find('Detail[label="Enabled Options"]') .containsAllMatchingElements([ -
  • Clean
  • , -
  • Delete on Update
  • , -
  • Update Revision on Launch
  • , -
  • Allow Branch Override
  • , +
  • Discard local changes before syncing
  • , +
  • Delete the project before syncing
  • , +
  • Track submodules latest commit on branch
  • , +
  • Update revision on job launch
  • , +
  • Allow branch override
  • , ]) ).toEqual(true); }); @@ -151,7 +152,7 @@ describe('', () => { const wrapper = mountWithContexts( ); - expect(wrapper.find('Detail[label="Options"]').length).toBe(0); + expect(wrapper.find('Detail[label="Enabled Options"]').length).toBe(0); }); test('should have proper number of delete detail requests', () => { diff --git a/awx/ui_next/src/screens/Template/JobTemplateDetail/JobTemplateDetail.jsx b/awx/ui_next/src/screens/Template/JobTemplateDetail/JobTemplateDetail.jsx index 914441a9d3..1e2df548c8 100644 --- a/awx/ui_next/src/screens/Template/JobTemplateDetail/JobTemplateDetail.jsx +++ b/awx/ui_next/src/screens/Template/JobTemplateDetail/JobTemplateDetail.jsx @@ -115,28 +115,37 @@ function JobTemplateDetail({ template }) { ); const generateCallBackUrl = `${window.location.origin + url}callback/`; const renderOptionsField = - become_enabled || host_config_key || allow_simultaneous || use_fact_cache; + become_enabled || + host_config_key || + allow_simultaneous || + use_fact_cache || + webhook_service; const renderOptions = ( {become_enabled && ( - {t`Enable Privilege Escalation`} + {t`Privilege Escalation`} )} {host_config_key && ( - {t`Allow Provisioning Callbacks`} + {t`Provisioning Callbacks`} )} {allow_simultaneous && ( - {t`Enable Concurrent Jobs`} + {t`Concurrent Jobs`} )} {use_fact_cache && ( - {t`Use Fact Storage`} + {t`Fact Storage`} + + )} + {webhook_service && ( + + {t`Webhooks`} )} @@ -258,9 +267,6 @@ function JobTemplateDetail({ template }) { } /> )} - {renderOptionsField && ( - - )} + {renderOptionsField && ( + + )} {summary_fields.credentials && summary_fields.credentials.length > 0 && ( {template.allow_simultaneous && ( - {t`- Enable Concurrent Jobs`} + {t`Concurrent Jobs`} )} {template.webhook_service && ( - {t`- Enable Webhooks`} + {t`Webhooks`} )} @@ -186,9 +186,6 @@ function WorkflowJobTemplateDetail({ template }) { } /> )} - {renderOptionsField && ( - - )} + {renderOptionsField && ( + + )} {summary_fields.labels?.results?.length > 0 && ( Date: Fri, 11 Jun 2021 11:17:38 -0400 Subject: [PATCH 17/28] Removes tooltip from user in page toolbar --- .../AppContainer/PageHeaderToolbar.jsx | 80 +++++++++---------- 1 file changed, 38 insertions(+), 42 deletions(-) diff --git a/awx/ui_next/src/components/AppContainer/PageHeaderToolbar.jsx b/awx/ui_next/src/components/AppContainer/PageHeaderToolbar.jsx index 2f57399dd6..c00721f7a8 100644 --- a/awx/ui_next/src/components/AppContainer/PageHeaderToolbar.jsx +++ b/awx/ui_next/src/components/AppContainer/PageHeaderToolbar.jsx @@ -127,48 +127,44 @@ function PageHeaderToolbar({ ]} /> - {t`User`}}> - - - - {loggedInUser && ( - - {loggedInUser.username} - - )} - - } - dropdownItems={[ - - {t`User Details`} - , - - {t`Logout`} - , - ]} - /> - - + + + + {loggedInUser && ( + + {loggedInUser.username} + + )} + + } + dropdownItems={[ + + {t`User Details`} + , + + {t`Logout`} + , + ]} + /> + ); From 1c70773cc22255d89bc10d7e8020cceef146b133 Mon Sep 17 00:00:00 2001 From: Alex Corey Date: Mon, 14 Jun 2021 12:09:21 -0400 Subject: [PATCH 18/28] shows user type fields only for sys admins --- .../src/screens/User/shared/UserForm.jsx | 37 ++++++++++--------- .../src/screens/User/shared/UserForm.test.jsx | 21 +++++++++++ 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/awx/ui_next/src/screens/User/shared/UserForm.jsx b/awx/ui_next/src/screens/User/shared/UserForm.jsx index 002cb5c9d2..60ffd35d36 100644 --- a/awx/ui_next/src/screens/User/shared/UserForm.jsx +++ b/awx/ui_next/src/screens/User/shared/UserForm.jsx @@ -4,6 +4,7 @@ import PropTypes from 'prop-types'; import { t } from '@lingui/macro'; import { Formik, useField, useFormikContext } from 'formik'; import { Form, FormGroup } from '@patternfly/react-core'; +import { useConfig } from '../../../contexts/Config'; import AnsibleSelect from '../../../components/AnsibleSelect'; import FormActionGroup from '../../../components/FormActionGroup/FormActionGroup'; import FormField, { @@ -16,7 +17,7 @@ import { FormColumnLayout } from '../../../components/FormLayout'; function UserFormFields({ user }) { const { setFieldValue, setFieldTouched } = useFormikContext(); - + const { me = {} } = useConfig(); const ldapUser = user.ldap_dn; const socialAuthUser = user.auth?.length > 0; const externalAccount = user.external_account; @@ -119,22 +120,24 @@ function UserFormFields({ user }) { validate={required(t`Select a value for this field`)} /> )} - - - + {me.is_superuser && ( + + + + )} ); } diff --git a/awx/ui_next/src/screens/User/shared/UserForm.test.jsx b/awx/ui_next/src/screens/User/shared/UserForm.test.jsx index d2bb6e3764..69c777bc5b 100644 --- a/awx/ui_next/src/screens/User/shared/UserForm.test.jsx +++ b/awx/ui_next/src/screens/User/shared/UserForm.test.jsx @@ -230,4 +230,25 @@ describe('', () => { wrapper.find('button[aria-label="Cancel"]').invoke('onClick')(); expect(handleCancel).toBeCalled(); }); + + test('should not show user type field', async () => { + const handleCancel = jest.fn(); + await act(async () => { + wrapper = mountWithContexts( + , + { + context: { + config: { + me: { is_superuser: false }, + }, + }, + } + ); + }); + expect(wrapper.find('FormGroup[label="User Type"]')).toHaveLength(0); + }); }); From 8d99f79de446e2776307448d2df19f82e88913c5 Mon Sep 17 00:00:00 2001 From: nixocio Date: Mon, 14 Jun 2021 13:38:08 -0400 Subject: [PATCH 19/28] Hide Edit button for non system admin Hide Edit button for non system admin See: https://github.com/ansible/tower/issues/5032 --- .../SubscriptionDetail/SubscriptionDetail.jsx | 22 ++++++++++--------- .../SubscriptionDetail.test.jsx | 8 +++++++ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/awx/ui_next/src/screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.jsx b/awx/ui_next/src/screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.jsx index 3508f32138..a8fed74240 100644 --- a/awx/ui_next/src/screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.jsx +++ b/awx/ui_next/src/screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.jsx @@ -23,7 +23,7 @@ import { } from '../../../../util/dates'; function SubscriptionDetail() { - const { license_info, version } = useConfig(); + const { me = {}, license_info, version } = useConfig(); const baseURL = '/settings/subscription'; const tabsArray = [ { @@ -164,15 +164,17 @@ function SubscriptionDetail() { contact us. - - - + {me.is_superuser && ( + + + + )}
    ); diff --git a/awx/ui_next/src/screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.test.jsx b/awx/ui_next/src/screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.test.jsx index 3b79c35db8..fffea57a4f 100644 --- a/awx/ui_next/src/screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.test.jsx +++ b/awx/ui_next/src/screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.test.jsx @@ -71,6 +71,14 @@ describe('', () => { assertDetail('Hosts remaining', '1000'); assertDetail('Hosts automated', '12 since 3/2/2021, 7:43:48 PM'); + expect(wrapper.find('Button[aria-label="edit"]').length).toBe(0); + }); + + test('should render edit button for system admin', () => { + wrapper = mountWithContexts(, { + context: { ...config, me: { is_superuser: true } }, + }); + expect(wrapper.find('Button[aria-label="edit"]').length).toBe(1); }); }); From 736cd4df360bf5e25c9e6413f1e1f865ad6c0175 Mon Sep 17 00:00:00 2001 From: Alex Corey Date: Mon, 14 Jun 2021 17:15:55 -0400 Subject: [PATCH 20/28] refetches smart inventory after edit save --- .../src/screens/Inventory/SmartInventory.jsx | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/awx/ui_next/src/screens/Inventory/SmartInventory.jsx b/awx/ui_next/src/screens/Inventory/SmartInventory.jsx index d490652138..b198e744d4 100644 --- a/awx/ui_next/src/screens/Inventory/SmartInventory.jsx +++ b/awx/ui_next/src/screens/Inventory/SmartInventory.jsx @@ -28,25 +28,22 @@ function SmartInventory({ setBreadcrumb }) { const match = useRouteMatch('/inventories/smart_inventory/:id'); const { - result: { inventory }, + result: inventory, error: contentError, isLoading: hasContentLoading, request: fetchInventory, } = useRequest( useCallback(async () => { const { data } = await InventoriesAPI.readDetail(match.params.id); - return { - inventory: data, - }; + return data; }, [match.params.id]), - { - inventory: null, - } + + null ); useEffect(() => { fetchInventory(); - }, [fetchInventory]); + }, [fetchInventory, location.pathname]); useEffect(() => { if (inventory) { From cf6b6d831ff0d3723851db3421a20d8fb478f113 Mon Sep 17 00:00:00 2001 From: Alex Corey Date: Mon, 14 Jun 2021 11:08:26 -0400 Subject: [PATCH 21/28] adds visualizer button to output toolbar --- .../WorkflowOutput/WorkflowOutputToolbar.jsx | 26 ++++++++++++++++--- .../WorkflowOutputToolbar.test.jsx | 10 +++++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/awx/ui_next/src/screens/Job/WorkflowOutput/WorkflowOutputToolbar.jsx b/awx/ui_next/src/screens/Job/WorkflowOutput/WorkflowOutputToolbar.jsx index bcfe7fcc6c..be396a6eb5 100644 --- a/awx/ui_next/src/screens/Job/WorkflowOutput/WorkflowOutputToolbar.jsx +++ b/awx/ui_next/src/screens/Job/WorkflowOutput/WorkflowOutputToolbar.jsx @@ -1,9 +1,14 @@ import React, { useContext } from 'react'; - +import { useHistory } from 'react-router-dom'; import { t } from '@lingui/macro'; import { shape } from 'prop-types'; import { Badge as PFBadge, Button, Tooltip } from '@patternfly/react-core'; -import { CompassIcon, WrenchIcon } from '@patternfly/react-icons'; + +import { + CompassIcon, + WrenchIcon, + ProjectDiagramIcon, +} from '@patternfly/react-icons'; import styled from 'styled-components'; import StatusIcon from '../../../components/StatusIcon'; import { @@ -58,11 +63,15 @@ const StatusIconWithMargin = styled(StatusIcon)` function WorkflowOutputToolbar({ job }) { const dispatch = useContext(WorkflowDispatchContext); - + const history = useHistory(); const { nodes, showLegend, showTools } = useContext(WorkflowStateContext); const totalNodes = nodes.reduce((n, node) => n + !node.isDeleted, 0) - 1; - + const navToWorkflow = () => { + history.push( + `/templates/workflow_job_template/${job.unified_job_template}/visualizer` + ); + }; return ( @@ -70,6 +79,15 @@ function WorkflowOutputToolbar({ job }) { {job.name} + + +
    {t`Total Nodes`}
    {totalNodes} diff --git a/awx/ui_next/src/screens/Job/WorkflowOutput/WorkflowOutputToolbar.test.jsx b/awx/ui_next/src/screens/Job/WorkflowOutput/WorkflowOutputToolbar.test.jsx index 6fbe4ff9eb..b597c35f0d 100644 --- a/awx/ui_next/src/screens/Job/WorkflowOutput/WorkflowOutputToolbar.test.jsx +++ b/awx/ui_next/src/screens/Job/WorkflowOutput/WorkflowOutputToolbar.test.jsx @@ -18,6 +18,9 @@ const workflowContext = { showTools: false, }; +function shouldFind(element) { + expect(wrapper.find(element)).toHaveLength(1); +} describe('WorkflowOutputToolbar', () => { beforeAll(() => { const nodes = [ @@ -45,6 +48,13 @@ describe('WorkflowOutputToolbar', () => { wrapper.unmount(); }); + test('should render correct toolbar item', () => { + shouldFind(`Button[ouiaId="edit-workflow"]`); + shouldFind('Button#workflow-output-toggle-legend'); + shouldFind('Badge'); + shouldFind('Button#workflow-output-toggle-tools'); + }); + test('Shows correct number of nodes', () => { // The start node (id=1) and deleted nodes (isDeleted=true) should be ignored expect(wrapper.find('Badge').text()).toBe('1'); From a1ded8db3fbd76604213d01e0d447f5ca072429a Mon Sep 17 00:00:00 2001 From: nixocio Date: Mon, 14 Jun 2021 12:42:02 -0400 Subject: [PATCH 22/28] Linkify credentials on JobTemplate and Organization Details Linkify credentials on JobTemplate and Organization Details. See: https://github.com/ansible/awx/issues/385 --- .../OrganizationDetail/OrganizationDetail.jsx | 12 +++++++----- .../Template/JobTemplateDetail/JobTemplateDetail.jsx | 4 +++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/awx/ui_next/src/screens/Organization/OrganizationDetail/OrganizationDetail.jsx b/awx/ui_next/src/screens/Organization/OrganizationDetail/OrganizationDetail.jsx index 647b441075..a2f62e851c 100644 --- a/awx/ui_next/src/screens/Organization/OrganizationDetail/OrganizationDetail.jsx +++ b/awx/ui_next/src/screens/Organization/OrganizationDetail/OrganizationDetail.jsx @@ -133,11 +133,13 @@ function OrganizationDetail({ organization }) { value={ {galaxy_credentials.map(credential => ( - + + + ))} } diff --git a/awx/ui_next/src/screens/Template/JobTemplateDetail/JobTemplateDetail.jsx b/awx/ui_next/src/screens/Template/JobTemplateDetail/JobTemplateDetail.jsx index 1e2df548c8..6fcc51714e 100644 --- a/awx/ui_next/src/screens/Template/JobTemplateDetail/JobTemplateDetail.jsx +++ b/awx/ui_next/src/screens/Template/JobTemplateDetail/JobTemplateDetail.jsx @@ -290,7 +290,9 @@ function JobTemplateDetail({ template }) { totalChips={summary_fields.credentials.length} > {summary_fields.credentials.map(c => ( - + + + ))} } From f9043864ce44cf92d1314f8d5a77196d57e98a0c Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Tue, 15 Jun 2021 11:02:32 -0400 Subject: [PATCH 23/28] Create make target for api linter tasks --- Makefile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Makefile b/Makefile index 9052b9a518..e495485b0f 100644 --- a/Makefile +++ b/Makefile @@ -288,6 +288,11 @@ swagger: reports check: black +api-lint: + BLACK_ARGS="--check" make black + flake8 awx + yamllint -s . + awx-link: [ -d "/awx_devel/awx.egg-info" ] || $(PYTHON) /awx_devel/setup.py egg_info_dev cp -f /tmp/awx.egg-link /var/lib/awx/venv/awx/lib/python$(PYTHON_VERSION)/site-packages/awx.egg-link From d671366cad6138b85dae6649dd78958c8db4b8d6 Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Wed, 16 Jun 2021 11:00:28 -0400 Subject: [PATCH 24/28] Add flake8 back to requirements_dev.txt --- requirements/requirements_dev.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements/requirements_dev.txt b/requirements/requirements_dev.txt index a1bae58598..4af0af3285 100644 --- a/requirements/requirements_dev.txt +++ b/requirements/requirements_dev.txt @@ -21,3 +21,4 @@ sdb remote-pdb gprof2dot atomicwrites==1.4.0 +flake8 From d1d3711feecda3883831cbf53e345066f612b36a Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Wed, 16 Jun 2021 11:30:45 -0400 Subject: [PATCH 25/28] Add yamllint to dev dependencies --- requirements/requirements_dev.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements/requirements_dev.txt b/requirements/requirements_dev.txt index 4af0af3285..6811a16fd3 100644 --- a/requirements/requirements_dev.txt +++ b/requirements/requirements_dev.txt @@ -22,3 +22,4 @@ remote-pdb gprof2dot atomicwrites==1.4.0 flake8 +yamllint From 85bb4e976f02112592bc9b36cedb43005517dd83 Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Mon, 14 Jun 2021 14:17:55 -0400 Subject: [PATCH 26/28] Execution environments are meaningless for workflows so, remove them from the API endpoints for workflows. Also, tear out the WFJT.execution_environment step in the resolver. If we want that to be a thing, it ought to be a .default_environment instead. --- awx/api/serializers.py | 17 +++++++++++------ awx/main/models/mixins.py | 7 ------- awx/main/models/workflow.py | 3 +++ 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 1f0f127fc9..12799e0d94 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -729,11 +729,12 @@ class UnifiedJobTemplateSerializer(BaseSerializer): if self.is_detail_view: resolved_ee = obj.resolve_execution_environment() - summary_fields['resolved_environment'] = { - field: getattr(resolved_ee, field, None) - for field in SUMMARIZABLE_FK_FIELDS['execution_environment'] - if getattr(resolved_ee, field, None) is not None - } + if resolved_ee is not None: + summary_fields['resolved_environment'] = { + field: getattr(resolved_ee, field, None) + for field in SUMMARIZABLE_FK_FIELDS['execution_environment'] + if getattr(resolved_ee, field, None) is not None + } return summary_fields @@ -3425,6 +3426,7 @@ class WorkflowJobTemplateSerializer(JobTemplateMixin, LabelsListMixin, UnifiedJo 'ask_limit_on_launch', 'webhook_service', 'webhook_credential', + '-execution_environment', ) def get_related(self, obj): @@ -3451,6 +3453,7 @@ class WorkflowJobTemplateSerializer(JobTemplateMixin, LabelsListMixin, UnifiedJo survey_spec=self.reverse('api:workflow_job_template_survey_spec', kwargs={'pk': obj.pk}), copy=self.reverse('api:workflow_job_template_copy', kwargs={'pk': obj.pk}), ) + res.pop('execution_environment', None) # EEs aren't meaningful for workflows if obj.organization: res['organization'] = self.reverse('api:organization_detail', kwargs={'pk': obj.organization.pk}) if obj.webhook_credential_id: @@ -3502,6 +3505,7 @@ class WorkflowJobSerializer(LabelsListMixin, UnifiedJobSerializer): 'allow_simultaneous', 'job_template', 'is_sliced_job', + '-execution_environment', '-execution_node', '-event_processing_finished', '-controller_node', @@ -3515,6 +3519,7 @@ class WorkflowJobSerializer(LabelsListMixin, UnifiedJobSerializer): def get_related(self, obj): res = super(WorkflowJobSerializer, self).get_related(obj) + res.pop('execution_environment', None) # EEs aren't meaningful for workflows if obj.workflow_job_template: res['workflow_job_template'] = self.reverse('api:workflow_job_template_detail', kwargs={'pk': obj.workflow_job_template.pk}) res['notifications'] = self.reverse('api:workflow_job_notifications_list', kwargs={'pk': obj.pk}) @@ -3539,7 +3544,7 @@ class WorkflowJobSerializer(LabelsListMixin, UnifiedJobSerializer): class WorkflowJobListSerializer(WorkflowJobSerializer, UnifiedJobListSerializer): class Meta: - fields = ('*', '-execution_node', '-controller_node') + fields = ('*', '-execution_environment', '-execution_node', '-controller_node') class WorkflowJobCancelSerializer(WorkflowJobSerializer): diff --git a/awx/main/models/mixins.py b/awx/main/models/mixins.py index 7f929c757d..45a3cae885 100644 --- a/awx/main/models/mixins.py +++ b/awx/main/models/mixins.py @@ -471,13 +471,6 @@ class ExecutionEnvironmentMixin(models.Model): template = getattr(self, 'unified_job_template', None) if template is not None and template.execution_environment is not None: return template.execution_environment - wf_node = getattr(self, 'unified_job_node', None) - while wf_node is not None: - wf_template = wf_node.workflow_job.workflow_job_template - # NOTE: sliced workflow_jobs have a null workflow_job_template - if wf_template and wf_template.execution_environment is not None: - return wf_template.execution_environment - wf_node = getattr(wf_node.workflow_job, 'unified_job_node', None) if getattr(self, 'project_id', None) and self.project.default_environment is not None: return self.project.default_environment if getattr(self, 'organization_id', None) and self.organization.default_environment is not None: diff --git a/awx/main/models/workflow.py b/awx/main/models/workflow.py index 463a98f805..b254ee41c8 100644 --- a/awx/main/models/workflow.py +++ b/awx/main/models/workflow.py @@ -595,6 +595,9 @@ class WorkflowJobTemplate(UnifiedJobTemplate, WorkflowJobOptions, SurveyJobTempl def _get_related_jobs(self): return WorkflowJob.objects.filter(workflow_job_template=self) + def resolve_execution_environment(self): + return None # EEs are not meaningful for workflows + class WorkflowJob(UnifiedJob, WorkflowJobOptions, SurveyJobMixin, JobNotificationMixin, WebhookMixin): class Meta: From 6c1ba0323523d1045e6fc7e49d7660fd09c32fc9 Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Mon, 14 Jun 2021 15:03:01 -0400 Subject: [PATCH 27/28] Remove EE from the workflow job template collection module --- awx_collection/plugins/modules/workflow_job_template.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/awx_collection/plugins/modules/workflow_job_template.py b/awx_collection/plugins/modules/workflow_job_template.py index 8a4c5d75ec..7fc6b66cca 100644 --- a/awx_collection/plugins/modules/workflow_job_template.py +++ b/awx_collection/plugins/modules/workflow_job_template.py @@ -47,10 +47,6 @@ options: description: - Variables which will be made available to jobs ran inside the workflow. type: dict - execution_environment: - description: - - Execution Environment to use for the WFJT. - type: str organization: description: - Organization the workflow job template exists in. @@ -666,7 +662,6 @@ def main(): description=dict(), extra_vars=dict(type='dict'), organization=dict(), - execution_environment=dict(), survey_spec=dict(type='dict', aliases=['survey']), survey_enabled=dict(type='bool'), allow_simultaneous=dict(type='bool'), @@ -713,10 +708,6 @@ def main(): organization_id = module.resolve_name_to_id('organizations', organization) search_fields['organization'] = new_fields['organization'] = organization_id - ee = module.params.get('execution_environment') - if ee: - new_fields['execution_environment'] = module.resolve_name_to_id('execution_environments', ee) - # Attempt to look up an existing item based on the provided data existing_item = module.get_one('workflow_job_templates', name_or_id=name, **{'data': search_fields}) From 03e73156ea77c042f285da38351e55c4242c2599 Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Wed, 16 Jun 2021 13:44:54 -0400 Subject: [PATCH 28/28] Allow inventory updates to run in container groups --- awx/main/models/inventory.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/awx/main/models/inventory.py b/awx/main/models/inventory.py index 585f1c6592..004e43401a 100644 --- a/awx/main/models/inventory.py +++ b/awx/main/models/inventory.py @@ -1225,6 +1225,10 @@ class InventoryUpdate(UnifiedJob, InventorySourceOptions, JobNotificationMixin, def is_container_group_task(self): return bool(self.instance_group and self.instance_group.is_container_group) + @property + def can_run_containerized(self): + return True + def _get_parent_field_name(self): return 'inventory_source'