Update web ui for dependency refactor
This updates the web ui to handle multiple changes per item. It is compatible with the current data output as well as the upcoming API. Change-Id: I536967e51b22b60c8ff7baa46b902a36d1ea44dd
This commit is contained in:
parent
c531adacae
commit
4a7e86f7f6
|
@ -48,22 +48,22 @@ ExternalLink.propTypes = {
|
|||
children: PropTypes.node,
|
||||
}
|
||||
|
||||
function buildExternalLink(buildish) {
|
||||
function buildExternalLink(ref) {
|
||||
/* TODO (felix): What should we show for periodic builds
|
||||
here? They don't provide a change, but the ref_url is
|
||||
also not usable */
|
||||
if (buildish.ref_url && buildish.change) {
|
||||
if (ref.ref_url && ref.change) {
|
||||
return (
|
||||
<ExternalLink target={buildish.ref_url}>
|
||||
<ExternalLink target={ref.ref_url}>
|
||||
<strong>Change </strong>
|
||||
{buildish.change},{buildish.patchset}
|
||||
{ref.change},{ref.patchset}
|
||||
</ExternalLink>
|
||||
)
|
||||
} else if (buildish.ref_url && buildish.newrev) {
|
||||
} else if (ref.ref_url && ref.newrev) {
|
||||
return (
|
||||
<ExternalLink target={buildish.ref_url}>
|
||||
<ExternalLink target={ref.ref_url}>
|
||||
<strong>Revision </strong>
|
||||
{buildish.newrev.slice(0, 7)}
|
||||
{ref.newrev.slice(0, 7)}
|
||||
</ExternalLink>
|
||||
)
|
||||
}
|
||||
|
@ -71,20 +71,20 @@ function buildExternalLink(buildish) {
|
|||
return null
|
||||
}
|
||||
|
||||
function buildExternalTableLink(buildish) {
|
||||
function buildExternalTableLink(ref) {
|
||||
/* TODO (felix): What should we show for periodic builds
|
||||
here? They don't provide a change, but the ref_url is
|
||||
also not usable */
|
||||
if (buildish.ref_url && buildish.change) {
|
||||
if (ref.ref_url && ref.change) {
|
||||
return (
|
||||
<ExternalLink target={buildish.ref_url}>
|
||||
{buildish.change},{buildish.patchset}
|
||||
<ExternalLink target={ref.ref_url}>
|
||||
{ref.change},{ref.patchset}
|
||||
</ExternalLink>
|
||||
)
|
||||
} else if (buildish.ref_url && buildish.newrev) {
|
||||
} else if (ref.ref_url && ref.newrev) {
|
||||
return (
|
||||
<ExternalLink target={buildish.ref_url}>
|
||||
{buildish.newrev.slice(0, 7)}
|
||||
<ExternalLink target={ref.ref_url}>
|
||||
{ref.newrev.slice(0, 7)}
|
||||
</ExternalLink>
|
||||
)
|
||||
}
|
||||
|
@ -92,6 +92,32 @@ function buildExternalTableLink(buildish) {
|
|||
return null
|
||||
}
|
||||
|
||||
function renderRefInfo(ref) {
|
||||
const refinfo = ref.branch ? (
|
||||
<>
|
||||
<strong>Branch </strong> {ref.branch}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<strong>Ref </strong> {ref.ref}
|
||||
</>
|
||||
)
|
||||
const oldrev = ref.oldrev ? (
|
||||
<><br/><strong>Old</strong> {ref.oldrev}</>
|
||||
) : ( <></> )
|
||||
const newrev = ref.newrev ? (
|
||||
<><br/><strong>New</strong> {ref.newrev}</>
|
||||
) : ( <></> )
|
||||
|
||||
return (
|
||||
<>
|
||||
{refinfo}
|
||||
{oldrev}
|
||||
{newrev}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function IconProperty(props) {
|
||||
const { icon, value, WrapElement = 'span' } = props
|
||||
return (
|
||||
|
@ -146,4 +172,4 @@ function setDarkMode(darkMode) {
|
|||
}
|
||||
}
|
||||
|
||||
export { IconProperty, removeHash, ExternalLink, buildExternalLink, buildExternalTableLink, ConditionalWrapper, resolveDarkMode, setDarkMode }
|
||||
export { IconProperty, removeHash, ExternalLink, buildExternalLink, buildExternalTableLink, renderRefInfo, ConditionalWrapper, resolveDarkMode, setDarkMode }
|
||||
|
|
|
@ -22,8 +22,6 @@ import {
|
|||
BookIcon,
|
||||
BuildIcon,
|
||||
CodeBranchIcon,
|
||||
CodeIcon,
|
||||
CubeIcon,
|
||||
FileCodeIcon,
|
||||
FingerprintIcon,
|
||||
HistoryIcon,
|
||||
|
@ -35,20 +33,36 @@ import {
|
|||
} from '@patternfly/react-icons'
|
||||
import * as moment from 'moment'
|
||||
import * as moment_tz from 'moment-timezone'
|
||||
import _ from 'lodash'
|
||||
import 'moment-duration-format'
|
||||
|
||||
import { BuildResultBadge, BuildResultWithIcon } from './Misc'
|
||||
import { buildExternalLink, ExternalLink, IconProperty } from '../../Misc'
|
||||
import { buildExternalLink, renderRefInfo, ExternalLink, IconProperty } from '../../Misc'
|
||||
|
||||
import AutoholdModal from '../autohold/autoholdModal'
|
||||
|
||||
function getRefs(build) {
|
||||
// This method has a purpose beyond backwards compat: return the
|
||||
// zuul ref for this build first, then the remaining refs.
|
||||
if (!('refs' in build.buildset)) {
|
||||
// Backwards compat
|
||||
return [build]
|
||||
}
|
||||
return [build.ref, ...build.buildset.refs.filter((i) => !_.isEqual(i, build.ref))]
|
||||
}
|
||||
|
||||
function getRef(build) {
|
||||
return 'project' in build ? build : build.ref
|
||||
}
|
||||
|
||||
function Build({ build, tenant, timezone, user }) {
|
||||
const [showAutoholdModal, setShowAutoholdModal] = useState(false)
|
||||
const change = build.change ? build.change : ''
|
||||
const ref = build.change ? '' : build.ref
|
||||
const project = build.project
|
||||
const buildRef = getRef(build)
|
||||
// the change or ref to use for api actions like autohold
|
||||
const actionRef = buildRef.change ? '' : buildRef.ref
|
||||
const actionChange = buildRef.change ? String(buildRef.change) : ''
|
||||
//const project = build.project
|
||||
const job_name = build.job_name
|
||||
const build_link = buildExternalLink(build)
|
||||
const index_links = build.manifest && build.manifest.index_links
|
||||
const build_log_url = build.log_url ?
|
||||
(index_links ? build.log_url + 'index.html' : build.log_url)
|
||||
|
@ -78,7 +92,6 @@ function Build({ build, tenant, timezone, user }) {
|
|||
)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<Title
|
||||
|
@ -112,38 +125,20 @@ function Build({ build, tenant, timezone, user }) {
|
|||
<Flex flex={{ lg: 'flex_1' }}>
|
||||
<FlexItem>
|
||||
<List style={{ listStyle: 'none' }}>
|
||||
{build_link && (
|
||||
{getRefs(build).map((ref, idx) => (
|
||||
<IconProperty
|
||||
key={idx}
|
||||
WrapElement={ListItem}
|
||||
icon={<CodeIcon />}
|
||||
value={build_link}
|
||||
icon={<CodeBranchIcon />}
|
||||
value={
|
||||
<span>
|
||||
{buildExternalLink(ref)}<br/>
|
||||
<strong>Project </strong> {ref.project}<br/>
|
||||
{renderRefInfo(ref)}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{/* TODO (felix): Link to project page in Zuul */}
|
||||
<IconProperty
|
||||
WrapElement={ListItem}
|
||||
icon={<CubeIcon />}
|
||||
value={
|
||||
<>
|
||||
<strong>Project </strong> {build.project}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<IconProperty
|
||||
WrapElement={ListItem}
|
||||
icon={<CodeBranchIcon />}
|
||||
value={
|
||||
build.branch ? (
|
||||
<>
|
||||
<strong>Branch </strong> {build.branch}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<strong>Ref </strong> {build.ref}
|
||||
</>
|
||||
)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
<IconProperty
|
||||
WrapElement={ListItem}
|
||||
icon={<StreamIcon />}
|
||||
|
@ -225,7 +220,7 @@ function Build({ build, tenant, timezone, user }) {
|
|||
'/builds?job_name=' +
|
||||
build.job_name +
|
||||
'&project=' +
|
||||
build.project
|
||||
buildRef.project
|
||||
}
|
||||
title="See previous runs of this job inside current project."
|
||||
>
|
||||
|
@ -277,9 +272,9 @@ function Build({ build, tenant, timezone, user }) {
|
|||
{<AutoholdModal
|
||||
showAutoholdModal={showAutoholdModal}
|
||||
setShowAutoholdModal={setShowAutoholdModal}
|
||||
change={String(change)}
|
||||
changeRef={ref}
|
||||
project={project}
|
||||
change={actionChange}
|
||||
changeRef={actionRef}
|
||||
project={buildRef.project}
|
||||
jobName={job_name}
|
||||
/>}
|
||||
</>
|
||||
|
|
|
@ -50,6 +50,10 @@ import * as moment_tz from 'moment-timezone'
|
|||
import { BuildResult, BuildResultWithIcon } from './Misc'
|
||||
import { buildExternalTableLink, IconProperty } from '../../Misc'
|
||||
|
||||
function getRef(build) {
|
||||
return 'project' in build ? build : build.ref
|
||||
}
|
||||
|
||||
function BuildTable({
|
||||
builds,
|
||||
fetching,
|
||||
|
@ -101,7 +105,8 @@ function BuildTable({
|
|||
]
|
||||
|
||||
function createBuildRow(build) {
|
||||
const changeOrRefLink = buildExternalTableLink(build)
|
||||
const ref = getRef(build)
|
||||
const changeOrRefLink = buildExternalTableLink(ref)
|
||||
|
||||
return {
|
||||
// Pass the build's uuid as row id, so we can use it later on in the
|
||||
|
@ -125,10 +130,10 @@ function BuildTable({
|
|||
),
|
||||
},
|
||||
{
|
||||
title: build.project,
|
||||
title: ref.project,
|
||||
},
|
||||
{
|
||||
title: build.branch ? build.branch : build.ref,
|
||||
title: ref.branch ? ref.branch : ref.ref,
|
||||
},
|
||||
{
|
||||
title: build.pipeline,
|
||||
|
|
|
@ -27,10 +27,8 @@ import {
|
|||
ModalVariant,
|
||||
} from '@patternfly/react-core'
|
||||
import {
|
||||
CodeIcon,
|
||||
CodeBranchIcon,
|
||||
OutlinedCommentDotsIcon,
|
||||
CubeIcon,
|
||||
FingerprintIcon,
|
||||
StreamIcon,
|
||||
OutlinedCalendarAltIcon,
|
||||
|
@ -41,15 +39,19 @@ import * as moment from 'moment'
|
|||
import * as moment_tz from 'moment-timezone'
|
||||
import 'moment-duration-format'
|
||||
|
||||
import { buildExternalLink, IconProperty } from '../../Misc'
|
||||
import { buildExternalLink, renderRefInfo, IconProperty } from '../../Misc'
|
||||
import { BuildResultBadge, BuildResultWithIcon } from './Misc'
|
||||
import { enqueue, enqueue_ref } from '../../api'
|
||||
import { addNotification, addApiError } from '../../actions/notifications'
|
||||
import { ChartModal } from '../charts/ChartModal'
|
||||
import BuildsetGanttChart from '../charts/GanttChart'
|
||||
|
||||
function getRefs(buildset) {
|
||||
// For backwards compat: get a list of this items changes.
|
||||
return 'refs' in buildset ? buildset.refs : [buildset]
|
||||
}
|
||||
|
||||
function Buildset({ buildset, timezone, tenant, user, preferences }) {
|
||||
const buildset_link = buildExternalLink(buildset)
|
||||
const [isGanttChartModalOpen, setIsGanttChartModalOpen] = useState(false)
|
||||
|
||||
function renderBuildTimes() {
|
||||
|
@ -234,32 +236,6 @@ function Buildset({ buildset, timezone, tenant, user, preferences }) {
|
|||
)
|
||||
}
|
||||
|
||||
function renderRefInfo(buildset) {
|
||||
const refinfo = buildset.branch ? (
|
||||
<>
|
||||
<strong>Branch </strong> {buildset.branch}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<strong>Ref </strong> {buildset.ref}
|
||||
</>
|
||||
)
|
||||
const oldrev = buildset.oldrev ? (
|
||||
<><br/><strong>Old</strong> {buildset.oldrev}</>
|
||||
) : ( <></> )
|
||||
const newrev = buildset.newrev ? (
|
||||
<><br/><strong>New</strong> {buildset.newrev}</>
|
||||
) : ( <></> )
|
||||
|
||||
return (
|
||||
<>
|
||||
{refinfo}
|
||||
{oldrev}
|
||||
{newrev}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Title headingLevel="h2">
|
||||
|
@ -276,32 +252,20 @@ function Buildset({ buildset, timezone, tenant, user, preferences }) {
|
|||
<Flex flex={{ default: 'flex_1' }}>
|
||||
<FlexItem>
|
||||
<List style={{ listStyle: 'none' }}>
|
||||
{/* TODO (felix): It would be cool if we could differentiate
|
||||
between the SVC system (Github, Gitlab, Gerrit), so we could
|
||||
show the respective icon here (GithubIcon, GitlabIcon,
|
||||
GitIcon - AFAIK the Gerrit icon is not very popular among
|
||||
icon frameworks like fontawesome */}
|
||||
{buildset_link && (
|
||||
{getRefs(buildset).map((ref, idx) => (
|
||||
<IconProperty
|
||||
WrapElement={ListItem}
|
||||
icon={<CodeIcon />}
|
||||
value={buildset_link}
|
||||
icon={<CodeBranchIcon />}
|
||||
key={idx}
|
||||
value={
|
||||
<span>
|
||||
{buildExternalLink(ref)}<br/>
|
||||
<strong>Project </strong> {ref.project}<br/>
|
||||
{renderRefInfo(ref)}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{/* TODO (felix): Link to project page in Zuul */}
|
||||
<IconProperty
|
||||
WrapElement={ListItem}
|
||||
icon={<CubeIcon />}
|
||||
value={
|
||||
<>
|
||||
<strong>Project </strong> {buildset.project}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<IconProperty
|
||||
WrapElement={ListItem}
|
||||
icon={<CodeBranchIcon />}
|
||||
value={renderRefInfo(buildset)}/>
|
||||
))}
|
||||
<IconProperty
|
||||
WrapElement={ListItem}
|
||||
icon={<StreamIcon />}
|
||||
|
|
|
@ -55,6 +55,10 @@ import * as moment_tz from 'moment-timezone'
|
|||
import { BuildResult, BuildResultWithIcon } from './Misc'
|
||||
import { buildExternalTableLink, IconProperty } from '../../Misc'
|
||||
|
||||
function getRef(buildset) {
|
||||
return 'refs' in buildset ? buildset.refs[0] : buildset
|
||||
}
|
||||
|
||||
function BuildsetTable({
|
||||
buildsets,
|
||||
fetching,
|
||||
|
@ -140,7 +144,8 @@ function BuildsetTable({
|
|||
]
|
||||
|
||||
function createBuildsetRow(buildset) {
|
||||
const changeOrRefLink = buildExternalTableLink(buildset)
|
||||
const ref = getRef(buildset)
|
||||
const changeOrRefLink = buildExternalTableLink(ref)
|
||||
|
||||
let duration
|
||||
if (currentDuration === 'Buildset Duration') {
|
||||
|
@ -163,12 +168,12 @@ function BuildsetTable({
|
|||
<BuildResultWithIcon
|
||||
result={buildset.result}
|
||||
link={`${tenant.linkPrefix}/buildset/${buildset.uuid}`}>
|
||||
{buildset.project}
|
||||
{ref.project}
|
||||
</BuildResultWithIcon>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: buildset.branch ? buildset.branch : buildset.ref,
|
||||
title: ref.branch ? ref.branch : ref.ref,
|
||||
},
|
||||
{
|
||||
title: buildset.pipeline,
|
||||
|
|
|
@ -17,7 +17,7 @@ import PropTypes from 'prop-types'
|
|||
import { Badge } from 'patternfly-react'
|
||||
import { Tooltip } from '@patternfly/react-core'
|
||||
|
||||
import Change from './Change'
|
||||
import Item from './Item'
|
||||
|
||||
|
||||
class ChangeQueue extends React.Component {
|
||||
|
@ -38,15 +38,15 @@ class ChangeQueue extends React.Component {
|
|||
shortName = shortName.substr(0, 32) + '...'
|
||||
}
|
||||
let changesList = []
|
||||
queue.heads.forEach((changes, changeIdx) => {
|
||||
changes.forEach((change, idx) => {
|
||||
queue.heads.forEach((items, itemIdx) => {
|
||||
items.forEach((item, idx) => {
|
||||
changesList.push(
|
||||
<Change
|
||||
change={change}
|
||||
<Item
|
||||
item={item}
|
||||
queue={queue}
|
||||
expanded={expanded}
|
||||
pipeline={pipeline}
|
||||
key={changeIdx.toString() + idx}
|
||||
key={itemIdx.toString() + idx}
|
||||
/>)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -37,12 +37,17 @@ import { fetchStatusIfNeeded } from '../../actions/status'
|
|||
|
||||
import LineAngleImage from '../../images/line-angle.png'
|
||||
import LineTImage from '../../images/line-t.png'
|
||||
import ChangePanel from './ChangePanel'
|
||||
import ItemPanel from './ItemPanel'
|
||||
|
||||
function getChange(item) {
|
||||
// For backwards compat: get a representative change for this item
|
||||
// if there is more than one.
|
||||
return 'changes' in item ? item.changes[0] : item
|
||||
}
|
||||
|
||||
class Change extends React.Component {
|
||||
class Item extends React.Component {
|
||||
static propTypes = {
|
||||
change: PropTypes.object.isRequired,
|
||||
item: PropTypes.object.isRequired,
|
||||
queue: PropTypes.object.isRequired,
|
||||
expanded: PropTypes.bool.isRequired,
|
||||
pipeline: PropTypes.object,
|
||||
|
@ -59,7 +64,10 @@ class Change extends React.Component {
|
|||
}
|
||||
|
||||
dequeueConfirm = () => {
|
||||
const { tenant, change, pipeline } = this.props
|
||||
const { tenant, item, pipeline } = this.props
|
||||
const change = getChange(item)
|
||||
// Use the first change as a proxy for the item since queue
|
||||
// commands operate on changes
|
||||
let projectName = change.project
|
||||
let changeId = change.id || 'N/A'
|
||||
let changeRef = change.ref
|
||||
|
@ -90,7 +98,8 @@ class Change extends React.Component {
|
|||
|
||||
renderDequeueModal() {
|
||||
const { showDequeueModal } = this.state
|
||||
const { change } = this.props
|
||||
const { item } = this.props
|
||||
const change = getChange(item)
|
||||
let projectName = change.project
|
||||
let changeId = change.id || change.ref
|
||||
const title = 'You are about to dequeue a change'
|
||||
|
@ -111,7 +120,8 @@ class Change extends React.Component {
|
|||
}
|
||||
|
||||
promoteConfirm = () => {
|
||||
const { tenant, change, pipeline } = this.props
|
||||
const { tenant, item, pipeline } = this.props
|
||||
const change = getChange(item)
|
||||
let changeId = change.id || 'NA'
|
||||
this.setState(() => ({ showPromoteModal: false }))
|
||||
if (changeId !== 'N/A') {
|
||||
|
@ -138,7 +148,8 @@ class Change extends React.Component {
|
|||
|
||||
renderPromoteModal() {
|
||||
const { showPromoteModal } = this.state
|
||||
const { change } = this.props
|
||||
const { item } = this.props
|
||||
const change = getChange(item)
|
||||
let changeId = change.id || 'N/A'
|
||||
const title = 'You are about to promote a change'
|
||||
return (
|
||||
|
@ -166,7 +177,7 @@ class Change extends React.Component {
|
|||
icon={<BanIcon style={{
|
||||
color: 'var(--pf-global--danger-color--100)',
|
||||
}} />}
|
||||
description="Stop all jobs for this change"
|
||||
description="Stop all jobs for this item"
|
||||
onClick={(event) => {
|
||||
event.preventDefault()
|
||||
this.setState(() => ({ showDequeueModal: true }))
|
||||
|
@ -177,7 +188,7 @@ class Change extends React.Component {
|
|||
icon={<AngleDoubleUpIcon style={{
|
||||
color: 'var(--pf-global--default-color--200)',
|
||||
}} />}
|
||||
description="Promote this change to the top of the queue"
|
||||
description="Promote this item to the top of the queue"
|
||||
onClick={(event) => {
|
||||
event.preventDefault()
|
||||
this.setState(() => ({ showPromoteModal: true }))
|
||||
|
@ -207,20 +218,19 @@ class Change extends React.Component {
|
|||
|
||||
}
|
||||
|
||||
|
||||
renderStatusIcon(change) {
|
||||
renderStatusIcon(item) {
|
||||
let iconGlyph = 'pficon pficon-ok'
|
||||
let iconTitle = 'Succeeding'
|
||||
if (change.active !== true) {
|
||||
if (item.active !== true) {
|
||||
iconGlyph = 'pficon pficon-pending'
|
||||
iconTitle = 'Waiting until closer to head of queue to' +
|
||||
' start jobs'
|
||||
} else if (change.live !== true) {
|
||||
} else if (item.live !== true) {
|
||||
iconGlyph = 'pficon pficon-info'
|
||||
iconTitle = 'Dependent change required for testing'
|
||||
} else if (change.failing_reasons &&
|
||||
change.failing_reasons.length > 0) {
|
||||
let reason = change.failing_reasons.join(', ')
|
||||
iconTitle = 'Dependent item required for testing'
|
||||
} else if (item.failing_reasons &&
|
||||
item.failing_reasons.length > 0) {
|
||||
let reason = item.failing_reasons.join(', ')
|
||||
iconTitle = 'Failing because ' + reason
|
||||
if (reason.match(/merge conflict/)) {
|
||||
iconGlyph = 'pficon pficon-error-circle-o zuul-build-merge-conflict'
|
||||
|
@ -233,9 +243,9 @@ class Change extends React.Component {
|
|||
className={'zuul-build-status ' + iconGlyph}
|
||||
title={iconTitle} />
|
||||
)
|
||||
if (change.live) {
|
||||
if (item.live) {
|
||||
return (
|
||||
<Link to={this.props.tenant.linkPrefix + '/status/change/' + change.id}>
|
||||
<Link to={this.props.tenant.linkPrefix + '/status/change/' + getChange(item).id}>
|
||||
{icon}
|
||||
</Link>
|
||||
)
|
||||
|
@ -244,9 +254,9 @@ class Change extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
renderLineImg(change, i) {
|
||||
renderLineImg(item, i) {
|
||||
let image = LineTImage
|
||||
if (change._tree_branches.indexOf(i) === change._tree_branches.length - 1) {
|
||||
if (item._tree_branches.indexOf(i) === item._tree_branches.length - 1) {
|
||||
// Angle line
|
||||
image = LineAngleImage
|
||||
}
|
||||
|
@ -254,13 +264,13 @@ class Change extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { change, queue, expanded, pipeline, user, tenant } = this.props
|
||||
const { item, queue, expanded, pipeline, user, tenant } = this.props
|
||||
let row = []
|
||||
let adminMenuWidth = 15
|
||||
let i
|
||||
for (i = 0; i < queue._tree_columns; i++) {
|
||||
let className = ''
|
||||
if (i < change._tree.length && change._tree[i] !== null) {
|
||||
if (i < item._tree.length && item._tree[i] !== null) {
|
||||
if (this.props.preferences.darkMode) {
|
||||
className = ' zuul-change-row-line-dark'
|
||||
} else {
|
||||
|
@ -269,19 +279,19 @@ class Change extends React.Component {
|
|||
}
|
||||
row.push(
|
||||
<td key={i} className={'zuul-change-row' + className}>
|
||||
{i === change._tree_index ? this.renderStatusIcon(change) : ''}
|
||||
{change._tree_branches.indexOf(i) !== -1 ? (
|
||||
this.renderLineImg(change, i)) : ''}
|
||||
{i === item._tree_index ? this.renderStatusIcon(item) : ''}
|
||||
{item._tree_branches.indexOf(i) !== -1 ? (
|
||||
this.renderLineImg(item, i)) : ''}
|
||||
</td>)
|
||||
}
|
||||
let changeWidth = (user.isAdmin && user.scope.indexOf(tenant.name) !== -1)
|
||||
let itemWidth = (user.isAdmin && user.scope.indexOf(tenant.name) !== -1)
|
||||
? 360 - adminMenuWidth - 16 * queue._tree_columns
|
||||
: 360 - 16 * queue._tree_columns
|
||||
row.push(
|
||||
<td key={i + 1}
|
||||
className="zuul-change-cell"
|
||||
style={{ width: changeWidth + 'px' }}>
|
||||
<ChangePanel change={change} globalExpanded={expanded} pipeline={pipeline} />
|
||||
style={{ width: itemWidth + 'px' }}>
|
||||
<ItemPanel item={item} globalExpanded={expanded} pipeline={pipeline} />
|
||||
</td>
|
||||
)
|
||||
if (user.isAdmin && user.scope.indexOf(tenant.name) !== -1) {
|
||||
|
@ -311,4 +321,4 @@ export default connect(state => ({
|
|||
tenant: state.tenant,
|
||||
user: state.user,
|
||||
preferences: state.preferences,
|
||||
}))(Change)
|
||||
}))(Item)
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright 2018 Red Hat, Inc
|
||||
// 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
|
||||
|
@ -20,11 +21,15 @@ import * as moment from 'moment'
|
|||
import 'moment-duration-format'
|
||||
import { Button } from '@patternfly/react-core'
|
||||
|
||||
function getChanges(item) {
|
||||
// For backwards compat: get a list of this items changes.
|
||||
return 'changes' in item ? item.changes : [item]
|
||||
}
|
||||
|
||||
class ChangePanel extends React.Component {
|
||||
class ItemPanel extends React.Component {
|
||||
static propTypes = {
|
||||
globalExpanded: PropTypes.bool.isRequired,
|
||||
change: PropTypes.object.isRequired,
|
||||
item: PropTypes.object.isRequired,
|
||||
tenant: PropTypes.object,
|
||||
preferences: PropTypes.object
|
||||
}
|
||||
|
@ -175,13 +180,13 @@ class ChangePanel extends React.Component {
|
|||
}
|
||||
return (
|
||||
<React.Fragment>
|
||||
<small title='Remaining Time' className='time' style={{display: 'inline'}}>
|
||||
{remainingTime}
|
||||
</small>
|
||||
<br />
|
||||
<small title='Elapsed Time' className='time' style={{display: 'inline'}}>
|
||||
{this.enqueueTime(change.enqueue_time)}
|
||||
</small>
|
||||
<small> | </small>
|
||||
<small title='Remaining Time' className='time' style={{display: 'inline'}}>
|
||||
{remainingTime}
|
||||
</small>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
@ -347,12 +352,12 @@ class ChangePanel extends React.Component {
|
|||
)
|
||||
}
|
||||
|
||||
calculateTimes (change) {
|
||||
calculateTimes (item) {
|
||||
let maxRemaining = 0
|
||||
let jobs = {}
|
||||
const now = Date.now()
|
||||
|
||||
for (const job of change.jobs) {
|
||||
for (const job of item.jobs) {
|
||||
let jobElapsed = null
|
||||
let jobRemaining = null
|
||||
if (job.start_time) {
|
||||
|
@ -378,7 +383,7 @@ class ChangePanel extends React.Component {
|
|||
}
|
||||
// If not all the jobs have started, this will be null, so only
|
||||
// use our value if it's oky to calculate it.
|
||||
if (change.remaininging_time === null) {
|
||||
if (item.remaininging_time === null) {
|
||||
maxRemaining = null
|
||||
}
|
||||
return {
|
||||
|
@ -389,36 +394,40 @@ class ChangePanel extends React.Component {
|
|||
|
||||
render () {
|
||||
const { expanded } = this.state
|
||||
const { change, globalExpanded } = this.props
|
||||
const { item, globalExpanded } = this.props
|
||||
let expand = globalExpanded
|
||||
if (this.clicked) {
|
||||
expand = expanded
|
||||
}
|
||||
const times = this.calculateTimes(change)
|
||||
const times = this.calculateTimes(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'}`}
|
||||
onClick={this.onClick}>
|
||||
<div className='row'>
|
||||
<div className='col-xs-8'>
|
||||
<span className='change_project'>{change.project}</span>
|
||||
<div>
|
||||
{item.live === true ? (
|
||||
<div className='row'>
|
||||
<div className='col-xs-4'>
|
||||
{this.renderChangeLink(change)}
|
||||
<div className='col-xs-6'>
|
||||
{this.renderProgressBar(item)}
|
||||
</div>
|
||||
<div className='col-xs-8'>
|
||||
{this.renderProgressBar(change)}
|
||||
<div className='col-xs-6 text-right'>
|
||||
{this.renderTimer(item, times)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{change.live === true ? (
|
||||
<div className='col-xs-4 text-right'>
|
||||
{this.renderTimer(change, times)}
|
||||
</div>
|
||||
) : ''}
|
||||
{getChanges(item).map((change, idx) => (
|
||||
<div key={idx} className='row'>
|
||||
<div className='col-xs-8'>
|
||||
<span className='change_project'>{change.project}</span>
|
||||
</div>
|
||||
<div className='col-xs-4 text-right'>
|
||||
{this.renderChangeLink(change)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{expand ? this.renderJobList(change.jobs, times) : ''}
|
||||
{expand ? this.renderJobList(item.jobs, times) : ''}
|
||||
</div >
|
||||
)
|
||||
return (
|
||||
|
@ -432,4 +441,4 @@ class ChangePanel extends React.Component {
|
|||
export default connect(state => ({
|
||||
tenant: state.tenant,
|
||||
preferences: state.preferences,
|
||||
}))(ChangePanel)
|
||||
}))(ItemPanel)
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright 2018 Red Hat, Inc
|
||||
// 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
|
||||
|
@ -20,11 +21,13 @@ import { Button } from '@patternfly/react-core'
|
|||
|
||||
import { setTenantAction } from '../../actions/tenant'
|
||||
import configureStore from '../../store'
|
||||
import ChangePanel from './ChangePanel'
|
||||
import ItemPanel from './ItemPanel'
|
||||
|
||||
|
||||
const fakeChange = {
|
||||
project: 'org-project',
|
||||
const fakeItem = {
|
||||
changes: [{
|
||||
project: 'org-project'
|
||||
}],
|
||||
jobs: [{
|
||||
name: 'job-name',
|
||||
url: 'stream/42',
|
||||
|
@ -32,14 +35,13 @@ const fakeChange = {
|
|||
}]
|
||||
}
|
||||
|
||||
|
||||
it('change panel render multi tenant links', () => {
|
||||
it('item panel render multi tenant links', () => {
|
||||
const store = configureStore()
|
||||
store.dispatch(setTenantAction('tenant-one', false))
|
||||
const application = create(
|
||||
<Provider store={store}>
|
||||
<Router>
|
||||
<ChangePanel change={fakeChange} globalExpanded={true} />
|
||||
<ItemPanel item={fakeItem} globalExpanded={true} />
|
||||
</Router>
|
||||
</Provider>
|
||||
)
|
||||
|
@ -50,13 +52,13 @@ it('change panel render multi tenant links', () => {
|
|||
expect(skipButton === undefined)
|
||||
})
|
||||
|
||||
it('change panel render white-label tenant links', () => {
|
||||
it('item panel render white-label tenant links', () => {
|
||||
const store = configureStore()
|
||||
store.dispatch(setTenantAction('tenant-one', true))
|
||||
const application = create(
|
||||
<Provider store={store}>
|
||||
<Router>
|
||||
<ChangePanel change={fakeChange} globalExpanded={true} />
|
||||
<ItemPanel item={fakeItem} globalExpanded={true} />
|
||||
</Router>
|
||||
</Provider>
|
||||
)
|
||||
|
@ -67,7 +69,77 @@ it('change panel render white-label tenant links', () => {
|
|||
expect(skipButton === undefined)
|
||||
})
|
||||
|
||||
it('change panel skip jobs', () => {
|
||||
it('item panel skip jobs', () => {
|
||||
const fakeItem = {
|
||||
changes: [{
|
||||
project: 'org-project'
|
||||
}],
|
||||
jobs: [{
|
||||
name: 'job-name',
|
||||
url: 'stream/42',
|
||||
result: 'skipped'
|
||||
}]
|
||||
}
|
||||
|
||||
const store = configureStore()
|
||||
store.dispatch(setTenantAction('tenant-one', true))
|
||||
const application = create(
|
||||
<Provider store={store}>
|
||||
<Router>
|
||||
<ItemPanel item={fakeItem} globalExpanded={true} />
|
||||
</Router>
|
||||
</Provider>
|
||||
)
|
||||
const skipButton = application.root.findByType(Button)
|
||||
expect(skipButton.props.children.includes('skipped job'))
|
||||
})
|
||||
|
||||
/* Backwards compat; remove after circular dependency refactor */
|
||||
|
||||
const fakeChange = {
|
||||
project: 'org-project',
|
||||
jobs: [{
|
||||
name: 'job-name',
|
||||
url: 'stream/42',
|
||||
result: null
|
||||
}]
|
||||
}
|
||||
|
||||
it('item panel backwards compat render multi tenant links', () => {
|
||||
const store = configureStore()
|
||||
store.dispatch(setTenantAction('tenant-one', false))
|
||||
const application = create(
|
||||
<Provider store={store}>
|
||||
<Router>
|
||||
<ItemPanel item={fakeChange} globalExpanded={true} />
|
||||
</Router>
|
||||
</Provider>
|
||||
)
|
||||
const jobLink = application.root.findByType(Link)
|
||||
expect(jobLink.props.to).toEqual(
|
||||
'/t/tenant-one/stream/42')
|
||||
const skipButton = application.root.findAllByType(Button)
|
||||
expect(skipButton === undefined)
|
||||
})
|
||||
|
||||
it('item panel backwards compat render white-label tenant links', () => {
|
||||
const store = configureStore()
|
||||
store.dispatch(setTenantAction('tenant-one', true))
|
||||
const application = create(
|
||||
<Provider store={store}>
|
||||
<Router>
|
||||
<ItemPanel item={fakeChange} globalExpanded={true} />
|
||||
</Router>
|
||||
</Provider>
|
||||
)
|
||||
const jobLink = application.root.findByType(Link)
|
||||
expect(jobLink.props.to).toEqual(
|
||||
'/stream/42')
|
||||
const skipButton = application.root.findAllByType(Button)
|
||||
expect(skipButton === undefined)
|
||||
})
|
||||
|
||||
it('item panel backwards compat skip jobs', () => {
|
||||
const fakeChange = {
|
||||
project: 'org-project',
|
||||
jobs: [{
|
||||
|
@ -82,7 +154,7 @@ it('change panel skip jobs', () => {
|
|||
const application = create(
|
||||
<Provider store={store}>
|
||||
<Router>
|
||||
<ChangePanel change={fakeChange} globalExpanded={true} />
|
||||
<ItemPanel item={fakeChange} globalExpanded={true} />
|
||||
</Router>
|
||||
</Provider>
|
||||
)
|
|
@ -209,7 +209,7 @@ a.refresh {
|
|||
|
||||
.zuul-change-total-result {
|
||||
height: 10px;
|
||||
width: 100px;
|
||||
width: 180px;
|
||||
margin: 0;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
|
|
|
@ -18,7 +18,7 @@ import { connect } from 'react-redux'
|
|||
import { PageSection, PageSectionVariants } from '@patternfly/react-core'
|
||||
|
||||
import { fetchChangeIfNeeded } from '../actions/change'
|
||||
import ChangePanel from '../containers/status/ChangePanel'
|
||||
import ItemPanel from '../containers/status/ItemPanel'
|
||||
import { Fetchable } from '../containers/Fetching'
|
||||
|
||||
|
||||
|
@ -63,7 +63,7 @@ class ChangeStatusPage extends React.Component {
|
|||
|
||||
render () {
|
||||
const { remoteData } = this.props
|
||||
const change = remoteData.change
|
||||
const itemlist = remoteData.change
|
||||
return (
|
||||
<PageSection variant={PageSectionVariants.light}>
|
||||
<PageSection style={{paddingRight: '5px'}}>
|
||||
|
@ -72,11 +72,11 @@ class ChangeStatusPage extends React.Component {
|
|||
fetchCallback={this.updateData}
|
||||
/>
|
||||
</PageSection>
|
||||
{change && change.map((item, idx) => (
|
||||
{itemlist && itemlist.map((item, idx) => (
|
||||
<div className='row zuul-change-content' key={idx}>
|
||||
<ChangePanel
|
||||
<ItemPanel
|
||||
globalExpanded={true}
|
||||
change={item}
|
||||
item={item}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
|
Loading…
Reference in New Issue