diff --git a/awx/ui_next/src/components/JobList/JobList.jsx b/awx/ui_next/src/components/JobList/JobList.jsx index 500b3272e4..0c2ab61a7c 100644 --- a/awx/ui_next/src/components/JobList/JobList.jsx +++ b/awx/ui_next/src/components/JobList/JobList.jsx @@ -234,7 +234,7 @@ function JobList({ i18n, defaultParams, showTypeColumn = false }) { }, ]} headerRow={ - + {i18n._(t`Name`)} {i18n._(t`Status`)} {showTypeColumn && {i18n._(t`Type`)}} diff --git a/awx/ui_next/src/components/JobList/JobListItem.jsx b/awx/ui_next/src/components/JobList/JobListItem.jsx index 3bc2345c75..cfc167f468 100644 --- a/awx/ui_next/src/components/JobList/JobListItem.jsx +++ b/awx/ui_next/src/components/JobList/JobListItem.jsx @@ -1,16 +1,48 @@ -import React from 'react'; +import React, { useState } from 'react'; import { Link } from 'react-router-dom'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; -import { Button } from '@patternfly/react-core'; -import { Tr, Td } from '@patternfly/react-table'; +import { Button, Chip } from '@patternfly/react-core'; +import { Tr, Td, ExpandableRowContent } from '@patternfly/react-table'; import { RocketIcon } from '@patternfly/react-icons'; import { ActionsTd, ActionItem } from '../PaginatedTable'; import LaunchButton from '../LaunchButton'; import StatusLabel from '../StatusLabel'; +import { DetailList, Detail } from '../DetailList'; +import ChipGroup from '../ChipGroup'; +import CredentialChip from '../CredentialChip'; import { formatDateString } from '../../util/dates'; import { JOB_TYPE_URL_SEGMENTS } from '../../constants'; +const getLaunchedByDetails = ({ summary_fields = {}, related = {} }) => { + const { + created_by: createdBy, + job_template: jobTemplate, + schedule, + } = summary_fields; + const { schedule: relatedSchedule } = related; + + if (!createdBy && !schedule) { + return {}; + } + + let link; + let value; + + if (createdBy) { + link = `/users/${createdBy.id}`; + value = createdBy.username; + } else if (relatedSchedule && jobTemplate) { + link = `/templates/job_template/${jobTemplate.id}/schedules/${schedule.id}`; + value = schedule.name; + } else { + link = null; + value = schedule.name; + } + + return { link, value }; +}; + function JobListItem({ i18n, job, @@ -20,6 +52,7 @@ function JobListItem({ showTypeColumn = false, }) { const labelId = `check-action-${job.id}`; + const [isExpanded, setIsExpanded] = useState(false); const jobTypes = { project_update: i18n._(t`Source Control Update`), @@ -30,57 +63,142 @@ function JobListItem({ workflow_job: i18n._(t`Workflow Job`), }; + const { value: launchedByValue, link: launchedByLink } = + getLaunchedByDetails(job) || {}; + const { credentials, inventory, labels } = job.summary_fields; + return ( - - - - - - - {job.id} — {job.name} - - - - - - {job.status && } - - {showTypeColumn && ( - {jobTypes[job.type]} - )} - {formatDateString(job.started)} - - {job.finished ? formatDateString(job.finished) : ''} - - - - - {({ handleRelaunch }) => ( - - )} - - - - + <> + + setIsExpanded(!isExpanded), + }} + /> + + + + + + {job.id} — {job.name} + + + + + + {job.status && } + + {showTypeColumn && ( + {jobTypes[job.type]} + )} + + {formatDateString(job.started)} + + + {job.finished ? formatDateString(job.finished) : ''} + + + + + {({ handleRelaunch }) => ( + + )} + + + + + + + + + + + + {launchedByValue} + ) : ( + launchedByValue + ) + } + /> + {credentials && credentials.length > 0 && ( + + {credentials.map(c => ( + + ))} + + } + /> + )} + {labels && labels.count > 0 && ( + + {labels.results.map(l => ( + + {l.name} + + ))} + + } + /> + )} + {inventory && ( + + {inventory.name} + + } + /> + )} + + + + + ); } diff --git a/awx/ui_next/src/components/PaginatedTable/HeaderRow.jsx b/awx/ui_next/src/components/PaginatedTable/HeaderRow.jsx index d1914ffae8..b284104053 100644 --- a/awx/ui_next/src/components/PaginatedTable/HeaderRow.jsx +++ b/awx/ui_next/src/components/PaginatedTable/HeaderRow.jsx @@ -12,7 +12,7 @@ const Th = styled(PFTh)` --pf-c-table--cell--Overflow: initial; `; -export default function HeaderRow({ qsConfig, children }) { +export default function HeaderRow({ qsConfig, isExpandable, children }) { const location = useLocation(); const history = useHistory(); @@ -46,6 +46,7 @@ export default function HeaderRow({ qsConfig, children }) { return ( + {isExpandable && } {React.Children.map( children, diff --git a/awx/ui_next/src/screens/Job/JobDetail/JobDetail.jsx b/awx/ui_next/src/screens/Job/JobDetail/JobDetail.jsx index 9255b27af1..50841bf95f 100644 --- a/awx/ui_next/src/screens/Job/JobDetail/JobDetail.jsx +++ b/awx/ui_next/src/screens/Job/JobDetail/JobDetail.jsx @@ -360,3 +360,4 @@ JobDetail.propTypes = { }; export default withI18n()(JobDetail); +export { getLaunchedByDetails };