summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuul <zuul@review.openstack.org>2018-12-31 10:12:59 +0000
committerGerrit Code Review <review@openstack.org>2018-12-31 10:12:59 +0000
commite6b854d2e5dace9ba78db94987f6c77890646482 (patch)
treed00c669f2b33567ec188ff2a56331b42c36d5d1f
parent57d0829d97d4679eed2c6fe47554c2a162cc7679 (diff)
parentf4b774957f6ed803007485134a2795bf73c0d19b (diff)
Merge "web: refactor job page to use a reducer"
-rw-r--r--web/src/actions/project.js99
-rw-r--r--web/src/pages/Project.jsx86
-rw-r--r--web/src/reducers/index.js2
-rw-r--r--web/src/reducers/project.js55
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 0000000..f2c64ad
--- /dev/null
+++ b/web/src/actions/project.js
@@ -0,0 +1,99 @@
1/* global Promise */
2// Copyright 2018 Red Hat, Inc
3//
4// Licensed under the Apache License, Version 2.0 (the "License"); you may
5// not use this file except in compliance with the License. You may obtain
6// a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13// License for the specific language governing permissions and limitations
14// under the License.
15
16import * as API from '../api'
17
18export const PROJECT_FETCH_REQUEST = 'PROJECT_FETCH_REQUEST'
19export const PROJECT_FETCH_SUCCESS = 'PROJECT_FETCH_SUCCESS'
20export const PROJECT_FETCH_FAIL = 'PROJECT_FETCH_FAIL'
21
22export const requestProject = () => ({
23 type: PROJECT_FETCH_REQUEST
24})
25
26export const receiveProject = (tenant, projectName, project) => {
27 // TODO: fix api to return template name or merge them
28 // in the mean-time, merge the jobs in project configs
29 const templateIdx = []
30 let idx
31 project.configs.forEach((config, idx) => {
32 if (config.default_branch === null) {
33 // This must be a template
34 templateIdx.push(idx)
35 config.pipelines.forEach(templatePipeline => {
36 let pipeline = project.configs[idx - 1].pipelines.filter(
37 item => item.name === templatePipeline.name)
38 if (pipeline.length === 0) {
39 // Pipeline doesn't exist in project config
40 project.configs[idx - 1].pipelines.push(templatePipeline)
41 } else {
42 if (pipeline[0].queue_name === null) {
43 pipeline[0].queue_name = templatePipeline.queue_name
44 }
45 templatePipeline.jobs.forEach(job => {
46 pipeline[0].jobs.push(job)
47 })
48 }
49 })
50 }
51 })
52 for (idx = templateIdx.length - 1; idx >= 0; idx -= 1) {
53 project.configs.splice(templateIdx[idx], 1)
54 }
55
56 return {
57 type: PROJECT_FETCH_SUCCESS,
58 tenant: tenant,
59 projectName: projectName,
60 project: project,
61 receivedAt: Date.now(),
62 }
63}
64
65const failedProject = error => ({
66 type: PROJECT_FETCH_FAIL,
67 error
68})
69
70const fetchProject = (tenant, project) => dispatch => {
71 dispatch(requestProject())
72 return API.fetchProject(tenant.apiPrefix, project)
73 .then(response => dispatch(receiveProject(
74 tenant.name, project, response.data)))
75 .catch(error => dispatch(failedProject(error)))
76}
77
78const shouldFetchProject = (tenant, projectName, state) => {
79 const tenantProjects = state.project.projects[tenant.name]
80 if (tenantProjects) {
81 const project = tenantProjects[projectName]
82 if (!project) {
83 return true
84 }
85 if (project.isFetching) {
86 return false
87 }
88 return false
89 }
90 return true
91}
92
93export const fetchProjectIfNeeded = (tenant, project, force) => (
94 dispatch, getState) => {
95 if (force || shouldFetchProject(tenant, project, getState())) {
96 return dispatch(fetchProject(tenant, project))
97 }
98 return Promise.resolve()
99}
diff --git a/web/src/pages/Project.jsx b/web/src/pages/Project.jsx
index de0659b..76c9ec0 100644
--- a/web/src/pages/Project.jsx
+++ b/web/src/pages/Project.jsx
@@ -17,83 +17,45 @@ import { connect } from 'react-redux'
17import PropTypes from 'prop-types' 17import PropTypes from 'prop-types'
18 18
19import Project from '../containers/project/Project' 19import Project from '../containers/project/Project'
20import { fetchProject } from '../api' 20import { fetchProjectIfNeeded } from '../actions/project'
21import Refreshable from '../containers/Refreshable'
21 22
22 23
23class ProjectPage extends React.Component { 24class ProjectPage extends Refreshable {
24 static propTypes = { 25 static propTypes = {
25 match: PropTypes.object.isRequired, 26 match: PropTypes.object.isRequired,
26 tenant: PropTypes.object 27 tenant: PropTypes.object,
28 remoteData: PropTypes.object,
29 dispatch: PropTypes.func
27 } 30 }
28 31
29 state = { 32 updateData = (force) => {
30 project: null 33 this.props.dispatch(fetchProjectIfNeeded(
31 } 34 this.props.tenant, this.props.match.params.projectName, force))
32
33 fixProjectConfig(project) {
34 let templateIdx = []
35 let idx
36 project.configs.forEach((config, idx) => {
37 if (config.default_branch === null) {
38 // This must be a template
39 templateIdx.push(idx)
40 config.pipelines.forEach(templatePipeline => {
41 let pipeline = project.configs[idx - 1].pipelines.filter(
42 item => item.name === templatePipeline.name)
43 if (pipeline.length === 0) {
44 // Pipeline doesn't exist in project config
45 project.configs[idx - 1].pipelines.push(templatePipeline)
46 } else {
47 if (pipeline[0].queue_name === null) {
48 pipeline[0].queue_name = templatePipeline.queue_name
49 }
50 templatePipeline.jobs.forEach(job => {
51 pipeline[0].jobs.push(job)
52 })
53 }
54 })
55 }
56 })
57 for (idx = templateIdx.length - 1; idx >= 0; idx -= 1) {
58 project.configs.splice(templateIdx[idx], 1)
59 }
60 }
61
62 updateData = () => {
63 fetchProject(
64 this.props.tenant.apiPrefix, this.props.match.params.projectName)
65 .then(response => {
66 // TODO: fix api to return template name or merge them
67 // in the mean-time, merge the jobs in project configs
68 this.fixProjectConfig(response.data)
69 this.setState({project: response.data})
70 })
71 } 35 }
72 36
73 componentDidMount () { 37 componentDidMount () {
74 document.title = 'Zuul Project | ' + this.props.match.params.projectName 38 document.title = 'Zuul Project | ' + this.props.match.params.projectName
75 if (this.props.tenant.name) { 39 super.componentDidMount()
76 this.updateData()
77 }
78 }
79
80 componentDidUpdate (prevProps) {
81 if (this.props.tenant.name !== prevProps.tenant.name ||
82 this.props.match.params.projectName !==
83 prevProps.match.params.projectName) {
84 this.updateData()
85 }
86 } 40 }
87 41
88 render () { 42 render () {
89 const { project } = this.state 43 const { remoteData } = this.props
90 if (!project) { 44 const tenantProjects = remoteData.projects[this.props.tenant.name]
91 return (<p>Loading...</p>) 45 const projectName = this.props.match.params.projectName
92 }
93 return ( 46 return (
94 <Project project={project} /> 47 <React.Fragment>
48 <div style={{float: 'right'}}>
49 {this.renderSpinner()}
50 </div>
51 {tenantProjects && tenantProjects[projectName] &&
52 <Project project={tenantProjects[projectName]} />}
53 </React.Fragment>
95 ) 54 )
96 } 55 }
97} 56}
98 57
99export default connect(state => ({tenant: state.tenant}))(ProjectPage) 58export default connect(state => ({
59 tenant: state.tenant,
60 remoteData: state.project,
61}))(ProjectPage)
diff --git a/web/src/reducers/index.js b/web/src/reducers/index.js
index 4089f27..69b24d6 100644
--- a/web/src/reducers/index.js
+++ b/web/src/reducers/index.js
@@ -20,6 +20,7 @@ import errors from './errors'
20import info from './info' 20import info from './info'
21import job from './job' 21import job from './job'
22import jobs from './jobs' 22import jobs from './jobs'
23import project from './project'
23import projects from './projects' 24import projects from './projects'
24import status from './status' 25import status from './status'
25import tenant from './tenant' 26import tenant from './tenant'
@@ -30,6 +31,7 @@ const reducers = {
30 info, 31 info,
31 job, 32 job,
32 jobs, 33 jobs,
34 project,
33 projects, 35 projects,
34 configErrors, 36 configErrors,
35 errors, 37 errors,
diff --git a/web/src/reducers/project.js b/web/src/reducers/project.js
new file mode 100644
index 0000000..d148f25
--- /dev/null
+++ b/web/src/reducers/project.js
@@ -0,0 +1,55 @@
1// Copyright 2018 Red Hat, Inc
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may
4// not use this file except in compliance with the License. You may obtain
5// a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations
13// under the License.
14
15import {
16 PROJECT_FETCH_FAIL,
17 PROJECT_FETCH_REQUEST,
18 PROJECT_FETCH_SUCCESS
19} from '../actions/project'
20
21import update from 'immutability-helper'
22
23export default (state = {
24 isFetching: false,
25 projects: {},
26}, action) => {
27 switch (action.type) {
28 case PROJECT_FETCH_REQUEST:
29 return {
30 isFetching: true,
31 projects: state.projects,
32 }
33 case PROJECT_FETCH_SUCCESS:
34 if (!state.projects[action.tenant]) {
35 state.projects = update(state.projects, {$merge: {[action.tenant]: {}}})
36 }
37 return {
38 isFetching: false,
39 projects: update(state.projects, {
40 [action.tenant]: {
41 $merge: {
42 [action.projectName]: action.project
43 }
44 }
45 })
46 }
47 case PROJECT_FETCH_FAIL:
48 return {
49 isFetching: false,
50 projects: state.projects,
51 }
52 default:
53 return state
54 }
55}