Make helper functions available to other components
This moves some of the helper functions used to format and display various information of a QueueItem to a more generic file so they can be used by other components as well. In addition, the render methods for the ChangeLink and (QueueItem)Progressbar where changed to React Components and now use newer PF4 components for styling. Finally, this changes fixes a typo in the calculateQueueItemTimes function to make the fallback to null work. Change-Id: Ia7f3f214dfa1dbc041c1dcb821ff478980d81011
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
|
||||
import * as React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import * as moment from 'moment'
|
||||
import { ExternalLinkAltIcon } from '@patternfly/react-icons'
|
||||
|
||||
function removeHash() {
|
||||
@@ -111,11 +112,11 @@ function renderRefInfo(ref) {
|
||||
</>
|
||||
)
|
||||
const oldrev = ref.oldrev ? (
|
||||
<><br/><strong>Old</strong> {ref.oldrev}</>
|
||||
) : ( <></> )
|
||||
<><br /><strong>Old</strong> {ref.oldrev}</>
|
||||
) : (<></>)
|
||||
const newrev = ref.newrev ? (
|
||||
<><br/><strong>New</strong> {ref.newrev}</>
|
||||
) : ( <></> )
|
||||
<><br /><strong>New</strong> {ref.newrev}</>
|
||||
) : (<></>)
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -180,4 +181,25 @@ function setDarkMode(darkMode) {
|
||||
}
|
||||
}
|
||||
|
||||
export { IconProperty, removeHash, ExternalLink, buildExternalLink, buildExternalTableLink, describeRef, renderRefInfo, ConditionalWrapper, resolveDarkMode, setDarkMode }
|
||||
function formatTime(ms) {
|
||||
return moment.duration(ms).format({
|
||||
template: 'h [hr] m [min]',
|
||||
largest: 2,
|
||||
minValue: 1,
|
||||
usePlural: false,
|
||||
})
|
||||
}
|
||||
|
||||
export {
|
||||
buildExternalLink,
|
||||
buildExternalTableLink,
|
||||
ConditionalWrapper,
|
||||
describeRef,
|
||||
ExternalLink,
|
||||
formatTime,
|
||||
IconProperty,
|
||||
removeHash,
|
||||
renderRefInfo,
|
||||
resolveDarkMode,
|
||||
setDarkMode,
|
||||
}
|
||||
|
||||
@@ -21,6 +21,14 @@ import * as moment from 'moment'
|
||||
import 'moment-duration-format'
|
||||
import { Button } from '@patternfly/react-core'
|
||||
|
||||
import {
|
||||
calculateQueueItemTimes,
|
||||
ChangeLink,
|
||||
getJobStrResult,
|
||||
QueueItemProgressbar,
|
||||
} from './Misc'
|
||||
import { formatTime } from '../../Misc'
|
||||
|
||||
function getRefs(item) {
|
||||
// For backwards compat: get a list of this items refs.
|
||||
return 'refs' in item ? item.refs : [item]
|
||||
@@ -58,22 +66,13 @@ class ItemPanel extends React.Component {
|
||||
this.setState({ expanded: !expanded })
|
||||
}
|
||||
|
||||
time (ms) {
|
||||
return moment.duration(ms).format({
|
||||
template: 'h [hr] m [min]',
|
||||
largest: 2,
|
||||
minValue: 1,
|
||||
usePlural: false,
|
||||
})
|
||||
}
|
||||
|
||||
enqueueTime (ms) {
|
||||
// Special format case for enqueue time to add style
|
||||
let hours = 60 * 60 * 1000
|
||||
let now = Date.now()
|
||||
let delta = now - ms
|
||||
let status = 'text-success'
|
||||
let text = this.time(delta)
|
||||
let text = formatTime(delta)
|
||||
if (delta > (4 * hours)) {
|
||||
status = 'text-danger'
|
||||
} else if (delta > (2 * hours)) {
|
||||
@@ -82,101 +81,12 @@ class ItemPanel extends React.Component {
|
||||
return <span className={status}>{text}</span>
|
||||
}
|
||||
|
||||
jobStrResult (job) {
|
||||
let result = job.result ? job.result.toLowerCase() : null
|
||||
if (result === null) {
|
||||
if (job.url === null) {
|
||||
if (job.queued === false) {
|
||||
result = 'waiting'
|
||||
} else {
|
||||
result = 'queued'
|
||||
}
|
||||
} else if (job.paused !== null && job.paused) {
|
||||
result = 'paused'
|
||||
} else {
|
||||
result = 'in progress'
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
renderChangeLink (change) {
|
||||
let changeId = change.id || 'NA'
|
||||
let changeTitle = changeId
|
||||
// Fall back to display the ref if there is no change id
|
||||
if (changeId === 'NA' && change.ref) {
|
||||
changeTitle = change.ref
|
||||
}
|
||||
let changeText = ''
|
||||
if (change.url !== null) {
|
||||
let githubId = changeId.match(/^([0-9]+),([0-9a-f]{40})$/)
|
||||
if (githubId) {
|
||||
changeTitle = githubId
|
||||
changeText = '#' + githubId[1]
|
||||
} else if (/^[0-9a-f]{40}$/.test(changeId)) {
|
||||
changeText = changeId.slice(0, 7)
|
||||
}
|
||||
} else if (changeId.length === 40) {
|
||||
changeText = changeId.slice(0, 7)
|
||||
}
|
||||
return (
|
||||
<small>
|
||||
<a href={change.url} onClick={(e) => e.stopPropagation()}>
|
||||
{changeText !== '' ? (
|
||||
<abbr title={changeTitle}>{changeText}</abbr>) : changeTitle}
|
||||
</a>
|
||||
</small>)
|
||||
}
|
||||
|
||||
renderProgressBar (change) {
|
||||
const interesting_jobs = change.jobs.filter(j => this.jobStrResult(j) !== 'skipped')
|
||||
let jobPercent = (100 / interesting_jobs.length).toFixed(2)
|
||||
return (
|
||||
<div className={`progress zuul-change-total-result${this.props.preferences.darkMode ? ' progress-dark' : ''}`}>
|
||||
{change.jobs.map((job, idx) => {
|
||||
let result = this.jobStrResult(job)
|
||||
if (['queued', 'waiting', 'skipped'].includes(result)) {
|
||||
return ''
|
||||
}
|
||||
let className = ''
|
||||
switch (result) {
|
||||
case 'success':
|
||||
className = ' progress-bar-success'
|
||||
break
|
||||
case 'lost':
|
||||
case 'failure':
|
||||
className = ' progress-bar-danger'
|
||||
break
|
||||
case 'unstable':
|
||||
case 'retry_limit':
|
||||
case 'post_failure':
|
||||
case 'node_failure':
|
||||
className = ' progress-bar-warning'
|
||||
break
|
||||
case 'paused':
|
||||
className = ' progress-bar-info'
|
||||
break
|
||||
default:
|
||||
if (job.pre_fail) {
|
||||
className = ' progress-bar-danger'
|
||||
}
|
||||
break
|
||||
}
|
||||
return <div className={'progress-bar' + className}
|
||||
key={idx}
|
||||
title={job.name}
|
||||
style={{width: jobPercent + '%'}}/>
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderTimer (change, times) {
|
||||
let remainingTime
|
||||
if (times.remaining === null) {
|
||||
remainingTime = 'unknown'
|
||||
} else {
|
||||
remainingTime = this.time(times.remaining)
|
||||
remainingTime = formatTime(times.remaining)
|
||||
}
|
||||
return (
|
||||
<React.Fragment>
|
||||
@@ -300,7 +210,7 @@ class ItemPanel extends React.Component {
|
||||
name = <span className='zuul-job-name'>{job_name}</span>
|
||||
}
|
||||
let resultBar
|
||||
let result = this.jobStrResult(job)
|
||||
let result = getJobStrResult(job)
|
||||
if (result === 'in progress') {
|
||||
resultBar = this.renderJobProgressBar(job, job_times.elapsed, job_times.remaining)
|
||||
} else {
|
||||
@@ -328,7 +238,7 @@ class ItemPanel extends React.Component {
|
||||
renderJobList (jobs, times) {
|
||||
const [buttonText, interestingJobs] = this.state.showSkipped ?
|
||||
['Hide', jobs] :
|
||||
['Show', jobs.filter(j => this.jobStrResult(j) !== 'skipped')]
|
||||
['Show', jobs.filter(j => getJobStrResult(j) !== 'skipped')]
|
||||
const skippedJobCount = jobs.length - interestingJobs.length
|
||||
|
||||
return (
|
||||
@@ -352,46 +262,6 @@ class ItemPanel extends React.Component {
|
||||
)
|
||||
}
|
||||
|
||||
calculateTimes (item) {
|
||||
let maxRemaining = 0
|
||||
let jobs = {}
|
||||
const now = Date.now()
|
||||
|
||||
for (const job of item.jobs) {
|
||||
let jobElapsed = null
|
||||
let jobRemaining = null
|
||||
if (job.start_time) {
|
||||
let jobStart = parseInt(job.start_time * 1000)
|
||||
|
||||
if (job.end_time) {
|
||||
let jobEnd = parseInt(job.end_time * 1000)
|
||||
jobElapsed = jobEnd - jobStart
|
||||
} else {
|
||||
jobElapsed = Math.max(now - jobStart, 0)
|
||||
if (job.estimated_time) {
|
||||
jobRemaining = Math.max(parseInt(job.estimated_time * 1000) - jobElapsed, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (jobRemaining && jobRemaining > maxRemaining) {
|
||||
maxRemaining = jobRemaining
|
||||
}
|
||||
jobs[job.name] = {
|
||||
elapsed: jobElapsed,
|
||||
remaining: jobRemaining,
|
||||
}
|
||||
}
|
||||
// If not all the jobs have started, this will be null, so only
|
||||
// use our value if it's oky to calculate it.
|
||||
if (item.remaininging_time === null) {
|
||||
maxRemaining = null
|
||||
}
|
||||
return {
|
||||
remaining: maxRemaining,
|
||||
jobs: jobs,
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { expanded } = this.state
|
||||
const { item, globalExpanded } = this.props
|
||||
@@ -399,7 +269,7 @@ class ItemPanel extends React.Component {
|
||||
if (this.clicked) {
|
||||
expand = expanded
|
||||
}
|
||||
const times = this.calculateTimes(item)
|
||||
const times = calculateQueueItemTimes(item)
|
||||
const header = (
|
||||
<div className={`panel panel-default ${this.props.preferences.darkMode ? 'zuul-change-dark' : 'zuul-change'}`}>
|
||||
<div className={`panel-heading ${this.props.preferences.darkMode ? 'zuul-patchset-header-dark' : 'zuul-patchset-header'}`}
|
||||
@@ -408,7 +278,7 @@ class ItemPanel extends React.Component {
|
||||
{item.live === true ? (
|
||||
<div className='row'>
|
||||
<div className='col-xs-6'>
|
||||
{this.renderProgressBar(item)}
|
||||
<QueueItemProgressbar item={item} darkMode={this.props.preferences.darkMode} />
|
||||
</div>
|
||||
<div className='col-xs-6 text-right'>
|
||||
{this.renderTimer(item, times)}
|
||||
@@ -421,7 +291,7 @@ class ItemPanel extends React.Component {
|
||||
<span className='change_project'>{change.project}</span>
|
||||
</div>
|
||||
<div className='col-xs-4 text-right'>
|
||||
{this.renderChangeLink(change)}
|
||||
<ChangeLink change={change} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright 2018 Red Hat, Inc
|
||||
// Copyright 2020 BMW Group
|
||||
// Copyright 2024 Acme Gating, LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
@@ -29,6 +30,8 @@ import {
|
||||
TimesIcon,
|
||||
} from '@patternfly/react-icons'
|
||||
|
||||
import { ExternalLink } from '../../Misc'
|
||||
|
||||
const QUEUE_ITEM_ICON_CONFIGS = {
|
||||
SUCCESS: {
|
||||
icon: CheckIcon,
|
||||
@@ -148,4 +151,148 @@ PipelineIcon.propTypes = {
|
||||
size: PropTypes.string,
|
||||
}
|
||||
|
||||
export { getQueueItemIconConfig, PipelineIcon }
|
||||
function ChangeLink({ change }) {
|
||||
let changeId = change.id || 'NA'
|
||||
let changeTitle = changeId
|
||||
// Fall back to display the ref if there is no change id
|
||||
if (changeId === 'NA' && change.ref) {
|
||||
changeTitle = change.ref
|
||||
}
|
||||
let changeText = ''
|
||||
if (change.url !== null) {
|
||||
let githubId = changeId.match(/^([0-9]+),([0-9a-f]{40})$/)
|
||||
if (githubId) {
|
||||
changeTitle = githubId
|
||||
changeText = '#' + githubId[1]
|
||||
} else if (/^[0-9a-f]{40}$/.test(changeId)) {
|
||||
changeText = changeId.slice(0, 7)
|
||||
}
|
||||
} else if (changeId.length === 40) {
|
||||
changeText = changeId.slice(0, 7)
|
||||
}
|
||||
return (
|
||||
<ExternalLink target={change.url}>
|
||||
{changeText !== '' ? changeText : changeTitle}
|
||||
</ExternalLink>
|
||||
)
|
||||
}
|
||||
|
||||
ChangeLink.propTypes = {
|
||||
change: PropTypes.object,
|
||||
}
|
||||
|
||||
const getJobStrResult = (job) => {
|
||||
let result = job.result ? job.result.toLowerCase() : null
|
||||
if (result === null) {
|
||||
if (job.url === null) {
|
||||
if (job.queued === false) {
|
||||
result = 'waiting'
|
||||
} else {
|
||||
result = 'queued'
|
||||
}
|
||||
} else if (job.paused !== null && job.paused) {
|
||||
result = 'paused'
|
||||
} else {
|
||||
result = 'in progress'
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const calculateQueueItemTimes = (item) => {
|
||||
let maxRemaining = 0
|
||||
let jobs = {}
|
||||
const now = Date.now()
|
||||
|
||||
for (const job of item.jobs) {
|
||||
let jobElapsed = null
|
||||
let jobRemaining = null
|
||||
if (job.start_time) {
|
||||
let jobStart = parseInt(job.start_time * 1000)
|
||||
|
||||
if (job.end_time) {
|
||||
let jobEnd = parseInt(job.end_time * 1000)
|
||||
jobElapsed = jobEnd - jobStart
|
||||
} else {
|
||||
jobElapsed = Math.max(now - jobStart, 0)
|
||||
if (job.estimated_time) {
|
||||
jobRemaining = Math.max(parseInt(job.estimated_time * 1000) - jobElapsed, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (jobRemaining && jobRemaining > maxRemaining) {
|
||||
maxRemaining = jobRemaining
|
||||
}
|
||||
jobs[job.name] = {
|
||||
elapsed: jobElapsed,
|
||||
remaining: jobRemaining,
|
||||
}
|
||||
}
|
||||
// If not all the jobs have started, this will be null, so only
|
||||
// use our value if it's oky to calculate it.
|
||||
if (item.remaining_time === null) {
|
||||
maxRemaining = null
|
||||
}
|
||||
return {
|
||||
remaining: maxRemaining,
|
||||
jobs: jobs,
|
||||
}
|
||||
}
|
||||
|
||||
function QueueItemProgressbar({ item, darkMode }) {
|
||||
// TODO (felix): Use a PF4 progress bar instead
|
||||
const interesting_jobs = item.jobs.filter(j => getJobStrResult(j) !== 'skipped')
|
||||
let jobPercent = (100 / interesting_jobs.length).toFixed(2)
|
||||
return (
|
||||
<div className={`progress zuul-change-total-result${darkMode ? ' progress-dark' : ''}`}>
|
||||
{item.jobs.map((job, idx) => {
|
||||
let result = getJobStrResult(job)
|
||||
if (['queued', 'waiting', 'skipped'].includes(result)) {
|
||||
return ''
|
||||
}
|
||||
let className = ''
|
||||
switch (result) {
|
||||
case 'success':
|
||||
className = ' progress-bar-success'
|
||||
break
|
||||
case 'lost':
|
||||
case 'failure':
|
||||
className = ' progress-bar-danger'
|
||||
break
|
||||
case 'unstable':
|
||||
case 'retry_limit':
|
||||
case 'post_failure':
|
||||
case 'node_failure':
|
||||
className = ' progress-bar-warning'
|
||||
break
|
||||
case 'paused':
|
||||
className = ' progress-bar-info'
|
||||
break
|
||||
default:
|
||||
if (job.pre_fail) {
|
||||
className = ' progress-bar-danger'
|
||||
}
|
||||
break
|
||||
}
|
||||
return <div className={'progress-bar' + className}
|
||||
key={idx}
|
||||
title={job.name}
|
||||
style={{ width: jobPercent + '%' }} />
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
QueueItemProgressbar.propTypes = {
|
||||
item: PropTypes.object,
|
||||
darkMode: PropTypes.bool,
|
||||
}
|
||||
|
||||
export {
|
||||
calculateQueueItemTimes,
|
||||
ChangeLink,
|
||||
getJobStrResult,
|
||||
getQueueItemIconConfig,
|
||||
QueueItemProgressbar,
|
||||
PipelineIcon,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user