448596cfe0
At a lot of place, the duration for an item shows the raw value. It lacks an indication of the unit (ms, s or minutes??) and is not very human friendly. `480` is better understood as `8 minutes`. The `moment-duration-format` package enhance moment duration objects to ease formatting. It has better options than moment.duration.humanize(), notably it does not truncate value and trim extra tokens when they are not needed. The package does not have any extra dependencies. https://www.npmjs.com/package/moment-duration-format Format duration on the pages build, buildset and on the build summary. The Change panel had custom logic to workaround moment.duration.humanize over rounding (so that 100 minutes became '1 hour'). The new package does not have such behavior and offers tuning for all the features we had: * `largest: 2`, to only keep the two most significants token. 100 minutes and 30 seconds would thus render as '1 hour 40 minutes', stripping the extra '30 seconds'. * `minValue: 1`, based on the least significant token which is minute, it means any value less than one minute is rendered as: < 1 minute * `usePLural: false`, to disable pluralization since that was not handled previously. That reverts https://review.opendev.org/#/c/704191/ Make class="time" to not wrap, they would wrap on the Status page since the box containing is not large enough. In the Console, show the duration for each tasks, rounding to 1 second resolution. Would ease finding potential slowdown in a play execution. The tasks do not have a duration property, use moment to compute it based on start and end times. Since hostname length might vary, the duration spans might be misaligned. Use a flex to have them vertically aligned, credits to mcoker@github: https://github.com/patternfly/patternfly-react/issues/1393#issuecomment-515202640 Thanks to Tristan Cacqueray for the React guidances! Change-Id: I955583713778911a8e50f08dc6d077594da4ae44
173 lines
4.8 KiB
JavaScript
173 lines
4.8 KiB
JavaScript
// 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 { Table } from 'patternfly-react'
|
|
import * as moment from 'moment'
|
|
import 'moment-duration-format'
|
|
|
|
import { fetchBuilds } from '../api'
|
|
import TableFilters from '../containers/TableFilters'
|
|
|
|
|
|
class BuildsPage extends TableFilters {
|
|
static propTypes = {
|
|
tenant: PropTypes.object
|
|
}
|
|
|
|
constructor () {
|
|
super()
|
|
|
|
this.prepareTableHeaders()
|
|
this.state = {
|
|
builds: null,
|
|
currentFilterType: this.filterTypes[0],
|
|
activeFilters: [],
|
|
currentValue: ''
|
|
}
|
|
}
|
|
|
|
updateData = (filters) => {
|
|
let queryString = ''
|
|
if (filters) {
|
|
filters.forEach(item => queryString += '&' + item.key + '=' + item.value)
|
|
}
|
|
this.setState({builds: null})
|
|
fetchBuilds(this.props.tenant.apiPrefix, queryString).then(response => {
|
|
this.setState({builds: response.data})
|
|
})
|
|
}
|
|
|
|
componentDidMount () {
|
|
document.title = 'Zuul Builds'
|
|
if (this.props.tenant.name) {
|
|
this.updateData(this.getFilterFromUrl())
|
|
}
|
|
}
|
|
|
|
componentDidUpdate (prevProps) {
|
|
if (this.props.tenant.name !== prevProps.tenant.name) {
|
|
this.updateData(this.getFilterFromUrl())
|
|
}
|
|
}
|
|
|
|
prepareTableHeaders() {
|
|
const headerFormat = value => <Table.Heading>{value}</Table.Heading>
|
|
const cellFormat = (value) => (
|
|
<Table.Cell>{value}</Table.Cell>)
|
|
const linkBuildFormat = (value, rowdata) => (
|
|
<Table.Cell>
|
|
<Link to={this.props.tenant.linkPrefix + '/build/' + rowdata.rowData.uuid}>{value}</Link>
|
|
</Table.Cell>
|
|
)
|
|
const linkChangeFormat = (value, rowdata) => (
|
|
<Table.Cell>
|
|
<a href={rowdata.rowData.ref_url}>{value ? rowdata.rowData.change+','+rowdata.rowData.patchset : rowdata.rowData.newrev ? rowdata.rowData.newrev.substr(0, 7) : rowdata.rowData.branch}</a>
|
|
</Table.Cell>
|
|
)
|
|
const durationFormat = (value) => (
|
|
<Table.Cell>
|
|
{moment.duration(value, 'seconds').format('h [hr] m [min] s [sec]')}
|
|
</Table.Cell>
|
|
)
|
|
this.columns = []
|
|
this.filterTypes = []
|
|
const myColumns = [
|
|
'job',
|
|
'project',
|
|
'branch',
|
|
'pipeline',
|
|
'change',
|
|
'duration',
|
|
'start time',
|
|
'result']
|
|
myColumns.forEach(column => {
|
|
let prop = column
|
|
let formatter = cellFormat
|
|
// Adapt column name and property name
|
|
if (column === 'job') {
|
|
prop = 'job_name'
|
|
} else if (column === 'start time') {
|
|
prop = 'start_time'
|
|
} else if (column === 'change') {
|
|
prop = 'change'
|
|
formatter = linkChangeFormat
|
|
} else if (column === 'result') {
|
|
formatter = linkBuildFormat
|
|
} else if (column === 'duration') {
|
|
formatter = durationFormat
|
|
}
|
|
const label = column.charAt(0).toUpperCase() + column.slice(1)
|
|
this.columns.push({
|
|
header: {label: label, formatters: [headerFormat]},
|
|
property: prop,
|
|
cell: {formatters: [formatter]}
|
|
})
|
|
if (prop !== 'start_time' && prop !== 'ref_url' && prop !== 'duration'
|
|
&& prop !== 'log_url' && prop !== 'uuid') {
|
|
this.filterTypes.push({
|
|
id: prop,
|
|
title: label,
|
|
placeholder: 'Filter by ' + label,
|
|
filterType: 'text',
|
|
})
|
|
}
|
|
})
|
|
// Add build filter at the end
|
|
this.filterTypes.push({
|
|
id: 'uuid',
|
|
title: 'Build',
|
|
placeholder: 'Filter by Build UUID',
|
|
filterType: 'text',
|
|
})
|
|
}
|
|
|
|
renderTable (builds) {
|
|
return (
|
|
<Table.PfProvider
|
|
striped
|
|
bordered
|
|
columns={this.columns}
|
|
>
|
|
<Table.Header/>
|
|
<Table.Body
|
|
rows={builds}
|
|
rowKey='uuid'
|
|
onRow={(row) => {
|
|
switch (row.result) {
|
|
case 'SUCCESS':
|
|
return { className: 'success' }
|
|
default:
|
|
return { className: 'warning' }
|
|
}
|
|
}} />
|
|
</Table.PfProvider>)
|
|
}
|
|
|
|
render() {
|
|
const { builds } = this.state
|
|
return (
|
|
<React.Fragment>
|
|
{this.renderFilter()}
|
|
{builds ? this.renderTable(builds) : <p>Loading...</p>}
|
|
</React.Fragment>
|
|
)
|
|
}
|
|
}
|
|
|
|
export default connect(state => ({tenant: state.tenant}))(BuildsPage)
|