summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuul <zuul@review.openstack.org>2018-12-31 05:31:14 +0000
committerGerrit Code Review <review@openstack.org>2018-12-31 05:31:14 +0000
commit57d0829d97d4679eed2c6fe47554c2a162cc7679 (patch)
tree1af5522c64c33343556b530db5ee6121704889ab
parent572065397074837b4137fb3d3b3404deb50d41ea (diff)
parentd3d0a08eb7f1be63813aa2ea1ce73ac587894c1a (diff)
Merge "web: add project page"
-rw-r--r--web/src/api.js4
-rw-r--r--web/src/containers/project/Project.jsx77
-rw-r--r--web/src/containers/project/ProjectVariant.jsx81
-rw-r--r--web/src/pages/Project.jsx99
-rw-r--r--web/src/pages/Projects.jsx4
-rw-r--r--web/src/routes.js5
6 files changed, 269 insertions, 1 deletions
diff --git a/web/src/api.js b/web/src/api.js
index 409aec8..51798ca 100644
--- a/web/src/api.js
+++ b/web/src/api.js
@@ -128,6 +128,9 @@ function fetchBuilds (apiPrefix, queryString) {
128 } 128 }
129 return Axios.get(apiUrl + apiPrefix + path) 129 return Axios.get(apiUrl + apiPrefix + path)
130} 130}
131function fetchProject (apiPrefix, projectName) {
132 return Axios.get(apiUrl + apiPrefix + 'project/' + projectName)
133}
131function fetchProjects (apiPrefix) { 134function fetchProjects (apiPrefix) {
132 return Axios.get(apiUrl + apiPrefix + 'projects') 135 return Axios.get(apiUrl + apiPrefix + 'projects')
133} 136}
@@ -146,6 +149,7 @@ export {
146 fetchStatus, 149 fetchStatus,
147 fetchBuild, 150 fetchBuild,
148 fetchBuilds, 151 fetchBuilds,
152 fetchProject,
149 fetchProjects, 153 fetchProjects,
150 fetchJob, 154 fetchJob,
151 fetchJobs, 155 fetchJobs,
diff --git a/web/src/containers/project/Project.jsx b/web/src/containers/project/Project.jsx
new file mode 100644
index 0000000..a522c06
--- /dev/null
+++ b/web/src/containers/project/Project.jsx
@@ -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
15import * as React from 'react'
16import PropTypes from 'prop-types'
17import {
18 Nav,
19 NavItem,
20 TabContainer,
21 TabPane,
22 TabContent,
23} from 'patternfly-react'
24
25import ProjectVariant from './ProjectVariant'
26
27
28class 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
77export default Project
diff --git a/web/src/containers/project/ProjectVariant.jsx b/web/src/containers/project/ProjectVariant.jsx
new file mode 100644
index 0000000..74be10a
--- /dev/null
+++ b/web/src/containers/project/ProjectVariant.jsx
@@ -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
15import * as React from 'react'
16import PropTypes from 'prop-types'
17import { connect } from 'react-redux'
18import { Link } from 'react-router-dom'
19
20
21class 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
81export default connect(state => ({tenant: state.tenant}))(ProjectVariant)
diff --git a/web/src/pages/Project.jsx b/web/src/pages/Project.jsx
new file mode 100644
index 0000000..de0659b
--- /dev/null
+++ b/web/src/pages/Project.jsx
@@ -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
15import * as React from 'react'
16import { connect } from 'react-redux'
17import PropTypes from 'prop-types'
18
19import Project from '../containers/project/Project'
20import { fetchProject } from '../api'
21
22
23class 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
99export default connect(state => ({tenant: state.tenant}))(ProjectPage)
diff --git a/web/src/pages/Projects.jsx b/web/src/pages/Projects.jsx
index 06c0960..5a43338 100644
--- a/web/src/pages/Projects.jsx
+++ b/web/src/pages/Projects.jsx
@@ -51,7 +51,9 @@ class ProjectsPage extends Refreshable {
51 <Table.Cell>{value}</Table.Cell>) 51 <Table.Cell>{value}</Table.Cell>)
52 const cellProjectFormat = (value) => ( 52 const cellProjectFormat = (value) => (
53 <Table.Cell> 53 <Table.Cell>
54 {value} 54 <Link to={this.props.tenant.linkPrefix + '/project/' + value}>
55 {value}
56 </Link>
55 </Table.Cell>) 57 </Table.Cell>)
56 const cellBuildFormat = (value) => ( 58 const cellBuildFormat = (value) => (
57 <Table.Cell> 59 <Table.Cell>
diff --git a/web/src/routes.js b/web/src/routes.js
index 77aad78..cd58dfe 100644
--- a/web/src/routes.js
+++ b/web/src/routes.js
@@ -14,6 +14,7 @@
14 14
15import StatusPage from './pages/Status' 15import StatusPage from './pages/Status'
16import ChangeStatusPage from './pages/ChangeStatus' 16import ChangeStatusPage from './pages/ChangeStatus'
17import ProjectPage from './pages/Project'
17import ProjectsPage from './pages/Projects' 18import ProjectsPage from './pages/Projects'
18import JobPage from './pages/Job' 19import JobPage from './pages/Job'
19import JobsPage from './pages/Jobs' 20import JobsPage from './pages/Jobs'
@@ -57,6 +58,10 @@ const routes = () => [
57 component: StreamPage 58 component: StreamPage
58 }, 59 },
59 { 60 {
61 to: '/project/:projectName*',
62 component: ProjectPage
63 },
64 {
60 to: '/job/:jobName', 65 to: '/job/:jobName',
61 component: JobPage 66 component: JobPage
62 }, 67 },