web: refactor job page to use a reducer

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

Change-Id: I142493c3a89379a75d97d247fbe1fcc0858e9723
This commit is contained in:
Tristan Cacqueray 2018-12-06 07:20:51 +00:00
parent 839fe0cceb
commit 51e5259477
5 changed files with 148 additions and 22 deletions

67
web/src/actions/job.js Normal file
View File

@ -0,0 +1,67 @@
/* global Promise */
// 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 JOB_FETCH_REQUEST = 'JOB_FETCH_REQUEST'
export const JOB_FETCH_SUCCESS = 'JOB_FETCH_SUCCESS'
export const JOB_FETCH_FAIL = 'JOB_FETCH_FAIL'
export const requestJob = () => ({
type: JOB_FETCH_REQUEST
})
export const receiveJob = (tenant, jobname, json) => ({
type: JOB_FETCH_SUCCESS,
tenant: tenant,
jobname: jobname,
job: json,
receivedAt: Date.now()
})
const failedJob = error => ({
type: JOB_FETCH_FAIL,
error
})
const fetchJob = (tenant, jobname) => dispatch => {
dispatch(requestJob())
return API.fetchJob(tenant.apiPrefix, jobname)
.then(response => dispatch(receiveJob(tenant.name, jobname, response.data)))
.catch(error => dispatch(failedJob(error)))
}
const shouldFetchJob = (tenant, jobname, state) => {
const tenantJobs = state.job.jobs[tenant.name]
if (tenantJobs) {
const job = tenantJobs[jobname]
if (!job) {
return true
}
if (job.isFetching) {
return false
}
return false
}
return true
}
export const fetchJobIfNeeded = (tenant, jobname, force) => (
dispatch, getState) => {
if (force || shouldFetchJob(tenant, jobname, getState())) {
return dispatch(fetchJob(tenant, jobname))
}
return Promise.resolve()
}

View File

@ -17,31 +17,26 @@ import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import Job from '../containers/job/Job'
import { fetchJob } from '../api'
import Refreshable from '../containers/Refreshable'
import { fetchJobIfNeeded } from '../actions/job'
class JobPage extends React.Component {
class JobPage extends Refreshable {
static propTypes = {
match: PropTypes.object.isRequired,
tenant: PropTypes.object
tenant: PropTypes.object,
remoteData: PropTypes.object,
dispatch: PropTypes.func
}
state = {
job: null
}
updateData = () => {
fetchJob(this.props.tenant.apiPrefix, this.props.match.params.jobName)
.then(response => {
this.setState({job: response.data})
})
updateData = (force) => {
this.props.dispatch(fetchJobIfNeeded(
this.props.tenant, this.props.match.params.jobName, force))
}
componentDidMount () {
document.title = 'Zuul Job | ' + this.props.match.params.jobName
if (this.props.tenant.name) {
this.updateData()
}
super.componentDidMount()
}
componentDidUpdate (prevProps) {
@ -52,14 +47,21 @@ class JobPage extends React.Component {
}
render () {
const { job } = this.state
if (!job) {
return (<p>Loading...</p>)
}
const { remoteData } = this.props
const tenantJobs = remoteData.jobs[this.props.tenant.name]
const jobName = this.props.match.params.jobName
return (
<Job job={job} />
<React.Fragment>
<div style={{float: 'right'}}>
{this.renderSpinner()}
</div>
{tenantJobs && tenantJobs[jobName] && <Job job={tenantJobs[jobName]} />}
</React.Fragment>
)
}
}
export default connect(state => ({tenant: state.tenant}))(JobPage)
export default connect(state => ({
tenant: state.tenant,
remoteData: state.job,
}))(JobPage)

View File

@ -42,7 +42,7 @@ class JobsPage extends Refreshable {
const jobs = remoteData.jobs[this.props.tenant.name]
return (
<React.Fragment>
<div className="pull-right" style={{display: 'flex'}}>
<div style={{float: 'right'}}>
{this.renderSpinner()}
</div>
{jobs && jobs.length > 0 &&

View File

@ -17,12 +17,14 @@ import { combineReducers } from 'redux'
import configErrors from './configErrors'
import errors from './errors'
import info from './info'
import job from './job'
import jobs from './jobs'
import status from './status'
import tenant from './tenant'
const reducers = {
info,
job,
jobs,
configErrors,
errors,

55
web/src/reducers/job.js Normal file
View File

@ -0,0 +1,55 @@
// 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 {
JOB_FETCH_FAIL,
JOB_FETCH_REQUEST,
JOB_FETCH_SUCCESS
} from '../actions/job'
import update from 'immutability-helper'
export default (state = {
isFetching: false,
jobs: {},
}, action) => {
switch (action.type) {
case JOB_FETCH_REQUEST:
return {
isFetching: true,
jobs: state.jobs,
}
case JOB_FETCH_SUCCESS:
if (!state.jobs[action.tenant]) {
state.jobs = update(state.jobs, {$merge: {[action.tenant]: {}}})
}
return {
isFetching: false,
jobs: update(state.jobs, {
[action.tenant]: {
$merge: {
[action.jobname]: action.job
}
}
})
}
case JOB_FETCH_FAIL:
return {
isFetching: false,
jobs: state.jobs,
}
default:
return state
}
}