Merge "Web: fix tabs on project page"

This commit is contained in:
Zuul
2022-08-17 03:09:33 +00:00
committed by Gerrit Code Review
4 changed files with 141 additions and 146 deletions

View File

@@ -1,4 +1,5 @@
// Copyright 2018 Red Hat, Inc
// Copyright 2022 Acme Gating, LLC
//
// 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
@@ -12,68 +13,44 @@
// License for the specific language governing permissions and limitations
// under the License.
import * as React from 'react'
import React, { useState } from 'react'
import PropTypes from 'prop-types'
import {
Nav,
NavItem,
TabContainer,
TabPane,
TabContent,
} from 'patternfly-react'
Tabs,
Tab,
} from '@patternfly/react-core'
import ProjectVariant from './ProjectVariant'
class Project extends React.Component {
static propTypes = {
project: PropTypes.object.isRequired,
}
function Project(props) {
const [variantIdx, setVariantIdx] = useState(0)
const { project } = props
state = {
variantIdx: 0,
}
renderVariantTitle (variant, selected) {
let title = variant.default_branch
if (selected) {
title = <strong>{title}</strong>
}
function renderVariantTitle (variant) {
let title = variant.source_context.project === project.name ?
variant.source_context.branch : variant.source_context.project
return title
}
render () {
const { project } = this.props
const { variantIdx } = this.state
return (
<React.Fragment>
<Tabs activeKey={variantIdx}
onSelect={(event, tabIndex) => setVariantIdx(tabIndex)}
isBox>
{project.configs.map((variant, idx) => (
<Tab key={idx} eventKey={idx}
title={renderVariantTitle(variant)}>
<ProjectVariant variant={variant} />
</Tab>
))}
</Tabs>
</React.Fragment>
)
}
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>
{project.configs[variantIdx] && (
<ProjectVariant variant={project.configs[variantIdx]} />
)}
</TabPane>
</TabContent>
</div>
</TabContainer>
</React.Fragment>
)
}
Project.propTypes = {
project: PropTypes.object.isRequired,
}
export default Project

View File

@@ -18,64 +18,68 @@ 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
function ProjectVariant(props) {
const { tenant, variant } = 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})
}
render () {
const { tenant, variant } = this.props
const rows = []
rows.push({label: 'Merge mode', value: variant.merge_mode})
if (variant.templates.length > 0) {
const 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'>
{variant.templates.map((item, idx) => (
<li className='list-group-item' key={idx}>{item}</li>))}
{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>
)
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>
</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>
)
}
ProjectVariant.propTypes = {
tenant: PropTypes.object,
variant: PropTypes.object.isRequired
}
function mapStateToProps(state) {
return {
tenant: state.tenant,
}
}
export default connect(state => ({tenant: state.tenant}))(ProjectVariant)
export default connect(mapStateToProps)(ProjectVariant)

View File

@@ -3,6 +3,12 @@ body {
padding: 0;
}
/* Make the H2 header inline-block so that the refresh icon/button can
share space with it floating on the right. */
h2 {
display: inline-block;
}
.pf-c-title {
padding-bottom: 10px;
}

View File

@@ -1,4 +1,5 @@
// Copyright 2018 Red Hat, Inc
// Copyright 2022 Acme Gating, LLC
//
// 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
@@ -12,67 +13,74 @@
// License for the specific language governing permissions and limitations
// under the License.
import * as React from 'react'
import React, { useEffect, useCallback } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import { PageSection, PageSectionVariants } from '@patternfly/react-core'
import {
PageSection,
PageSectionVariants,
Text,
TextContent,
} from '@patternfly/react-core'
import Project from '../containers/project/Project'
import JobGraph from '../containers/jobgraph/JobGraph'
import { fetchProjectIfNeeded } from '../actions/project'
import { Fetchable } from '../containers/Fetching'
class ProjectPage extends React.Component {
static propTypes = {
match: PropTypes.object.isRequired,
tenant: PropTypes.object,
remoteData: PropTypes.object,
dispatch: PropTypes.func
}
function ProjectPage(props) {
const { tenant, fetchProjectIfNeeded, remoteData } = props
const { projectName } = props.match.params
const tenantProjects = remoteData.projects[tenant.name]
updateData = (force) => {
this.props.dispatch(fetchProjectIfNeeded(
this.props.tenant, this.props.match.params.projectName, force))
}
componentDidMount () {
document.title = 'Zuul Project | ' + this.props.match.params.projectName
if (this.props.tenant.name) {
this.updateData()
const updateData = useCallback((force) => {
if (tenant.name) {
fetchProjectIfNeeded(tenant, projectName, force)
}
}
}, [tenant, projectName, fetchProjectIfNeeded])
componentDidUpdate (prevProps) {
if (this.props.tenant.name !== prevProps.tenant.name) {
this.updateData()
}
}
useEffect(() => {
document.title = 'Zuul Project | ' + projectName
updateData()
}, [tenant, projectName, updateData])
render () {
const { remoteData } = this.props
const tenantProjects = remoteData.projects[this.props.tenant.name]
const projectName = this.props.match.params.projectName
return (
return (
<>
<PageSection variant={PageSectionVariants.light}>
<PageSection style={{paddingRight: '5px'}}>
<Fetchable
isFetching={remoteData.isFetching}
fetchCallback={this.updateData}
/>
</PageSection>
<TextContent>
<Text component="h2">Project {projectName}</Text>
<Fetchable
isFetching={remoteData.isFetching}
fetchCallback={updateData}
/>
</TextContent>
{tenantProjects && tenantProjects[projectName] &&
<>
<Project project={tenantProjects[projectName]} />
<JobGraph project={tenantProjects[projectName]} />
</>
}
</PageSection>
)
</PageSection>
</>
)
}
ProjectPage.propTypes = {
match: PropTypes.object.isRequired,
tenant: PropTypes.object,
remoteData: PropTypes.object,
fetchProjectIfNeeded: PropTypes.func,
}
function mapStateToProps(state) {
return {
tenant: state.tenant,
remoteData: state.project,
}
}
export default connect(state => ({
tenant: state.tenant,
remoteData: state.project,
}))(ProjectPage)
const mapDispatchToProps = {
fetchProjectIfNeeded,
}
export default connect(mapStateToProps, mapDispatchToProps)(ProjectPage)