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 {
|
||||
static propTypes = {
|
||||
build: PropTypes.object.isRequired
|
||||
artifacts: PropTypes.array.isRequired
|
||||
}
|
||||
|
||||
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>,
|
||||
icon: null}
|
||||
if (artifact.metadata) {
|
||||
|
@ -60,11 +60,14 @@ class ArtifactList extends React.Component {
|
|||
})
|
||||
|
||||
return (
|
||||
<div className="tree-view-container">
|
||||
<TreeView
|
||||
nodes={nodes}
|
||||
/>
|
||||
</div>
|
||||
<>
|
||||
<br/>
|
||||
<div className="tree-view-container">
|
||||
<TreeView
|
||||
nodes={nodes}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,68 +16,238 @@ import * as React from 'react'
|
|||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
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 Manifest from './Manifest'
|
||||
import Console from './Console'
|
||||
import { BuildResultBadge, BuildResultWithIcon, IconProperty } from './Misc'
|
||||
import { ExternalLink } from '../../Misc'
|
||||
|
||||
class Build extends React.Component {
|
||||
static propTypes = {
|
||||
build: PropTypes.object,
|
||||
tenant: PropTypes.object,
|
||||
active: PropTypes.string,
|
||||
hash: PropTypes.array,
|
||||
}
|
||||
|
||||
render () {
|
||||
const { build, active, hash } = this.props
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Build result {build.uuid}</h2>
|
||||
<div>
|
||||
<ul className="nav nav-tabs nav-tabs-pf">
|
||||
<li className={active==='summary'?'active':undefined}>
|
||||
<Link to={this.props.tenant.linkPrefix + '/build/' + build.uuid}>
|
||||
Summary
|
||||
function Build(props) {
|
||||
const { build, tenant, timezone, fetchable } = props
|
||||
return (
|
||||
<>
|
||||
<Title
|
||||
headingLevel="h2"
|
||||
style={{
|
||||
color: build.voting
|
||||
? 'inherit'
|
||||
: 'var(--pf-global--disabled-color--100)',
|
||||
}}
|
||||
>
|
||||
<BuildResultWithIcon
|
||||
result={build.result}
|
||||
colored={build.voting}
|
||||
size="md"
|
||||
>
|
||||
{build.job_name} {!build.voting && ' (non-voting)'}
|
||||
</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>
|
||||
</li>
|
||||
{build.manifest &&
|
||||
<li className={active==='logs'?'active':undefined}>
|
||||
<Link to={this.props.tenant.linkPrefix + '/build/' + build.uuid + '/logs'}>
|
||||
Logs
|
||||
</Link>
|
||||
</li>}
|
||||
{build.output &&
|
||||
<li className={active==='console'?'active':undefined}>
|
||||
<Link
|
||||
to={this.props.tenant.linkPrefix + '/build/' + build.uuid + '/console'}>
|
||||
Console
|
||||
</Link>
|
||||
</li>}
|
||||
</ul>
|
||||
<div>
|
||||
{/* NOTE (felix): Since I'm already working on a PF4 change for
|
||||
this file, I don't want to change too much here for now and
|
||||
just make it compatible to the improved routing solution.
|
||||
*/}
|
||||
{active === 'summary' && <Summary build={build} />}
|
||||
{active === 'logs' && build && build.manifest && (
|
||||
<Manifest tenant={this.props.tenant} build={build}/>
|
||||
)}
|
||||
{active === 'console' && build && build.output && (
|
||||
<Console
|
||||
output={build.output}
|
||||
errorIds={build.errorIds}
|
||||
displayPath={hash.length>0?hash:undefined}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
/>
|
||||
<IconProperty
|
||||
WrapElement={ListItem}
|
||||
icon={<HistoryIcon />}
|
||||
value={
|
||||
<Link
|
||||
to={
|
||||
tenant.linkPrefix +
|
||||
'/builds?job_name=' +
|
||||
build.job_name +
|
||||
'&project=' +
|
||||
build.project
|
||||
}
|
||||
title="See previous runs of this job inside current project."
|
||||
>
|
||||
View build history
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
{/* In some cases not all build data is available on initial
|
||||
page load (e.g. when we come from another page like the
|
||||
buildset result page). Thus, we have to check for the
|
||||
buildset here. */}
|
||||
{build.buildset && (
|
||||
<IconProperty
|
||||
WrapElement={ListItem}
|
||||
icon={<BuildIcon />}
|
||||
value={
|
||||
<Link
|
||||
to={
|
||||
tenant.linkPrefix + '/buildset/' + build.buildset.uuid
|
||||
}
|
||||
>
|
||||
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
|
||||
return (
|
||||
<React.Fragment>
|
||||
<br />
|
||||
<div key="tasks">
|
||||
{Object.entries(output)
|
||||
.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 { connect } from 'react-redux'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
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 { 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 BuildOutput from '../containers/build/BuildOutput'
|
||||
import Console from '../containers/build/Console'
|
||||
import Manifest from '../containers/build/Manifest'
|
||||
|
||||
class BuildPage extends React.Component {
|
||||
static propTypes = {
|
||||
|
@ -30,45 +53,220 @@ class BuildPage extends React.Component {
|
|||
dispatch: PropTypes.func,
|
||||
activeTab: PropTypes.string.isRequired,
|
||||
location: PropTypes.object,
|
||||
history: PropTypes.object,
|
||||
}
|
||||
|
||||
updateData = (force) => {
|
||||
this.props.dispatch(fetchBuildIfNeeded(
|
||||
this.props.tenant, this.props.match.params.buildId, null, force))
|
||||
this.props.dispatch(
|
||||
fetchBuildIfNeeded(
|
||||
this.props.tenant,
|
||||
this.props.match.params.buildId,
|
||||
null,
|
||||
force
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
componentDidMount() {
|
||||
document.title = 'Zuul Build'
|
||||
if (this.props.tenant.name) {
|
||||
this.updateData()
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.tenant.name !== prevProps.tenant.name) {
|
||||
this.updateData()
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { remoteData, activeTab, location } = this.props
|
||||
handleTabClick = (tabIndex, build) => {
|
||||
// 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 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 (
|
||||
<PageSection variant={PageSectionVariants.light}>
|
||||
<PageSection style={{paddingRight: '5px'}}>
|
||||
<Fetchable
|
||||
isFetching={remoteData.isFetching}
|
||||
fetchCallback={this.updateData}
|
||||
<>
|
||||
<PageSection variant={PageSectionVariants.light}>
|
||||
<Build
|
||||
build={build}
|
||||
active={activeTab}
|
||||
hash={hash}
|
||||
fetchable={fetchable}
|
||||
/>
|
||||
</PageSection>
|
||||
{build && <Build build={build} active={activeTab} hash={hash}/>}
|
||||
</PageSection>
|
||||
<PageSection variant={PageSectionVariants.light}>
|
||||
<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,
|
||||
remoteData: state.build,
|
||||
}))(BuildPage)
|
||||
}))(withRouter(BuildPage))
|
||||
|
|
|
@ -89,7 +89,12 @@ const routes = () => [
|
|||
{
|
||||
to: '/build/:buildId',
|
||||
component: BuildPage,
|
||||
props: {'activeTab': 'summary'},
|
||||
props: {'activeTab': 'results'},
|
||||
},
|
||||
{
|
||||
to: '/build/:buildId/artifacts',
|
||||
component: BuildPage,
|
||||
props: {'activeTab': 'artifacts'},
|
||||
},
|
||||
{
|
||||
to: '/build/:buildId/logs',
|
||||
|
|
Loading…
Reference in New Issue