From 48a044cc689dda38cd2d061e51f0902781e90a1b Mon Sep 17 00:00:00 2001 From: Alex Corey Date: Thu, 19 Aug 2021 14:11:44 -0400 Subject: [PATCH] Adds source data to job list and job details view --- awx/ui/src/components/JobList/JobList.js | 29 +++++++++++++-- awx/ui/src/components/JobList/JobList.test.js | 15 ++++++++ awx/ui/src/components/JobList/JobListItem.js | 11 ++++++ .../components/JobList/JobListItem.test.js | 28 +++++++++++++++ awx/ui/src/screens/Job/Job.js | 21 +++++++++-- awx/ui/src/screens/Job/JobDetail/JobDetail.js | 35 +++++++++++------- .../screens/Job/JobDetail/JobDetail.test.js | 36 +++++++++++++++++++ 7 files changed, 158 insertions(+), 17 deletions(-) diff --git a/awx/ui/src/components/JobList/JobList.js b/awx/ui/src/components/JobList/JobList.js index 0547212d0d..d312113339 100644 --- a/awx/ui/src/components/JobList/JobList.js +++ b/awx/ui/src/components/JobList/JobList.js @@ -12,7 +12,7 @@ import useSelected from 'hooks/useSelected'; import useExpanded from 'hooks/useExpanded'; import { isJobRunning, getJobModel } from 'util/jobs'; import { getQSConfig, parseQueryString } from 'util/qs'; -import { UnifiedJobsAPI } from 'api'; +import { UnifiedJobsAPI, InventorySourcesAPI } from 'api'; import AlertModal from '../AlertModal'; import DatalistToolbar from '../DataListToolbar'; import ErrorDetail from '../ErrorDetail'; @@ -41,7 +41,13 @@ function JobList({ defaultParams, showTypeColumn = false }) { const { me } = useConfig(); const location = useLocation(); const { - result: { results, count, relatedSearchableKeys, searchableKeys }, + result: { + results, + count, + relatedSearchableKeys, + searchableKeys, + inventorySourceChoices, + }, error: contentError, isLoading, request: fetchJobs, @@ -49,13 +55,28 @@ function JobList({ defaultParams, showTypeColumn = false }) { useCallback( async () => { const params = parseQueryString(qsConfig, location.search); - const [response, actionsResponse] = await Promise.all([ + const [ + response, + actionsResponse, + { + data: { + actions: { + GET: { + source: { choices }, + }, + }, + }, + }, + ] = await Promise.all([ UnifiedJobsAPI.read({ ...params }), UnifiedJobsAPI.readOptions(), + InventorySourcesAPI.readOptions(), ]); + return { results: response.data.results, count: response.data.count, + inventorySourceChoices: choices, relatedSearchableKeys: ( actionsResponse?.data?.related_search_fields || [] ).map((val) => val.slice(0, -8)), @@ -69,6 +90,7 @@ function JobList({ defaultParams, showTypeColumn = false }) { { results: [], count: 0, + inventorySourceChoices: [], relatedSearchableKeys: [], searchableKeys: [], } @@ -264,6 +286,7 @@ function JobList({ defaultParams, showTypeColumn = false }) { renderRow={(job, index) => ( row.id === job.id)} onExpand={() => handleExpand(job)} diff --git a/awx/ui/src/components/JobList/JobList.test.js b/awx/ui/src/components/JobList/JobList.test.js index a3f158df6d..388592d5c3 100644 --- a/awx/ui/src/components/JobList/JobList.test.js +++ b/awx/ui/src/components/JobList/JobList.test.js @@ -8,6 +8,7 @@ import { SystemJobsAPI, UnifiedJobsAPI, WorkflowJobsAPI, + InventorySourcesAPI, } from 'api'; import { mountWithContexts, @@ -140,6 +141,20 @@ describe('', () => { related_search_fields: [], }, }); + InventorySourcesAPI.readOptions.mockResolvedValue({ + data: { + actions: { + GET: { + source: { + choices: [ + ['scm', 'Sourced from Project'], + ['file', 'File, Directory or Script'], + ], + }, + }, + }, + }, + }); debug = global.console.debug; // eslint-disable-line prefer-destructuring global.console.debug = () => {}; }); diff --git a/awx/ui/src/components/JobList/JobListItem.js b/awx/ui/src/components/JobList/JobListItem.js index e244c08ab9..10a9f2d5e2 100644 --- a/awx/ui/src/components/JobList/JobListItem.js +++ b/awx/ui/src/components/JobList/JobListItem.js @@ -28,6 +28,7 @@ function JobListItem({ onSelect, showTypeColumn = false, isSuperUser = false, + inventorySourceLabels, }) { const labelId = `check-action-${job.id}`; @@ -151,6 +152,16 @@ function JobListItem({ + {job.type === 'inventory_update' && + inventorySourceLabels.length > 0 && ( + + string === job.source ? label : null + )} + /> + )} {job_template && ( ', () => { expect(wrapper.find('LaunchButton').length).toBe(1); }); + test('should render souce data in expanded view', () => { + wrapper = mountWithContexts( + + + {}} + isSelected={false} + /> + +
+ ); + expect(wrapper.find('ExpandableRowContent')).toHaveLength(1); + expect( + wrapper.find('dd[data-cy="job-inventory-source-type-value"]').text() + ).toBe('Sourced from Project'); + }); test('launch button hidden from users without launch capabilities', () => { wrapper = mountWithContexts( diff --git a/awx/ui/src/screens/Job/Job.js b/awx/ui/src/screens/Job/Job.js index f92df64385..e13a935669 100644 --- a/awx/ui/src/screens/Job/Job.js +++ b/awx/ui/src/screens/Job/Job.js @@ -11,6 +11,7 @@ import { import { t } from '@lingui/macro'; import { CaretLeftIcon } from '@patternfly/react-icons'; import { Card, PageSection } from '@patternfly/react-core'; +import { InventorySourcesAPI } from 'api'; import ContentError from 'components/ContentError'; import ContentLoading from 'components/ContentLoading'; import RoutedTabs from 'components/RoutedTabs'; @@ -41,7 +42,12 @@ function Job({ setBreadcrumb }) { isLoading, error, request: fetchJob, - result: { jobDetail, eventRelatedSearchableKeys, eventSearchableKeys }, + result: { + jobDetail, + eventRelatedSearchableKeys, + eventSearchableKeys, + inventorySourceChoices, + }, } = useRequest( useCallback(async () => { let eventOptions = {}; @@ -63,9 +69,16 @@ function Job({ setBreadcrumb }) { jobDetailData.summary_fields.credentials = results; } + setBreadcrumb(jobDetailData); + let choices; + if (jobDetailData.type === 'inventory_update') { + choices = await InventorySourcesAPI.readOptions(); + } return { + inventorySourceChoices: + choices?.data?.actions?.GET?.source?.choices || [], jobDetail: jobDetailData, eventRelatedSearchableKeys: ( eventOptions?.related_search_fields || [] @@ -77,6 +90,7 @@ function Job({ setBreadcrumb }) { }, [id, type, setBreadcrumb]), { jobDetail: null, + inventorySourceChoices: [], eventRelatedSearchableKeys: [], eventSearchableKeys: [], } @@ -145,7 +159,10 @@ function Job({ setBreadcrumb }) { key={job.type === 'workflow_job' ? 'workflow-details' : 'details'} path="/jobs/:typeSegment/:id/details" > - + , {job.type === 'workflow_job' ? ( diff --git a/awx/ui/src/screens/Job/JobDetail/JobDetail.js b/awx/ui/src/screens/Job/JobDetail/JobDetail.js index 8aa83071b9..8653290035 100644 --- a/awx/ui/src/screens/Job/JobDetail/JobDetail.js +++ b/awx/ui/src/screens/Job/JobDetail/JobDetail.js @@ -50,7 +50,7 @@ const VERBOSITY = { 4: '4 (Connection Debug)', }; -function JobDetail({ job }) { +function JobDetail({ job, inventorySourceLabels }) { const { me } = useConfig(); const { created_by, @@ -203,17 +203,28 @@ function JobDetail({ job }) { /> )} {inventory_source && ( - - {inventory_source.name} - - } - /> + <> + + {inventory_source.name} + + } + /> + {inventorySourceLabels.length > 0 && ( + + string === job.source ? label : null + )} + /> + )} + )} {inventory_source && inventory_source.source === 'scm' && ( ', () => { assertDetail('Job Type', 'Run Command'); }); + test('should display source data', () => { + wrapper = mountWithContexts( + + ); + assertDetail('Source', 'Sourced from Project'); + }); + test('should show schedule that launched workflow job', async () => { wrapper = mountWithContexts(