add expandable rows to JobList

This commit is contained in:
Keith Grant
2020-12-23 14:36:54 -08:00
parent da16785201
commit 8bde6060c4
4 changed files with 175 additions and 55 deletions

View File

@@ -234,7 +234,7 @@ function JobList({ i18n, defaultParams, showTypeColumn = false }) {
}, },
]} ]}
headerRow={ headerRow={
<HeaderRow qsConfig={qsConfig}> <HeaderRow qsConfig={qsConfig} isExpandable>
<HeaderCell sortKey="name">{i18n._(t`Name`)}</HeaderCell> <HeaderCell sortKey="name">{i18n._(t`Name`)}</HeaderCell>
<HeaderCell sortKey="status">{i18n._(t`Status`)}</HeaderCell> <HeaderCell sortKey="status">{i18n._(t`Status`)}</HeaderCell>
{showTypeColumn && <HeaderCell>{i18n._(t`Type`)}</HeaderCell>} {showTypeColumn && <HeaderCell>{i18n._(t`Type`)}</HeaderCell>}

View File

@@ -1,16 +1,48 @@
import React from 'react'; import React, { useState } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Button } from '@patternfly/react-core'; import { Button, Chip } from '@patternfly/react-core';
import { Tr, Td } from '@patternfly/react-table'; import { Tr, Td, ExpandableRowContent } from '@patternfly/react-table';
import { RocketIcon } from '@patternfly/react-icons'; import { RocketIcon } from '@patternfly/react-icons';
import { ActionsTd, ActionItem } from '../PaginatedTable'; import { ActionsTd, ActionItem } from '../PaginatedTable';
import LaunchButton from '../LaunchButton'; import LaunchButton from '../LaunchButton';
import StatusLabel from '../StatusLabel'; import StatusLabel from '../StatusLabel';
import { DetailList, Detail } from '../DetailList';
import ChipGroup from '../ChipGroup';
import CredentialChip from '../CredentialChip';
import { formatDateString } from '../../util/dates'; import { formatDateString } from '../../util/dates';
import { JOB_TYPE_URL_SEGMENTS } from '../../constants'; 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({ function JobListItem({
i18n, i18n,
job, job,
@@ -20,6 +52,7 @@ function JobListItem({
showTypeColumn = false, showTypeColumn = false,
}) { }) {
const labelId = `check-action-${job.id}`; const labelId = `check-action-${job.id}`;
const [isExpanded, setIsExpanded] = useState(false);
const jobTypes = { const jobTypes = {
project_update: i18n._(t`Source Control Update`), project_update: i18n._(t`Source Control Update`),
@@ -30,57 +63,142 @@ function JobListItem({
workflow_job: i18n._(t`Workflow Job`), workflow_job: i18n._(t`Workflow Job`),
}; };
const { value: launchedByValue, link: launchedByLink } =
getLaunchedByDetails(job) || {};
const { credentials, inventory, labels } = job.summary_fields;
return ( return (
<Tr id={`job-row-${job.id}`}> <>
<Td <Tr id={`job-row-${job.id}`}>
select={{ <Td
rowIndex, expand={{
isSelected, rowIndex: job.id,
onSelect, isExpanded,
disable: false, onToggle: () => setIsExpanded(!isExpanded),
}} }}
/> />
<Td id={labelId} dataLabel={i18n._(t`Name`)}> <Td
<span> select={{
<Link to={`/jobs/${JOB_TYPE_URL_SEGMENTS[job.type]}/${job.id}`}> rowIndex,
<b> isSelected,
{job.id} &mdash; {job.name} onSelect,
</b> disable: false,
</Link> }}
</span> />
</Td> <Td id={labelId} dataLabel={i18n._(t`Name`)}>
<Td dataLabel={i18n._(t`Status`)}> <span>
{job.status && <StatusLabel status={job.status} />} <Link to={`/jobs/${JOB_TYPE_URL_SEGMENTS[job.type]}/${job.id}`}>
</Td> <b>
{showTypeColumn && ( {job.id} &mdash; {job.name}
<Td dataLabel={i18n._(t`Type`)}>{jobTypes[job.type]}</Td> </b>
)} </Link>
<Td dataLabel={i18n._(t`Start Time`)}>{formatDateString(job.started)}</Td> </span>
<Td dataLabel={i18n._(t`Finish Time`)}> </Td>
{job.finished ? formatDateString(job.finished) : ''} <Td dataLabel={i18n._(t`Status`)}>
</Td> {job.status && <StatusLabel status={job.status} />}
<ActionsTd dataLabel={i18n._(t`Actions`)}> </Td>
<ActionItem {showTypeColumn && (
visible={ <Td dataLabel={i18n._(t`Type`)}>{jobTypes[job.type]}</Td>
job.type !== 'system_job' && )}
job.summary_fields?.user_capabilities?.start <Td dataLabel={i18n._(t`Start Time`)}>
} {formatDateString(job.started)}
tooltip={i18n._(t`Relaunch Job`)} </Td>
> <Td dataLabel={i18n._(t`Finish Time`)}>
<LaunchButton resource={job}> {job.finished ? formatDateString(job.finished) : ''}
{({ handleRelaunch }) => ( </Td>
<Button <ActionsTd dataLabel={i18n._(t`Actions`)}>
variant="plain" <ActionItem
onClick={handleRelaunch} visible={
aria-label={i18n._(t`Relaunch`)} job.type !== 'system_job' &&
> job.summary_fields?.user_capabilities?.start
<RocketIcon /> }
</Button> tooltip={i18n._(t`Relaunch Job`)}
)} >
</LaunchButton> <LaunchButton resource={job}>
</ActionItem> {({ handleRelaunch }) => (
</ActionsTd> <Button
</Tr> variant="plain"
onClick={handleRelaunch}
aria-label={i18n._(t`Relaunch`)}
>
<RocketIcon />
</Button>
)}
</LaunchButton>
</ActionItem>
</ActionsTd>
</Tr>
<Tr isExpanded={isExpanded}>
<Td colSpan={2} />
<Td colSpan={showTypeColumn ? 5 : 4}>
<ExpandableRowContent>
<DetailList>
<Detail
label={i18n._(t`Started`)}
value={formatDateString(job.started)}
/>
<Detail
label={i18n._(t`Finished`)}
value={formatDateString(job.started)}
/>
<Detail
label={i18n._(t`Launched By`)}
value={
launchedByLink ? (
<Link to={`${launchedByLink}`}>{launchedByValue}</Link>
) : (
launchedByValue
)
}
/>
{credentials && credentials.length > 0 && (
<Detail
fullWidth
label={i18n._(t`Credentials`)}
value={
<ChipGroup numChips={5} totalChips={credentials.length}>
{credentials.map(c => (
<CredentialChip key={c.id} credential={c} isReadOnly />
))}
</ChipGroup>
}
/>
)}
{labels && labels.count > 0 && (
<Detail
label={i18n._(t`Labels`)}
value={
<ChipGroup numChips={5} totalChips={labels.results.length}>
{labels.results.map(l => (
<Chip key={l.id} isReadOnly>
{l.name}
</Chip>
))}
</ChipGroup>
}
/>
)}
{inventory && (
<Detail
label={i18n._(t`Inventory`)}
value={
<Link
to={
inventory.kind === 'smart'
? `/inventories/smart_inventory/${inventory.id}`
: `/inventories/inventory/${inventory.id}`
}
>
{inventory.name}
</Link>
}
/>
)}
</DetailList>
</ExpandableRowContent>
</Td>
</Tr>
</>
); );
} }

View File

@@ -12,7 +12,7 @@ const Th = styled(PFTh)`
--pf-c-table--cell--Overflow: initial; --pf-c-table--cell--Overflow: initial;
`; `;
export default function HeaderRow({ qsConfig, children }) { export default function HeaderRow({ qsConfig, isExpandable, children }) {
const location = useLocation(); const location = useLocation();
const history = useHistory(); const history = useHistory();
@@ -46,6 +46,7 @@ export default function HeaderRow({ qsConfig, children }) {
return ( return (
<Thead> <Thead>
<Tr> <Tr>
{isExpandable && <Th />}
<Th /> <Th />
{React.Children.map( {React.Children.map(
children, children,

View File

@@ -360,3 +360,4 @@ JobDetail.propTypes = {
}; };
export default withI18n()(JobDetail); export default withI18n()(JobDetail);
export { getLaunchedByDetails };