Merge "PF4: Update build result page layout"
This commit is contained in:
commit
7c2707be02
|
@ -43,13 +43,13 @@ class Artifact extends React.Component {
|
||||||
|
|
||||||
class ArtifactList extends React.Component {
|
class ArtifactList extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
build: PropTypes.object.isRequired
|
artifacts: PropTypes.array.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { build } = this.props
|
const { artifacts } = this.props
|
||||||
|
|
||||||
const nodes = build.artifacts.map((artifact, index) => {
|
const nodes = artifacts.map((artifact, index) => {
|
||||||
const node = {text: <a href={artifact.url}>{artifact.name}</a>,
|
const node = {text: <a href={artifact.url}>{artifact.name}</a>,
|
||||||
icon: null}
|
icon: null}
|
||||||
if (artifact.metadata) {
|
if (artifact.metadata) {
|
||||||
|
@ -60,11 +60,14 @@ class ArtifactList extends React.Component {
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tree-view-container">
|
<>
|
||||||
<TreeView
|
<br/>
|
||||||
nodes={nodes}
|
<div className="tree-view-container">
|
||||||
/>
|
<TreeView
|
||||||
</div>
|
nodes={nodes}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,68 +16,238 @@ import * as React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
|
import {
|
||||||
|
Flex,
|
||||||
|
FlexItem,
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
Title,
|
||||||
|
} from '@patternfly/react-core'
|
||||||
|
import {
|
||||||
|
BookIcon,
|
||||||
|
BuildIcon,
|
||||||
|
CodeBranchIcon,
|
||||||
|
CodeIcon,
|
||||||
|
CubeIcon,
|
||||||
|
FileCodeIcon,
|
||||||
|
FingerprintIcon,
|
||||||
|
HistoryIcon,
|
||||||
|
OutlinedCalendarAltIcon,
|
||||||
|
OutlinedClockIcon,
|
||||||
|
StreamIcon,
|
||||||
|
} from '@patternfly/react-icons'
|
||||||
|
import * as moment from 'moment'
|
||||||
|
import 'moment-duration-format'
|
||||||
|
|
||||||
import Summary from './Summary'
|
import { BuildResultBadge, BuildResultWithIcon, IconProperty } from './Misc'
|
||||||
import Manifest from './Manifest'
|
import { ExternalLink } from '../../Misc'
|
||||||
import Console from './Console'
|
|
||||||
|
|
||||||
class Build extends React.Component {
|
function Build(props) {
|
||||||
static propTypes = {
|
const { build, tenant, timezone, fetchable } = props
|
||||||
build: PropTypes.object,
|
return (
|
||||||
tenant: PropTypes.object,
|
<>
|
||||||
active: PropTypes.string,
|
<Title
|
||||||
hash: PropTypes.array,
|
headingLevel="h2"
|
||||||
}
|
style={{
|
||||||
|
color: build.voting
|
||||||
render () {
|
? 'inherit'
|
||||||
const { build, active, hash } = this.props
|
: 'var(--pf-global--disabled-color--100)',
|
||||||
|
}}
|
||||||
return (
|
>
|
||||||
<div>
|
<BuildResultWithIcon
|
||||||
<h2>Build result {build.uuid}</h2>
|
result={build.result}
|
||||||
<div>
|
colored={build.voting}
|
||||||
<ul className="nav nav-tabs nav-tabs-pf">
|
size="md"
|
||||||
<li className={active==='summary'?'active':undefined}>
|
>
|
||||||
<Link to={this.props.tenant.linkPrefix + '/build/' + build.uuid}>
|
{build.job_name} {!build.voting && ' (non-voting)'}
|
||||||
Summary
|
</BuildResultWithIcon>
|
||||||
|
<BuildResultBadge result={build.result} />
|
||||||
|
{fetchable}
|
||||||
|
</Title>
|
||||||
|
{/* We handle the spacing for the body and the flex items by ourselves
|
||||||
|
so they go hand in hand. By default, the flex items' spacing only
|
||||||
|
affects left/right margin, but not top or bottom (which looks
|
||||||
|
awkward when the items are stacked at certain breakpoints) */}
|
||||||
|
<Flex className="zuul-build-attributes">
|
||||||
|
<Flex flex={{ lg: 'flex_1' }}>
|
||||||
|
<FlexItem>
|
||||||
|
<List style={{ listStyle: 'none' }}>
|
||||||
|
{/* TODO (felix): What should we show for periodic builds
|
||||||
|
here? They don't provide a change, but the ref_url is
|
||||||
|
also not usable */}
|
||||||
|
{build.change && (
|
||||||
|
<IconProperty
|
||||||
|
WrapElement={ListItem}
|
||||||
|
icon={<CodeIcon />}
|
||||||
|
value={
|
||||||
|
<ExternalLink target={build.ref_url}>
|
||||||
|
<strong>Change </strong>
|
||||||
|
{build.change},{build.patchset}
|
||||||
|
</ExternalLink>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{/* 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={
|
||||||
|
<>
|
||||||
|
<strong>Branch </strong> {build.branch}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<IconProperty
|
||||||
|
WrapElement={ListItem}
|
||||||
|
icon={<StreamIcon />}
|
||||||
|
value={
|
||||||
|
<>
|
||||||
|
<strong>Pipeline </strong> {build.pipeline}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<IconProperty
|
||||||
|
WrapElement={ListItem}
|
||||||
|
icon={<FingerprintIcon />}
|
||||||
|
value={
|
||||||
|
<span>
|
||||||
|
<strong>UUID </strong> {build.uuid} <br />
|
||||||
|
<strong>Event ID </strong> {build.event_id} <br />
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</List>
|
||||||
|
</FlexItem>
|
||||||
|
</Flex>
|
||||||
|
<Flex flex={{ lg: 'flex_1' }}>
|
||||||
|
<FlexItem>
|
||||||
|
<List style={{ listStyle: 'none' }}>
|
||||||
|
<IconProperty
|
||||||
|
WrapElement={ListItem}
|
||||||
|
icon={<OutlinedCalendarAltIcon />}
|
||||||
|
value={
|
||||||
|
<span>
|
||||||
|
<strong>Started at </strong>
|
||||||
|
{moment
|
||||||
|
.utc(build.start_time)
|
||||||
|
.tz(timezone)
|
||||||
|
.format('YYYY-MM-DD HH:mm:ss')}
|
||||||
|
<br />
|
||||||
|
<strong>Completed at </strong>
|
||||||
|
{moment
|
||||||
|
.utc(build.end_time)
|
||||||
|
.tz(timezone)
|
||||||
|
.format('YYYY-MM-DD HH:mm:ss')}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<IconProperty
|
||||||
|
WrapElement={ListItem}
|
||||||
|
icon={<OutlinedClockIcon />}
|
||||||
|
value={
|
||||||
|
<>
|
||||||
|
<strong>Took </strong>
|
||||||
|
{moment
|
||||||
|
.duration(build.duration, 'seconds')
|
||||||
|
.format('h [hr] m [min] s [sec]')}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</List>
|
||||||
|
</FlexItem>
|
||||||
|
</Flex>
|
||||||
|
<Flex flex={{ lg: 'flex_1' }}>
|
||||||
|
<FlexItem>
|
||||||
|
<List style={{ listStyle: 'none' }}>
|
||||||
|
<IconProperty
|
||||||
|
WrapElement={ListItem}
|
||||||
|
icon={<BookIcon />}
|
||||||
|
value={
|
||||||
|
<Link to={tenant.linkPrefix + '/job/' + build.job_name}>
|
||||||
|
View job documentation
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
}
|
||||||
{build.manifest &&
|
/>
|
||||||
<li className={active==='logs'?'active':undefined}>
|
<IconProperty
|
||||||
<Link to={this.props.tenant.linkPrefix + '/build/' + build.uuid + '/logs'}>
|
WrapElement={ListItem}
|
||||||
Logs
|
icon={<HistoryIcon />}
|
||||||
</Link>
|
value={
|
||||||
</li>}
|
<Link
|
||||||
{build.output &&
|
to={
|
||||||
<li className={active==='console'?'active':undefined}>
|
tenant.linkPrefix +
|
||||||
<Link
|
'/builds?job_name=' +
|
||||||
to={this.props.tenant.linkPrefix + '/build/' + build.uuid + '/console'}>
|
build.job_name +
|
||||||
Console
|
'&project=' +
|
||||||
</Link>
|
build.project
|
||||||
</li>}
|
}
|
||||||
</ul>
|
title="See previous runs of this job inside current project."
|
||||||
<div>
|
>
|
||||||
{/* NOTE (felix): Since I'm already working on a PF4 change for
|
View build history
|
||||||
this file, I don't want to change too much here for now and
|
</Link>
|
||||||
just make it compatible to the improved routing solution.
|
}
|
||||||
*/}
|
/>
|
||||||
{active === 'summary' && <Summary build={build} />}
|
{/* In some cases not all build data is available on initial
|
||||||
{active === 'logs' && build && build.manifest && (
|
page load (e.g. when we come from another page like the
|
||||||
<Manifest tenant={this.props.tenant} build={build}/>
|
buildset result page). Thus, we have to check for the
|
||||||
)}
|
buildset here. */}
|
||||||
{active === 'console' && build && build.output && (
|
{build.buildset && (
|
||||||
<Console
|
<IconProperty
|
||||||
output={build.output}
|
WrapElement={ListItem}
|
||||||
errorIds={build.errorIds}
|
icon={<BuildIcon />}
|
||||||
displayPath={hash.length>0?hash:undefined}
|
value={
|
||||||
/>
|
<Link
|
||||||
)}
|
to={
|
||||||
</div>
|
tenant.linkPrefix + '/buildset/' + build.buildset.uuid
|
||||||
</div>
|
}
|
||||||
</div>
|
>
|
||||||
)
|
View buildset result
|
||||||
}
|
</Link>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<IconProperty
|
||||||
|
WrapElement={ListItem}
|
||||||
|
icon={<FileCodeIcon />}
|
||||||
|
value={
|
||||||
|
build.log_url ? (
|
||||||
|
<ExternalLink target={build.log_url}>View log</ExternalLink>
|
||||||
|
) : (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
color: 'var(--pf-global--disabled-color--100)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
No log available
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</List>
|
||||||
|
</FlexItem>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Build.propTypes = {
|
||||||
|
build: PropTypes.object,
|
||||||
|
tenant: PropTypes.object,
|
||||||
|
hash: PropTypes.array,
|
||||||
|
timezone: PropTypes.string,
|
||||||
|
fetchable: PropTypes.node,
|
||||||
|
}
|
||||||
|
|
||||||
export default connect(state => ({tenant: state.tenant}))(Build)
|
export default connect((state) => ({
|
||||||
|
tenant: state.tenant,
|
||||||
|
timezone: state.timezone,
|
||||||
|
}))(Build)
|
||||||
|
|
|
@ -108,6 +108,7 @@ class BuildOutput extends React.Component {
|
||||||
const { output } = this.props
|
const { output } = this.props
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
<br />
|
||||||
<div key="tasks">
|
<div key="tasks">
|
||||||
{Object.entries(output)
|
{Object.entries(output)
|
||||||
.filter(([, values]) => values.failed.length > 0)
|
.filter(([, values]) => values.failed.length > 0)
|
||||||
|
|
|
@ -1,131 +0,0 @@
|
||||||
// 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'
|
|
||||||
|
|
||||||
import * as moment from 'moment'
|
|
||||||
import 'moment-duration-format'
|
|
||||||
|
|
||||||
|
|
||||||
class Summary extends React.Component {
|
|
||||||
static propTypes = {
|
|
||||||
build: PropTypes.object,
|
|
||||||
tenant: PropTypes.object,
|
|
||||||
timezone: PropTypes.string,
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { build } = this.props
|
|
||||||
const rows = []
|
|
||||||
const myColumns = [
|
|
||||||
'job_name', 'result', 'buildset', 'voting',
|
|
||||||
'pipeline', 'start_time', 'end_time', 'duration',
|
|
||||||
'project', 'branch', 'change', 'patchset', 'oldrev', 'newrev',
|
|
||||||
'ref', 'new_rev', 'ref_url', 'log_url', 'event_id']
|
|
||||||
|
|
||||||
if (!build.buildset) {
|
|
||||||
// Safely handle missing buildset information
|
|
||||||
myColumns.splice(myColumns.indexOf('buildset'), 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
myColumns.forEach(column => {
|
|
||||||
let label = column
|
|
||||||
let value = build[column]
|
|
||||||
if (column === 'job_name') {
|
|
||||||
label = 'job'
|
|
||||||
value = (
|
|
||||||
<React.Fragment>
|
|
||||||
<Link to={this.props.tenant.linkPrefix + '/job/' + value}>
|
|
||||||
{value}
|
|
||||||
</Link>
|
|
||||||
<span> — </span>
|
|
||||||
<Link to={this.props.tenant.linkPrefix + '/builds?job_name=' + value + '&project=' + build.project} title="See previous runs of this job inside current project.">
|
|
||||||
build history
|
|
||||||
</Link>
|
|
||||||
</React.Fragment>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (column === 'buildset') {
|
|
||||||
value = (
|
|
||||||
<Link to={this.props.tenant.linkPrefix + '/buildset/' + value.uuid}>
|
|
||||||
{value.uuid}
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (column === 'voting') {
|
|
||||||
if (value) {
|
|
||||||
value = 'true'
|
|
||||||
} else {
|
|
||||||
value = 'false'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (column === 'start_time' || column === 'end_time') {
|
|
||||||
value = moment.utc(value).tz(this.props.timezone).format('YYYY-MM-DD HH:mm:ss')
|
|
||||||
}
|
|
||||||
if (column === 'duration') {
|
|
||||||
value = moment.duration(value, 'seconds')
|
|
||||||
.format('h [hr] m [min] s [sec]')
|
|
||||||
}
|
|
||||||
if (value && (column === 'log_url' || column === 'ref_url')) {
|
|
||||||
value = <a href={value}>{value}</a>
|
|
||||||
}
|
|
||||||
if (column === 'log_url') {
|
|
||||||
label = 'log url'
|
|
||||||
if (build.manifest && build.manifest.index_links) {
|
|
||||||
value = <a href={value + 'index.html'}>{value}</a>
|
|
||||||
} else {
|
|
||||||
value = <a href={value}>{value}</a>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (column === 'ref_url') {
|
|
||||||
label = 'ref url'
|
|
||||||
value = <a href={value}>{value}</a>
|
|
||||||
}
|
|
||||||
if (column === 'event_id') {
|
|
||||||
label = 'event id'
|
|
||||||
}
|
|
||||||
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, timezone: state.timezone}))(Summary)
|
|
|
@ -14,13 +14,36 @@
|
||||||
|
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
|
import { withRouter } from 'react-router-dom'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { PageSection, PageSectionVariants } from '@patternfly/react-core'
|
import {
|
||||||
|
EmptyState,
|
||||||
|
EmptyStateVariant,
|
||||||
|
EmptyStateIcon,
|
||||||
|
PageSection,
|
||||||
|
PageSectionVariants,
|
||||||
|
Tab,
|
||||||
|
Tabs,
|
||||||
|
TabTitleIcon,
|
||||||
|
TabTitleText,
|
||||||
|
Title,
|
||||||
|
} from '@patternfly/react-core'
|
||||||
|
import {
|
||||||
|
BuildIcon,
|
||||||
|
FileArchiveIcon,
|
||||||
|
FileCodeIcon,
|
||||||
|
TerminalIcon,
|
||||||
|
PollIcon,
|
||||||
|
} from '@patternfly/react-icons'
|
||||||
|
|
||||||
import { fetchBuildIfNeeded } from '../actions/build'
|
import { fetchBuildIfNeeded } from '../actions/build'
|
||||||
import { Fetchable } from '../containers/Fetching'
|
import { EmptyPage } from '../containers/Errors'
|
||||||
|
import { Fetchable, Fetching } from '../containers/Fetching'
|
||||||
|
import ArtifactList from '../containers/build/Artifact'
|
||||||
import Build from '../containers/build/Build'
|
import Build from '../containers/build/Build'
|
||||||
|
import BuildOutput from '../containers/build/BuildOutput'
|
||||||
|
import Console from '../containers/build/Console'
|
||||||
|
import Manifest from '../containers/build/Manifest'
|
||||||
|
|
||||||
class BuildPage extends React.Component {
|
class BuildPage extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -30,45 +53,220 @@ class BuildPage extends React.Component {
|
||||||
dispatch: PropTypes.func,
|
dispatch: PropTypes.func,
|
||||||
activeTab: PropTypes.string.isRequired,
|
activeTab: PropTypes.string.isRequired,
|
||||||
location: PropTypes.object,
|
location: PropTypes.object,
|
||||||
|
history: PropTypes.object,
|
||||||
}
|
}
|
||||||
|
|
||||||
updateData = (force) => {
|
updateData = (force) => {
|
||||||
this.props.dispatch(fetchBuildIfNeeded(
|
this.props.dispatch(
|
||||||
this.props.tenant, this.props.match.params.buildId, null, force))
|
fetchBuildIfNeeded(
|
||||||
|
this.props.tenant,
|
||||||
|
this.props.match.params.buildId,
|
||||||
|
null,
|
||||||
|
force
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount() {
|
||||||
document.title = 'Zuul Build'
|
document.title = 'Zuul Build'
|
||||||
if (this.props.tenant.name) {
|
if (this.props.tenant.name) {
|
||||||
this.updateData()
|
this.updateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate (prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (this.props.tenant.name !== prevProps.tenant.name) {
|
if (this.props.tenant.name !== prevProps.tenant.name) {
|
||||||
this.updateData()
|
this.updateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
handleTabClick = (tabIndex, build) => {
|
||||||
const { remoteData, activeTab, location } = this.props
|
// Usually tabs should only be used to display content in-page and not link
|
||||||
|
// to other pages:
|
||||||
|
// "Tabs are used to present a set on tabs for organizing content on a
|
||||||
|
// .page. It must always be used together with a tab content component."
|
||||||
|
// https://www.patternfly.org/v4/documentation/react/components/tabs
|
||||||
|
// But as want to be able to reach every tab's content via a dedicated URL
|
||||||
|
// while having the look and feel of tabs, we could hijack this onClick
|
||||||
|
// handler to do the link/routing stuff.
|
||||||
|
const { history, tenant } = this.props
|
||||||
|
|
||||||
|
switch (tabIndex) {
|
||||||
|
case 'artifacts':
|
||||||
|
history.push(`${tenant.linkPrefix}/build/${build.uuid}/artifacts`)
|
||||||
|
break
|
||||||
|
case 'logs':
|
||||||
|
history.push(`${tenant.linkPrefix}/build/${build.uuid}/logs`)
|
||||||
|
break
|
||||||
|
case 'console':
|
||||||
|
history.push(`${tenant.linkPrefix}/build/${build.uuid}/console`)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
// results
|
||||||
|
history.push(`${tenant.linkPrefix}/build/${build.uuid}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { remoteData, activeTab, location, tenant } = this.props
|
||||||
const build = remoteData.builds[this.props.match.params.buildId]
|
const build = remoteData.builds[this.props.match.params.buildId]
|
||||||
const hash = location.hash.substring(1).split('/')
|
const hash = location.hash.substring(1).split('/')
|
||||||
|
|
||||||
|
if (!build && remoteData.isFetching) {
|
||||||
|
return <Fetching />
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!build) {
|
||||||
|
return (
|
||||||
|
<EmptyPage
|
||||||
|
title="This build does not exist"
|
||||||
|
icon={BuildIcon}
|
||||||
|
linkTarget={`${tenant.linkPrefix}/builds`}
|
||||||
|
linkText="Show all builds"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchable = (
|
||||||
|
<Fetchable
|
||||||
|
isFetching={remoteData.isFetching}
|
||||||
|
fetchCallback={this.updateData}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
const resultsTabContent =
|
||||||
|
!build.hosts && remoteData.isFetchingOutput ? (
|
||||||
|
<Fetching />
|
||||||
|
) : build.hosts ? (
|
||||||
|
<BuildOutput output={build.hosts} />
|
||||||
|
) : (
|
||||||
|
<EmptyState variant={EmptyStateVariant.small}>
|
||||||
|
<EmptyStateIcon icon={PollIcon} />
|
||||||
|
<Title headingLevel="h4" size="lg">
|
||||||
|
This build does not provide any results
|
||||||
|
</Title>
|
||||||
|
</EmptyState>
|
||||||
|
)
|
||||||
|
|
||||||
|
const artifactsTabContent = build.artifacts.length ? (
|
||||||
|
<ArtifactList artifacts={build.artifacts} />
|
||||||
|
) : (
|
||||||
|
<EmptyState variant={EmptyStateVariant.small}>
|
||||||
|
<EmptyStateIcon icon={FileArchiveIcon} />
|
||||||
|
<Title headingLevel="h4" size="lg">
|
||||||
|
This build does not provide any artifacts
|
||||||
|
</Title>
|
||||||
|
</EmptyState>
|
||||||
|
)
|
||||||
|
|
||||||
|
const logsTabContent =
|
||||||
|
!build.manifest && remoteData.isFetchingManifest ? (
|
||||||
|
<Fetching />
|
||||||
|
) : build.manifest ? (
|
||||||
|
<Manifest tenant={this.props.tenant} build={build} />
|
||||||
|
) : (
|
||||||
|
<EmptyState variant={EmptyStateVariant.small}>
|
||||||
|
<EmptyStateIcon icon={FileCodeIcon} />
|
||||||
|
<Title headingLevel="h4" size="lg">
|
||||||
|
This build does not provide any logs
|
||||||
|
</Title>
|
||||||
|
</EmptyState>
|
||||||
|
)
|
||||||
|
|
||||||
|
const consoleTabContent =
|
||||||
|
!build.output && remoteData.isFetchingOutput ? (
|
||||||
|
<Fetching />
|
||||||
|
) : build.output ? (
|
||||||
|
<Console
|
||||||
|
output={build.output}
|
||||||
|
errorIds={build.errorIds}
|
||||||
|
displayPath={hash.length > 0 ? hash : undefined}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<EmptyState variant={EmptyStateVariant.small}>
|
||||||
|
<EmptyStateIcon icon={TerminalIcon} />
|
||||||
|
<Title headingLevel="h4" size="lg">
|
||||||
|
This build does not provide any console information
|
||||||
|
</Title>
|
||||||
|
</EmptyState>
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageSection variant={PageSectionVariants.light}>
|
<>
|
||||||
<PageSection style={{paddingRight: '5px'}}>
|
<PageSection variant={PageSectionVariants.light}>
|
||||||
<Fetchable
|
<Build
|
||||||
isFetching={remoteData.isFetching}
|
build={build}
|
||||||
fetchCallback={this.updateData}
|
active={activeTab}
|
||||||
|
hash={hash}
|
||||||
|
fetchable={fetchable}
|
||||||
/>
|
/>
|
||||||
</PageSection>
|
</PageSection>
|
||||||
{build && <Build build={build} active={activeTab} hash={hash}/>}
|
<PageSection variant={PageSectionVariants.light}>
|
||||||
</PageSection>
|
<Tabs
|
||||||
|
isFilled
|
||||||
|
activeKey={activeTab}
|
||||||
|
onSelect={(event, tabIndex) => this.handleTabClick(tabIndex, build)}
|
||||||
|
>
|
||||||
|
<Tab
|
||||||
|
eventKey="results"
|
||||||
|
title={
|
||||||
|
<>
|
||||||
|
<TabTitleIcon>
|
||||||
|
<PollIcon />
|
||||||
|
</TabTitleIcon>
|
||||||
|
<TabTitleText>Results</TabTitleText>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{resultsTabContent}
|
||||||
|
</Tab>
|
||||||
|
<Tab
|
||||||
|
eventKey="artifacts"
|
||||||
|
title={
|
||||||
|
<>
|
||||||
|
<TabTitleIcon>
|
||||||
|
<FileArchiveIcon />
|
||||||
|
</TabTitleIcon>
|
||||||
|
<TabTitleText>Artifacts</TabTitleText>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{artifactsTabContent}
|
||||||
|
</Tab>
|
||||||
|
<Tab
|
||||||
|
eventKey="logs"
|
||||||
|
title={
|
||||||
|
<>
|
||||||
|
<TabTitleIcon>
|
||||||
|
<FileCodeIcon />
|
||||||
|
</TabTitleIcon>
|
||||||
|
<TabTitleText>Logs</TabTitleText>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{logsTabContent}
|
||||||
|
</Tab>
|
||||||
|
<Tab
|
||||||
|
eventKey="console"
|
||||||
|
title={
|
||||||
|
<>
|
||||||
|
<TabTitleIcon>
|
||||||
|
<TerminalIcon />
|
||||||
|
</TabTitleIcon>
|
||||||
|
<TabTitleText>Console</TabTitleText>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{consoleTabContent}
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
</PageSection>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(state => ({
|
export default connect((state) => ({
|
||||||
tenant: state.tenant,
|
tenant: state.tenant,
|
||||||
remoteData: state.build,
|
remoteData: state.build,
|
||||||
}))(BuildPage)
|
}))(withRouter(BuildPage))
|
||||||
|
|
|
@ -89,7 +89,12 @@ const routes = () => [
|
||||||
{
|
{
|
||||||
to: '/build/:buildId',
|
to: '/build/:buildId',
|
||||||
component: BuildPage,
|
component: BuildPage,
|
||||||
props: {'activeTab': 'summary'},
|
props: {'activeTab': 'results'},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
to: '/build/:buildId/artifacts',
|
||||||
|
component: BuildPage,
|
||||||
|
props: {'activeTab': 'artifacts'},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
to: '/build/:buildId/logs',
|
to: '/build/:buildId/logs',
|
||||||
|
|
Loading…
Reference in New Issue