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:
James E. Blair 2024-02-01 13:30:49 -08:00
parent c531adacae
commit 4a7e86f7f6
11 changed files with 278 additions and 192 deletions

View File

@ -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 }

View File

@ -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}
/>}
</>

View File

@ -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,

View File

@ -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 />}

View File

@ -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,

View File

@ -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}
/>)
})
})

View File

@ -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)

View File

@ -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)

View File

@ -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>
)

View File

@ -209,7 +209,7 @@ a.refresh {
.zuul-change-total-result {
height: 10px;
width: 100px;
width: 180px;
margin: 0;
display: inline-block;
vertical-align: middle;

View File

@ -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>
))}