Browse Source

Merge "web: add project page"

tags/3.4.0
Zuul 5 months ago
parent
commit
57d0829d97

+ 4
- 0
web/src/api.js View File

@@ -128,6 +128,9 @@ function fetchBuilds (apiPrefix, queryString) {
128 128
   }
129 129
   return Axios.get(apiUrl + apiPrefix + path)
130 130
 }
131
+function fetchProject (apiPrefix, projectName) {
132
+  return Axios.get(apiUrl + apiPrefix + 'project/' + projectName)
133
+}
131 134
 function fetchProjects (apiPrefix) {
132 135
   return Axios.get(apiUrl + apiPrefix + 'projects')
133 136
 }
@@ -146,6 +149,7 @@ export {
146 149
   fetchStatus,
147 150
   fetchBuild,
148 151
   fetchBuilds,
152
+  fetchProject,
149 153
   fetchProjects,
150 154
   fetchJob,
151 155
   fetchJobs,

+ 77
- 0
web/src/containers/project/Project.jsx View File

@@ -0,0 +1,77 @@
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 * as React from 'react'
16
+import PropTypes from 'prop-types'
17
+import {
18
+  Nav,
19
+  NavItem,
20
+  TabContainer,
21
+  TabPane,
22
+  TabContent,
23
+} from 'patternfly-react'
24
+
25
+import ProjectVariant from './ProjectVariant'
26
+
27
+
28
+class Project extends React.Component {
29
+  static propTypes = {
30
+    project: PropTypes.object.isRequired,
31
+  }
32
+
33
+  state = {
34
+    variantIdx: 0,
35
+  }
36
+
37
+  renderVariantTitle (variant, selected) {
38
+    let title = variant.default_branch
39
+    if (selected) {
40
+      title = <strong>{title}</strong>
41
+    }
42
+    return title
43
+  }
44
+
45
+  render () {
46
+    const { project } = this.props
47
+    const { variantIdx } = this.state
48
+
49
+    return (
50
+      <React.Fragment>
51
+        <h2>{project.canonical_name}</h2>
52
+        <TabContainer id="zuul-project">
53
+          <div>
54
+            <Nav bsClass="nav nav-tabs nav-tabs-pf">
55
+              {project.configs.map((variant, idx) => (
56
+                <NavItem
57
+                  key={idx}
58
+                  onClick={() => this.setState({variantIdx: idx})}>
59
+                  <div>
60
+                    {this.renderVariantTitle(variant, variantIdx === idx)}
61
+                  </div>
62
+                </NavItem>
63
+              ))}
64
+            </Nav>
65
+            <TabContent>
66
+              <TabPane>
67
+                <ProjectVariant variant={project.configs[variantIdx]} />
68
+              </TabPane>
69
+            </TabContent>
70
+          </div>
71
+        </TabContainer>
72
+      </React.Fragment>
73
+    )
74
+  }
75
+}
76
+
77
+export default Project

+ 81
- 0
web/src/containers/project/ProjectVariant.jsx View File

@@ -0,0 +1,81 @@
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 * as React from 'react'
16
+import PropTypes from 'prop-types'
17
+import { connect } from 'react-redux'
18
+import { Link } from 'react-router-dom'
19
+
20
+
21
+class ProjectVariant extends React.Component {
22
+  static propTypes = {
23
+    tenant: PropTypes.object,
24
+    variant: PropTypes.object.isRequired
25
+  }
26
+
27
+  render () {
28
+    const { tenant, variant } = this.props
29
+    const rows = []
30
+
31
+    rows.push({label: 'Merge mode', value: variant.merge_mode})
32
+
33
+    if (variant.templates.length > 0) {
34
+      const templateList = (
35
+        <ul className='list-group'>
36
+          {variant.templates.map((item, idx) => (
37
+            <li className='list-group-item' key={idx}>{item}</li>))}
38
+        </ul>
39
+      )
40
+      rows.push({label: 'Templates', value: templateList})
41
+    }
42
+
43
+    variant.pipelines.forEach(pipeline => {
44
+      // TODO: either adds job link anchor to load the right variant
45
+      // and/or show the job variant config in a modal?
46
+      const jobList = (
47
+        <React.Fragment>
48
+          {pipeline.queue_name && (
49
+            <p><strong>Queue: </strong> {pipeline.queue_name} </p>)}
50
+          <ul className='list-group'>
51
+            {pipeline.jobs.map((item, idx) => (
52
+              <li className='list-group-item' key={idx}>
53
+                <Link to={tenant.linkPrefix + '/job/' + item[0].name}>
54
+                  {item[0].name}
55
+                </Link>
56
+              </li>
57
+            ))}
58
+          </ul>
59
+        </React.Fragment>
60
+      )
61
+      rows.push({label: pipeline.name + ' jobs', value: jobList})
62
+    })
63
+
64
+    return (
65
+      <div>
66
+        <table className='table table-striped table-bordered'>
67
+          <tbody>
68
+            {rows.map(item => (
69
+              <tr key={item.label}>
70
+                <td style={{width: '10%'}}>{item.label}</td>
71
+                <td>{item.value}</td>
72
+              </tr>
73
+            ))}
74
+          </tbody>
75
+        </table>
76
+      </div>
77
+    )
78
+  }
79
+}
80
+
81
+export default connect(state => ({tenant: state.tenant}))(ProjectVariant)

+ 99
- 0
web/src/pages/Project.jsx View File

@@ -0,0 +1,99 @@
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 * as React from 'react'
16
+import { connect } from 'react-redux'
17
+import PropTypes from 'prop-types'
18
+
19
+import Project from '../containers/project/Project'
20
+import { fetchProject } from '../api'
21
+
22
+
23
+class ProjectPage extends React.Component {
24
+  static propTypes = {
25
+    match: PropTypes.object.isRequired,
26
+    tenant: PropTypes.object
27
+  }
28
+
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
+      })
71
+  }
72
+
73
+  componentDidMount () {
74
+    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
+    }
86
+  }
87
+
88
+  render () {
89
+    const { project } = this.state
90
+    if (!project) {
91
+      return (<p>Loading...</p>)
92
+    }
93
+    return (
94
+      <Project project={project} />
95
+    )
96
+  }
97
+}
98
+
99
+export default connect(state => ({tenant: state.tenant}))(ProjectPage)

