Merge "web: add project page"
This commit is contained in:
commit
57d0829d97
|
@ -128,6 +128,9 @@ function fetchBuilds (apiPrefix, queryString) {
|
|||
}
|
||||
return Axios.get(apiUrl + apiPrefix + path)
|
||||
}
|
||||
function fetchProject (apiPrefix, projectName) {
|
||||
return Axios.get(apiUrl + apiPrefix + 'project/' + projectName)
|
||||
}
|
||||
function fetchProjects (apiPrefix) {
|
||||
return Axios.get(apiUrl + apiPrefix + 'projects')
|
||||
}
|
||||
|
@ -146,6 +149,7 @@ export {
|
|||
fetchStatus,
|
||||
fetchBuild,
|
||||
fetchBuilds,
|
||||
fetchProject,
|
||||
fetchProjects,
|
||||
fetchJob,
|
||||
fetchJobs,
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
// 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 React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import {
|
||||
Nav,
|
||||
NavItem,
|
||||
TabContainer,
|
||||
TabPane,
|
||||
TabContent,
|
||||
} from 'patternfly-react'
|
||||
|
||||
import ProjectVariant from './ProjectVariant'
|
||||
|
||||
|
||||
class Project extends React.Component {
|
||||
static propTypes = {
|
||||
project: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
state = {
|
||||
variantIdx: 0,
|
||||
}
|
||||
|
||||
renderVariantTitle (variant, selected) {
|
||||
let title = variant.default_branch
|
||||
if (selected) {
|
||||
title = <strong>{title}</strong>
|
||||
}
|
||||
return title
|
||||
}
|
||||
|
||||
render () {
|
||||
const { project } = this.props
|
||||
const { variantIdx } = this.state
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<h2>{project.canonical_name}</h2>
|
||||
<TabContainer id="zuul-project">
|
||||
<div>
|
||||
<Nav bsClass="nav nav-tabs nav-tabs-pf">
|
||||
{project.configs.map((variant, idx) => (
|
||||
<NavItem
|
||||
key={idx}
|
||||
onClick={() => this.setState({variantIdx: idx})}>
|
||||
<div>
|
||||
{this.renderVariantTitle(variant, variantIdx === idx)}
|
||||
</div>
|
||||
</NavItem>
|
||||
))}
|
||||
</Nav>
|
||||
<TabContent>
|
||||
<TabPane>
|
||||
<ProjectVariant variant={project.configs[variantIdx]} />
|
||||
</TabPane>
|
||||
</TabContent>
|
||||
</div>
|
||||
</TabContainer>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Project
|
|
@ -0,0 +1,81 @@
|
|||
// 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 React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
|
||||
class ProjectVariant extends React.Component {
|
||||
static propTypes = {
|
||||
tenant: PropTypes.object,
|
||||
variant: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
render () {
|
||||
const { tenant, variant } = this.props
|
||||
const rows = []
|
||||
|
||||
rows.push({label: 'Merge mode', value: variant.merge_mode})
|
||||
|
||||
if (variant.templates.length > 0) {
|
||||
const templateList = (
|
||||
<ul className='list-group'>
|
||||
{variant.templates.map((item, idx) => (
|
||||
<li className='list-group-item' key={idx}>{item}</li>))}
|
||||
</ul>
|
||||
)
|
||||
rows.push({label: 'Templates', value: templateList})
|
||||
}
|
||||
|
||||
variant.pipelines.forEach(pipeline => {
|
||||
// TODO: either adds job link anchor to load the right variant
|
||||
// and/or show the job variant config in a modal?
|
||||
const jobList = (
|
||||
<React.Fragment>
|
||||
{pipeline.queue_name && (
|
||||
<p><strong>Queue: </strong> {pipeline.queue_name} </p>)}
|
||||
<ul className='list-group'>
|
||||
{pipeline.jobs.map((item, idx) => (
|
||||
<li className='list-group-item' key={idx}>
|
||||
<Link to={tenant.linkPrefix + '/job/' + item[0].name}>
|
||||
{item[0].name}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</React.Fragment>
|
||||
)
|
||||
rows.push({label: pipeline.name + ' jobs', value: jobList})
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
<table className='table table-striped table-bordered'>
|
||||
<tbody>
|
||||
{rows.map(item => (
|
||||
<tr key={item.label}>
|
||||
<td style={{width: '10%'}}>{item.label}</td>
|
||||
<td>{item.value}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(state => ({tenant: state.tenant}))(ProjectVariant)
|
|
@ -0,0 +1,99 @@
|
|||
// 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 React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import Project from '../containers/project/Project'
|
||||
import { fetchProject } from '../api'
|
||||
|
||||
|
||||
class ProjectPage extends React.Component {
|
||||
static propTypes = {
|
||||
match: PropTypes.object.isRequired,
|
||||
tenant: PropTypes.object
|
||||
}
|
||||
|
||||
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})
|
||||
})
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { project } = this.state
|
||||
if (!project) {
|
||||
return (<p>Loading...</p>)
|
||||
}
|
||||
return (
|
||||
<Project project={project} />
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(state => ({tenant: state.tenant}))(ProjectPage)
|
|
@ -51,7 +51,9 @@ class ProjectsPage extends Refreshable {
|
|||
<Table.Cell>{value}</Table.Cell>)
|
||||
const cellProjectFormat = (value) => (
|
||||
<Table.Cell>
|
||||
{value}
|
||||
<Link to={this.props.tenant.linkPrefix + '/project/' + value}>
|
||||
{value}
|
||||
</Link>
|
||||
</Table.Cell>)
|
||||
const cellBuildFormat = (value) => (
|
||||
<Table.Cell>
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
import StatusPage from './pages/Status'
|
||||
import ChangeStatusPage from './pages/ChangeStatus'
|
||||
import ProjectPage from './pages/Project'
|
||||
import ProjectsPage from './pages/Projects'
|
||||
import JobPage from './pages/Job'
|
||||
import JobsPage from './pages/Jobs'
|
||||
|
@ -56,6 +57,10 @@ const routes = () => [
|
|||
to: '/stream/:buildId',
|
||||
component: StreamPage
|
||||
},
|
||||
{
|
||||
to: '/project/:projectName*',
|
||||
component: ProjectPage
|
||||
},
|
||||
{
|
||||
to: '/job/:jobName',
|
||||
component: JobPage
|
||||
|
|
Loading…
Reference in New Issue