diff --git a/web/src/containers/build/BuildList.jsx b/web/src/containers/build/BuildList.jsx index b0a3bc3f53..2902985ad6 100644 --- a/web/src/containers/build/BuildList.jsx +++ b/web/src/containers/build/BuildList.jsx @@ -22,8 +22,15 @@ import { DataListItem, DataListItemRow, DataListItemCells, + Flex, + FlexItem, + Switch, } from '@patternfly/react-core' -import { OutlinedClockIcon } from '@patternfly/react-icons' +import { + AngleDownIcon, + AngleRightIcon, + OutlinedClockIcon +} from '@patternfly/react-icons' import 'moment-duration-format' import * as moment from 'moment' @@ -39,75 +46,175 @@ class BuildList extends React.Component { // page. Without this flag we might then even use this (with more // information) on the /builds page. - constructor() { - super() + constructor(props) { + super(props) + const { builds } = this.props + let retriedJobs = builds.filter((build) => { + return !build.final + }).map((build) => (build.job_name) + ).filter((build, idx, self) => { + return self.indexOf(build) === idx + }) + this.state = { - selectedBuildId: null, + visibleNonFinalBuilds: retriedJobs, + retriedJobs: retriedJobs, } } - handleSelectDataListItem = (buildId) => { - this.setState({ - selectedBuildId: buildId, + sortedBuilds = () => { + const { builds } = this.props + const { visibleNonFinalBuilds } = this.state + + return builds.sort((a, b) => { + // Group builds by job name, then order by decreasing start time; this will ensure retries are together + if (a.job_name === b.job_name) { + if (a.start_time < b.start_time) { + return 1 + } + if (a.start_time > b.start_time) { + return -1 + } + return 0 + } + if (a.job_name > b.job_name) { + return 1 + } else { + return -1 + } + }).filter((build) => { + if (build.final || visibleNonFinalBuilds.indexOf(build.job_name) >= 0) { + return true + } + else { + return false + } }) } - render() { - const { builds, tenant } = this.props - const { selectedBuildId } = this.state + handleFinalSwitch = isChecked => { + const { retriedJobs } = this.state + this.setState({ visibleNonFinalBuilds: (isChecked ? retriedJobs : []) }) + } + + handleToggleVisibleNonFinalBuilds = (jobName) => { + const { visibleNonFinalBuilds } = this.state + const index = visibleNonFinalBuilds.indexOf(jobName) + const newVisible = + index >= 0 ? [...visibleNonFinalBuilds.slice(0, index), ...visibleNonFinalBuilds.slice(index + 1, visibleNonFinalBuilds.length)] : [...visibleNonFinalBuilds, jobName] + this.setState({ + visibleNonFinalBuilds: newVisible, + }) + } + + renderRetriesButton = (build, hasRetries) => { + const { visibleNonFinalBuilds } = this.state + if (!build.final || !hasRetries) { + return + {/* Hide the icon to maintain alignment between final and non-final elements */} + + + } + const isExpanded = (visibleNonFinalBuilds.indexOf(build.job_name) >= 0) + const RetryIcon = + isExpanded + ? AngleDownIcon + : AngleRightIcon + const retryAltText = + isExpanded + ? 'Hide retries for this job' + : 'Show retries for this job' + // TODO either replace this with an ExpandableSection (but this breaks the layout) or figure out CSS animations for the icon. return ( - - {builds.map((build) => ( - - - - - - {build.job_name} - {!build.voting && ' (non-voting)'} - - , - - } - value={moment - .duration(build.duration, 'seconds') - .format('h [hr] m [min] s [sec]')} - /> - , - - - , - ]} - /> - - - - ))} - + + { this.handleToggleVisibleNonFinalBuilds(build.job_name) }} + title={retryAltText} + style={{ cursor: 'pointer' }} /> + + ) + } + + render() { + const { tenant } = this.props + const { visibleNonFinalBuilds, retriedJobs } = this.state + + let retrySwitch = retriedJobs.length > 0 ? + + Show retries   + + : + <> + + const sortedBuilds = this.sortedBuilds() + + return ( + + {retrySwitch} + + + {sortedBuilds.map((build) => { + function linkWrap(cell) { + return ( + {cell} + ) + } + return ( + + + = 0), + + {linkWrap( + {build.job_name} + {!build.voting && ' (non-voting)'} + )} + , + + {linkWrap(} + value={moment + .duration(build.duration, 'seconds') + .format('h [hr] m [min] s [sec]')} + />)} + , + + {linkWrap()} + , + ]} + /> + + + ) + })} + + + ) } } diff --git a/web/src/index.css b/web/src/index.css index 806cbf2392..990317ae17 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -51,6 +51,11 @@ a.refresh { font-weight: bold; } +/* Remove ugly outline when a Switch is selected */ +.pf-c-switch { + --pf-c-switch__input--focus__toggle--OutlineWidth: 0; +} + /* Keep the normal font-size for compact tables */ .zuul-build-table td { font-size: var(--pf-global--FontSize--md);