Merge "Refactor build page tabs"

This commit is contained in:
Zuul
2019-08-08 20:54:02 +00:00
committed by Gerrit Code Review
7 changed files with 265 additions and 99 deletions

View File

@@ -17,115 +17,47 @@ import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Link } from 'react-router-dom'
import { Panel } from 'react-bootstrap'
import {
Nav,
NavItem,
TabContainer,
TabPane,
TabContent,
} from 'patternfly-react'
import ArtifactList from './Artifact'
import BuildOutput from './BuildOutput'
import Manifest from './Manifest'
import Console from './Console'
class Build extends React.Component {
static propTypes = {
build: PropTypes.object,
tenant: PropTypes.object,
active: PropTypes.string,
children: PropTypes.object,
}
render () {
const { build } = this.props
const rows = []
const myColumns = [
'job_name', 'result', 'voting',
'pipeline', 'start_time', 'end_time', 'duration',
'project', 'branch', 'change', 'patchset', 'oldrev', 'newrev',
'ref', 'new_rev', 'ref_url', 'log_url']
const defaultTab = window.location.hash.substring(1) || 'summary'
myColumns.forEach(column => {
let label = column
let value = build[column]
if (column === 'job_name') {
label = 'job'
value = (
<Link to={this.props.tenant.linkPrefix + '/job/' + value}>
{value}
</Link>
)
}
if (column === 'voting') {
if (value) {
value = 'true'
} else {
value = 'false'
}
}
if (value && (column === 'log_url' || column === 'ref_url')) {
value = <a href={value}>{value}</a>
}
if (column === 'log_url') {
label = 'log url'
}
if (column === 'ref_url') {
label = 'ref url'
}
if (value) {
rows.push({key: label, value: value})
}
})
const { build, active } = this.props
return (
<Panel>
<Panel.Heading>Build result {build.uuid}</Panel.Heading>
<Panel.Body>
<TabContainer id="zuul-project" defaultActiveKey={defaultTab}>
<div>
<Nav bsClass="nav nav-tabs nav-tabs-pf">
<NavItem eventKey={'summary'} href="#summary">
Summary
</NavItem>
<ul className="nav nav-tabs nav-tabs-pf">
<li className={active==='summary'?'active':undefined}>
<Link to={this.props.tenant.linkPrefix + '/build/' + build.uuid}>
Summary
</Link>
</li>
{build.manifest &&
<NavItem eventKey={'logs'} href="#logs">
Logs
</NavItem>}
<li className={active==='logs'?'active':undefined}>
<Link to={this.props.tenant.linkPrefix + '/build/' + build.uuid + '/logs'}>
Logs
</Link>
</li>}
{build.output &&
<NavItem eventKey={'console'} href="#console">
Console
</NavItem>}
</Nav>
<TabContent>
<TabPane eventKey={'summary'}>
<table className="table table-striped table-bordered">
<tbody>
{rows.map(item => (
<tr key={item.key}>
<td>{item.key}</td>
<td>{item.value}</td>
</tr>
))}
</tbody>
</table>
<h3>Artifacts</h3>
<ArtifactList build={build}/>
<h3>Results</h3>
{build.hosts && <BuildOutput output={build.hosts}/>}
</TabPane>
{build.manifest &&
<TabPane eventKey={'logs'}>
<Manifest tenant={this.props.tenant} build={build}/>
</TabPane>}
{build.output &&
<TabPane eventKey={'console'}>
<Console output={build.output}/>
</TabPane>}
</TabContent>
<li className={active==='console'?'active':undefined}>
<Link
to={this.props.tenant.linkPrefix + '/build/' + build.uuid + '/console'}>
Console
</Link>
</li>}
</ul>
<div>
{this.props.children}
</div>
</div>
</TabContainer>
</Panel.Body>
</Panel>
)

View File

@@ -68,11 +68,14 @@ class Manifest extends React.Component {
const nodes = build.manifest.tree.map(n => renderTree(tenant, build, '', n))
return (
<div className="tree-view-container">
<TreeView
nodes={nodes}
/>
</div>
<React.Fragment>
<br/>
<div className="tree-view-container">
<TreeView
nodes={nodes}
/>
</div>
</React.Fragment>
)
}
}

View File

