Browse Source

Merge "web: refactor job page to use a reducer"

tags/3.4.0
Zuul 5 months ago
parent
commit
e6b854d2e5
4 changed files with 180 additions and 62 deletions
  1. 99
    0
      web/src/actions/project.js
  2. 24
    62
      web/src/pages/Project.jsx
  3. 2
    0
      web/src/reducers/index.js
  4. 55
    0
      web/src/reducers/project.js

+ 99
- 0
web/src/actions/project.js View File

@@ -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
+
16
+import * as API from '../api'
17
+
18
+export const PROJECT_FETCH_REQUEST = 'PROJECT_FETCH_REQUEST'
19
+export const PROJECT_FETCH_SUCCESS = 'PROJECT_FETCH_SUCCESS'
20
+export const PROJECT_FETCH_FAIL = 'PROJECT_FETCH_FAIL'
21
+
22
+export const requestProject = () => ({
23
+  type: PROJECT_FETCH_REQUEST
24
+})
25
+
26
+export 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
+
65
+const failedProject = error => ({
66
+  type: PROJECT_FETCH_FAIL,
67
+  error
68
+})
69
+
70
+const 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
+
78
+const 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
+
93
+export 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
+}

+ 24
- 62
web/src/pages/Project.jsx View File

@@ -17,83 +17,45 @@ import { connect } from 'react-redux'
17 17
 import PropTypes from 'prop-types'
18 18
 
19 19
 import Project from '../containers/project/Project'
20
-import { fetchProject } from '../api'
20
+import { fetchProjectIfNeeded } from '../actions/project'
21
+import Refreshable from '../containers/Refreshable'
21 22
 
22 23
 
23
-class ProjectPage extends React.Component {
24
+class ProjectPage extends Refreshable {
24 25
   static propTypes = {
25 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 = {
30
-    project: null
31
-  }
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
-      })
32
+  updateData = (force) => {
33
+    this.props.dispatch(fetchProjectIfNeeded(
34
+      this.props.tenant, this.props.match.params.projectName, force))
71 35
   }
72 36
 
73 37
   componentDidMount () {
74 38
     document.title = 'Zuul Project | ' + this.props.match.params.projectName
75
-    if (this.props.tenant.name) {
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
-    }
39
+    super.componentDidMount()
86 40
   }
87 41
 
88 42
   render () {
89
-    const { project } = this.state
90
-    if (!project) {
91
-      return (<p>Loading...</p>)
92
-    }
43
+    const { remoteData } = this.props
44
+    const tenantProjects = remoteData.projects[this.props.tenant.name]
45
+    const projectName = this.props.match.params.projectName
93 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
 
99
-export default connect(state => ({tenant: state.tenant}))(ProjectPage)
58
+export default connect(state => ({
59
+  tenant: state.tenant,
60
+  remoteData: state.project,
61
+}))(ProjectPage)

+ 2
- 0
web/src/reducers/index.js View File

@@ -20,6 +20,7 @@ import errors from './errors'
20 20
 import info from './info'
21 21
 import job from './job'
22 22
 import jobs from './jobs'
23
+import project from './project'
23 24
 import projects from './projects'
24 25
 import status from './status'
25 26
 import tenant from './tenant'
@@ -30,6 +31,7 @@ const reducers = {
30 31
   info,
31 32
   job,
32 33
   jobs,
34
+  project,
33 35
   projects,
34 36
   configErrors,
35 37
   errors,

+ 55
- 0
web/src/reducers/project.js View File

@@ -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
+
15
+import {
16
+  PROJECT_FETCH_FAIL,
17
+  PROJECT_FETCH_REQUEST,
18
+  PROJECT_FETCH_SUCCESS
19
+} from '../actions/project'
20
+
21
+import update from 'immutability-helper'
22
+
23
+export 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
+}

Loading…
Cancel
Save