diff options
author | Tristan Cacqueray <tdecacqu@redhat.com> | 2018-12-14 05:07:09 +0000 |
---|---|---|
committer | Tristan Cacqueray <tdecacqu@redhat.com> | 2018-12-14 08:33:48 +0000 |
commit | f4b774957f6ed803007485134a2795bf73c0d19b (patch) | |
tree | 34a24f4eb5817af2ecfbc47df42f02e371ef4ad8 | |
parent | d3d0a08eb7f1be63813aa2ea1ce73ac587894c1a (diff) | |
download | zuul-f4b774957f6ed803007485134a2795bf73c0d19b.tar.gz |
web: refactor job page to use a reducer
This change updates the project page component to dispatch reducer
action instead of direct axios call. This enables using the generic
error reducers as well as keeping the tenant projects in the store to
avoid repeated query.
Change-Id: Ib91e0f338fd87c57534a90d7168f0dab98f2df62
-rw-r--r-- | web/src/actions/project.js | 99 | ||||
-rw-r--r-- | web/src/pages/Project.jsx | 86 | ||||
-rw-r--r-- | web/src/reducers/index.js | 2 | ||||
-rw-r--r-- | web/src/reducers/project.js | 55 |
4 files changed, 180 insertions, 62 deletions
diff --git a/web/src/actions/project.js b/web/src/actions/project.js new file mode 100644 index 000000000..f2c64adb6 --- /dev/null +++ b/web/src/actions/project.js @@ -0,0 +1,99 @@ +/* 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 PROJECT_FETCH_REQUEST = 'PROJECT_FETCH_REQUEST' +export const PROJECT_FETCH_SUCCESS = 'PROJECT_FETCH_SUCCESS' +export const PROJECT_FETCH_FAIL = 'PROJECT_FETCH_FAIL' + +export const requestProject = () => ({ + type: PROJECT_FETCH_REQUEST +}) + +export const receiveProject = (tenant, projectName, project) => { + // TODO: fix api to return template name or merge them + // in the mean-time, merge the jobs in project configs + const templateIdx = [] + let idx + project.configs.forEach((config, idx) => { + if (config.default_branch === null) { + // This must be a template + templateIdx.push(idx) + config.pipelines.forEach(templatePipeline => { + let pipeline = project.configs[idx - 1].pipelines.filter( + item => item.name === templatePipeline.name) + if (pipeline.length === 0) { + // Pipeline doesn't exist in project config + project.configs[idx - 1].pipelines.push(templatePipeline) + } else { + if (pipeline[0].queue_name === null) { + pipeline[0].queue_name = templatePipeline.queue_name + } + templatePipeline.jobs.forEach(job => { + pipeline[0].jobs.push(job) + }) + } + }) + } + }) + for (idx = templateIdx.length - 1; idx >= 0; idx -= 1) { + project.configs.splice(templateIdx[idx], 1) + } + + return { + type: PROJECT_FETCH_SUCCESS, + tenant: tenant, + projectName: projectName, + project: project, + receivedAt: Date.now(), + } +} + +const failedProject = error => ({ + type: PROJECT_FETCH_FAIL, + error +}) + +const fetchProject = (tenant, project) => dispatch => { + dispatch(requestProject()) + return API.fetchProject(tenant.apiPrefix, project) + .then(response => dispatch(receiveProject( + tenant.name, project, response.data))) + .catch(error => dispatch(failedProject(error))) +} + +const shouldFetchProject = (tenant, projectName, state) => { + const tenantProjects = state.project.projects[tenant.name] + if (tenantProjects) { + const project = tenantProjects[projectName] + if (!project) { + return true + } + if (project.isFetching) { + return false + } + return false + } + return true +} + +export const fetchProjectIfNeeded = (tenant, project, force) => ( + dispatch, getState) => { + if (force || shouldFetchProject(tenant, project, getState())) { + return dispatch(fetchProject(tenant, project)) + } + return Promise.resolve() +} diff --git a/web/src/pages/Project.jsx b/web/src/pages/Project.jsx index de0659bd6..76c9ec06c 100644 --- a/web/src/pages/Project.jsx +++ b/web/src/pages/Project.jsx @@ -17,83 +17,45 @@ import { connect } from 'react-redux' import PropTypes from 'prop-types' import Project from '../containers/project/Project' -import { fetchProject } from '../api' +import { fetchProjectIfNeeded } from '../actions/project' +import Refreshable from '../containers/Refreshable' -class ProjectPage extends React.Component { +class ProjectPage extends Refreshable { static propTypes = { match: PropTypes.object.isRequired, - tenant: PropTypes.object + tenant: PropTypes.object, + remoteData: PropTypes.object, + dispatch: PropTypes.func } - state = { - project: null - } - - fixProjectConfig(project) { - let templateIdx = [] - let idx - project.configs.forEach((config, idx) => { - if (config.default_branch === null) { - // This must be a template - templateIdx.push(idx) - config.pipelines.forEach(templatePipeline => { - let pipeline = project.configs[idx - 1].pipelines.filter( - item => item.name === templatePipeline.name) - if (pipeline.length === 0) { - // Pipeline doesn't exist in project config - project.configs[idx - 1].pipelines.push(templatePipeline) - } else { - if (pipeline[0].queue_name === null) { - pipeline[0].queue_name = templatePipeline.queue_name - } - templatePipeline.jobs.forEach(job => { - pipeline[0].jobs.push(job) - }) - } - }) - } - }) - for (idx = templateIdx.length - 1; idx >= 0; idx -= 1) { - project.configs.splice(templateIdx[idx], 1) - } - } - - updateData = () => { - fetchProject( - this.props.tenant.apiPrefix, this.props.match.params.projectName) - .then(response => { - // TODO: fix api to return template name or merge them - // in the mean-time, merge the jobs in project configs - this.fixProjectConfig(response.data) - this.setState({project: response.data}) - }) + updateData = (force) => { + this.props.dispatch(fetchProjectIfNeeded( + this.props.tenant, this.props.match.params.projectName, force)) } componentDidMount () { document.title = 'Zuul Project | ' + this.props.match.params.projectName - if (this.props.tenant.name) { - this.updateData() - } - } - - componentDidUpdate (prevProps) { - if (this.props.tenant.name !== prevProps.tenant.name || - this.props.match.params.projectName !== - prevProps.match.params.projectName) { - this.updateData() - } + super.componentDidMount() } render () { - const { project } = this.state - if (!project) { - return (<p>Loading...</p>) - } + const { remoteData } = this.props + const tenantProjects = remoteData.projects[this.props.tenant.name] + const projectName = this.props.match.params.projectName return ( - <Project project={project} /> + <React.Fragment> + <div style={{float: 'right'}}> + {this.renderSpinner()} + </div> + {tenantProjects && tenantProjects[projectName] && + <Project project={tenantProjects[projectName]} />} + </React.Fragment> ) } } -export default connect(state => ({tenant: state.tenant}))(ProjectPage) +export default connect(state => ({ + tenant: state.tenant, + remoteData: state.project, +}))(ProjectPage) diff --git a/web/src/reducers/index.js b/web/src/reducers/index.js index 4089f2790..69b24d672 100644 --- a/web/src/reducers/index.js +++ b/web/src/reducers/index.js @@ -20,6 +20,7 @@ import errors from './errors' import info from './info' import job from './job' import jobs from './jobs' +import project from './project' import projects from './projects' import status from './status' import tenant from './tenant' @@ -30,6 +31,7 @@ const reducers = { info, job, jobs, + project, projects, configErrors, errors, diff --git a/web/src/reducers/project.js b/web/src/reducers/project.js new file mode 100644 index 000000000..d148f2589 --- /dev/null +++ b/web/src/reducers/project.js @@ -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 { + PROJECT_FETCH_FAIL, + PROJECT_FETCH_REQUEST, + PROJECT_FETCH_SUCCESS +} from '../actions/project' + +import update from 'immutability-helper' + +export default (state = { + isFetching: false, + projects: {}, +}, action) => { + switch (action.type) { + case PROJECT_FETCH_REQUEST: + return { + isFetching: true, + projects: state.projects, + } + case PROJECT_FETCH_SUCCESS: + if (!state.projects[action.tenant]) { + state.projects = update(state.projects, {$merge: {[action.tenant]: {}}}) + } + return { + isFetching: false, + projects: update(state.projects, { + [action.tenant]: { + $merge: { + [action.projectName]: action.project + } + } + }) + } + case PROJECT_FETCH_FAIL: + return { + isFetching: false, + projects: state.projects, + } + default: + return state + } +} |