+ 3
- 1
web/src/pages/Projects.jsx View File

@@ -51,7 +51,9 @@ class ProjectsPage extends Refreshable {
51 51
       <Table.Cell>{value}</Table.Cell>)
52 52
     const cellProjectFormat = (value) => (
53 53
       <Table.Cell>
54
-        {value}
54
+        <Link to={this.props.tenant.linkPrefix + '/project/' + value}>
55
+          {value}
56
+        </Link>
55 57
       </Table.Cell>)
56 58
     const cellBuildFormat = (value) => (
57 59
       <Table.Cell>

+ 5
- 0
web/src/routes.js View File

@@ -14,6 +14,7 @@
14 14
 
15 15
 import StatusPage from './pages/Status'
16 16
 import ChangeStatusPage from './pages/ChangeStatus'
17
+import ProjectPage from './pages/Project'
17 18
 import ProjectsPage from './pages/Projects'
18 19
 import JobPage from './pages/Job'
19 20
 import JobsPage from './pages/Jobs'
@@ -56,6 +57,10 @@ const routes = () => [
56 57
     to: '/stream/:buildId',
57 58
     component: StreamPage
58 59
   },
60
+  {
61
+    to: '/project/:projectName*',
62
+    component: ProjectPage
63
+  },
59 64
   {
60 65
     to: '/job/:jobName',
61 66
     component: JobPage

Loading…
Cancel
Save