web: refactor build page to use a reducer

This change updates the build page component to dispatch reducer
action instead of direct axios call. This enables using the generic
error reducers as well as keeping the builds in the store to avoid
repeated query.

Change-Id: I705514e5fa32ec2d81a5b0ca9c5ebb7d8ac6ac2a
This commit is contained in:
Tristan Cacqueray 2018-12-13 06:03:25 +00:00
parent d061c33415
commit 342c29c994
4 changed files with 135 additions and 37 deletions

60
web/src/actions/build.js Normal file
View File

@ -0,0 +1,60 @@
// 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 API from '../api'
export const BUILD_FETCH_REQUEST = 'BUILD_FETCH_REQUEST'
export const BUILD_FETCH_SUCCESS = 'BUILD_FETCH_SUCCESS'
export const BUILD_FETCH_FAIL = 'BUILD_FETCH_FAIL'
export const requestBuild = () => ({
type: BUILD_FETCH_REQUEST
})
export const receiveBuild = (buildId, build) => ({
type: BUILD_FETCH_SUCCESS,
buildId: buildId,
build: build,
receivedAt: Date.now()
})
const failedBuild = error => ({
type: BUILD_FETCH_FAIL,
error
})
const fetchBuild = (tenant, build) => dispatch => {
dispatch(requestBuild())
return API.fetchBuild(tenant.apiPrefix, build)
.then(response => dispatch(receiveBuild(build, response.data)))
.catch(error => dispatch(failedBuild(error)))
}
const shouldFetchBuild = (buildId, state) => {
const build = state.build.builds[buildId]
if (!build) {
return true
}
if (build.isFetching) {
return false
}
return false
}
export const fetchBuildIfNeeded = (tenant, buildId, force) => (
dispatch, getState) => {
if (force || shouldFetchBuild(buildId, getState())) {
return dispatch(fetchBuild(tenant, buildId))
}
}

View File

@ -18,41 +18,30 @@ import PropTypes from 'prop-types'
import { Link } from 'react-router-dom'
import { Panel } from 'react-bootstrap'
import { fetchBuild } from '../api'
import { fetchBuildIfNeeded } from '../actions/build'
import Refreshable from '../containers/Refreshable'
class BuildPage extends React.Component {
class BuildPage extends Refreshable {
static propTypes = {
match: PropTypes.object.isRequired,
remoteData: PropTypes.object,
tenant: PropTypes.object
}
state = {
build: null
}
updateData = () => {
fetchBuild(this.props.tenant.apiPrefix, this.props.match.params.buildId)
.then(response => {
this.setState({build: response.data})
})
updateData = (force) => {
this.props.dispatch(fetchBuildIfNeeded(
this.props.tenant, this.props.match.params.buildId, force))
}
componentDidMount () {
document.title = 'Zuul Build'
if (this.props.tenant.name) {
this.updateData()
}
}
componentDidUpdate (prevProps) {
if (this.props.tenant.name !== prevProps.tenant.name) {
this.updateData()
}
super.componentDidMount()
}
render () {
const { build } = this.state
const { remoteData } = this.props
const build = remoteData.builds[this.props.match.params.buildId]
if (!build) {
return (<p>Loading...</p>)
}
@ -95,23 +84,31 @@ class BuildPage extends React.Component {
}
})
return (
<Panel>
<Panel.Heading>Build result {build.uuid}</Panel.Heading>
<Panel.Body>
<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>
</Panel.Body>
</Panel>
<React.Fragment>
<div style={{float: 'right'}}>
{this.renderSpinner()}
</div>
<Panel>
<Panel.Heading>Build result {build.uuid}</Panel.Heading>
<Panel.Body>
<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>
</Panel.Body>
</Panel>
</React.Fragment>
)
}
}
export default connect(state => ({tenant: state.tenant}))(BuildPage)
export default connect(state => ({
tenant: state.tenant,
remoteData: state.build,
}))(BuildPage)

39
web/src/reducers/build.js Normal file
View File

@ -0,0 +1,39 @@
// 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 {
BUILD_FETCH_FAIL,
BUILD_FETCH_REQUEST,
BUILD_FETCH_SUCCESS
} from '../actions/build'
import update from 'immutability-helper'
export default (state = {
isFetching: false,
builds: {},
}, action) => {
switch (action.type) {
case BUILD_FETCH_REQUEST:
return update(state, {$merge: {isFetching: true}})
case BUILD_FETCH_SUCCESS:
state.builds = update(
state.builds, {$merge: {[action.buildId]: action.build}})
return update(state, {$merge: {isFetching: false}})
case BUILD_FETCH_FAIL:
return update(state, {$merge: {isFetching: false}})
default:
return state
}
}

View File

@ -17,6 +17,7 @@ import { combineReducers } from 'redux'
import configErrors from './configErrors'
import change from './change'
import errors from './errors'
import build from './build'
import info from './info'
import job from './job'
import jobs from './jobs'
@ -30,6 +31,7 @@ import tenants from './tenants'
const reducers = {
change,
build,
info,
job,
jobs,