@@ -0,0 +1,93 @@
// Copyright 2018 Red Hat, Inc
//
// 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
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
import * as React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Link } from 'react-router-dom'
import ArtifactList from './Artifact'
import BuildOutput from './BuildOutput'
class Summary extends React.Component {
static propTypes = {
build: PropTypes.object,
tenant: PropTypes.object,
}
render () {
const { build } = this.props
const rows = []
const myColumns = [
'job_name', 'result', 'voting',
'pipeline', 'start_time', 'end_time', 'duration',
'project', 'branch', 'change', 'patchset', 'oldrev', 'newrev',
'ref', 'new_rev', 'ref_url', 'log_url']
myColumns.forEach(column => {
let label = column
let value = build[column]
if (column === 'job_name') {
label = 'job'
value = (
<Link to={this.props.tenant.linkPrefix + '/job/' + value}>
{value}
</Link>
)
}
if (column === 'voting') {
if (value) {
value = 'true'
} else {
value = 'false'
}
}
if (value && (column === 'log_url' || column === 'ref_url')) {
value = <a href={value}>{value}</a>
}
if (column === 'log_url') {
label = 'log url'
}
if (column === 'ref_url') {
label = 'ref url'
}
if (value) {
rows.push({key: label, value: value})
}
})
return (
<React.Fragment>
<br/>
<table className="table table-striped table-bordered">
<tbody>
{rows.map(item => (
<tr key={item.key}>
<td>{item.key}</td>
<td>{item.value}</td>
</tr>
))}
</tbody>
</table>
<h3>Artifacts</h3>
<ArtifactList build={build}/>
<h3>Results</h3>
{build.hosts && <BuildOutput output={build.hosts}/>}
</React.Fragment>
)
}
}
export default connect(state => ({tenant: state.tenant}))(Summary)

View File

@@ -19,6 +19,7 @@ import PropTypes from 'prop-types'
import { fetchBuildIfNeeded } from '../actions/build'
import Refreshable from '../containers/Refreshable'
import Build from '../containers/build/Build'
import Summary from '../containers/build/Summary'
class BuildPage extends Refreshable {
@@ -46,7 +47,10 @@ class BuildPage extends Refreshable {
<div style={{float: 'right'}}>
{this.renderSpinner()}
</div>
{build && <Build build={build}/>}
{build &&
<Build build={build} active='summary'>
<Summary build={build}/>
</Build>}
</React.Fragment>
)
}

View File

@@ -0,0 +1,62 @@
// Copyright 2018 Red Hat, Inc
//
// 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
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
import * as React from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import { fetchBuildIfNeeded } from '../actions/build'
import Refreshable from '../containers/Refreshable'
import Build from '../containers/build/Build'
import Console from '../containers/build/Console'
class BuildConsolePage extends Refreshable {
static propTypes = {
match: PropTypes.object.isRequired,
remoteData: PropTypes.object,
tenant: PropTypes.object
}
updateData = (force) => {
this.props.dispatch(fetchBuildIfNeeded(
this.props.tenant, this.props.match.params.buildId, null, force))
}
componentDidMount () {
document.title = 'Zuul Build'
super.componentDidMount()
}
render () {
const { remoteData } = this.props
const build = remoteData.builds[this.props.match.params.buildId]
return (
<React.Fragment>
<div style={{float: 'right'}}>
{this.renderSpinner()}
</div>
{build && build.output &&
<Build build={build} active='console'>
<Console output={build.output}/>
</Build>}
</React.Fragment>
)
}
}
export default connect(state => ({
tenant: state.tenant,
remoteData: state.build,
}))(BuildConsolePage)

View File

@@ -0,0 +1,62 @@
// Copyright 2018 Red Hat, Inc
//
// 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
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
import * as React from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import { fetchBuildIfNeeded } from '../actions/build'
import Refreshable from '../containers/Refreshable'
import Build from '../containers/build/Build'
import Manifest from '../containers/build/Manifest'
class BuildLogsPage extends Refreshable {
static propTypes = {
match: PropTypes.object.isRequired,
remoteData: PropTypes.object,
tenant: PropTypes.object
}
updateData = (force) => {
this.props.dispatch(fetchBuildIfNeeded(
this.props.tenant, this.props.match.params.buildId, null, force))
}
componentDidMount () {
document.title = 'Zuul Build'
super.componentDidMount()
}
render () {
const { remoteData } = this.props
const build = remoteData.builds[this.props.match.params.buildId]
return (
<React.Fragment>
<div style={{float: 'right'}}>
{this.renderSpinner()}
</div>
{build && build.manifest &&
<Build build={build} active='logs'>
<Manifest tenant={this.props.tenant} build={build}/>
</Build>}
</React.Fragment>
)
}
}
export default connect(state => ({
tenant: state.tenant,
remoteData: state.build,
}))(BuildLogsPage)

View File

@@ -21,6 +21,8 @@ import JobsPage from './pages/Jobs'
import LabelsPage from './pages/Labels'
import NodesPage from './pages/Nodes'
import BuildPage from './pages/Build'
import BuildLogsPage from './pages/BuildLogs'
import BuildConsolePage from './pages/BuildConsole'
import LogFilePage from './pages/LogFile'
import BuildsPage from './pages/Builds'
import BuildsetsPage from './pages/Buildsets'
@@ -89,6 +91,14 @@ const routes = () => [
to: '/build/:buildId',
component: BuildPage
},
{
to: '/build/:buildId/logs',
component: BuildLogsPage
},
{
to: '/build/:buildId/console',
component: BuildConsolePage
},
{
to: '/build/:buildId/log/:file*',
component: LogFilePage