summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFelix Edel <felix.edel@bmw.de>2020-08-11 14:47:50 +0200
committerFelix Edel <felix.edel@bmw.de>2020-09-23 13:07:07 +0200
commit28ecbf42be30cbfe61b22eefecf3a521e05a1f9d (patch)
tree186610e77770d64f6adaab2fb16da9136543fa55
parentfe877d6052be01b02f31c5923ba62f3d6eb5dfed (diff)
downloadzuul-28ecbf42be30cbfe61b22eefecf3a521e05a1f9d.tar.gz
PF4: Update builds and buildsets tables + use newest patternfly release
This updates the tables shown on the builds and buildsets pages with new PF4 components. While the data is loading, a fetching animation is shown and in case no results could be found the page shows an empty state. Each table row is clickable and points to the build/buildset result page. The table uses the same hover effect as the build list on the buildset result page. This change also updates the used @patternfly/react-core version to match the current version of @patternfly/react-table [1]. Otherwise, some of the styles in the different @patternfly/react-core versions conflict with each other leading to wrong font-weights and wrong text colors in labels. [1] patternfly.org/v4/documentation/react/overview/release-notes#202010-release-notes-2020-08-17 Change-Id: I0f5e0815c53d18e8ae3570dc94594c77fecb90ce
-rw-r--r--web/package.json3
-rw-r--r--web/src/containers/build/BuildTable.jsx268
-rw-r--r--web/src/containers/build/BuildsetTable.jsx212
-rw-r--r--web/src/index.css70
-rw-r--r--web/src/pages/Builds.jsx132
-rw-r--r--web/src/pages/Buildsets.jsx128
-rw-r--r--web/yarn.lock65
7 files changed, 648 insertions, 230 deletions
diff --git a/web/package.json b/web/package.json
index f1cf7f2dc..693f892f6 100644
--- a/web/package.json
+++ b/web/package.json
@@ -7,7 +7,8 @@
"license": "Apache-2.0",
"private": true,
"dependencies": {
- "@patternfly/react-core": "^4.18.5",
+ "@patternfly/react-core": "^4.40.4",
+ "@patternfly/react-table": "^4.15.5",
"axios": "^0.19.0",
"immutability-helper": "^2.8.1",
"js-yaml": "^3.13.0",
diff --git a/web/src/containers/build/BuildTable.jsx b/web/src/containers/build/BuildTable.jsx
new file mode 100644
index 000000000..336d3406f
--- /dev/null
+++ b/web/src/containers/build/BuildTable.jsx
@@ -0,0 +1,268 @@
+// Copyright 2020 BMW Group
+//
+// 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 React from 'react'
+import PropTypes from 'prop-types'
+import { connect } from 'react-redux'
+import { Link } from 'react-router-dom'
+import {
+ Button,
+ EmptyState,
+ EmptyStateBody,
+ EmptyStateIcon,
+ EmptyStateSecondaryActions,
+ Spinner,
+ Title,
+} from '@patternfly/react-core'
+import {
+ BuildIcon,
+ CodeBranchIcon,
+ CodeIcon,
+ CubeIcon,
+ OutlinedCalendarAltIcon,
+ OutlinedClockIcon,
+ PollIcon,
+ StreamIcon,
+} from '@patternfly/react-icons'
+import {
+ Table,
+ TableHeader,
+ TableBody,
+ TableVariant,
+} from '@patternfly/react-table'
+import 'moment-duration-format'
+import * as moment from 'moment'
+
+import { BuildResult, BuildResultWithIcon, IconProperty } from './Misc'
+import { ExternalLink } from '../../Misc'
+
+function BuildTable(props) {
+ const { builds, fetching, onClearFilters, tenant, timezone } = props
+ const columns = [
+ {
+ title: <IconProperty icon={<BuildIcon />} value="Job" />,
+ dataLabel: 'Job',
+ },
+ {
+ title: <IconProperty icon={<CubeIcon />} value="Project" />,
+ dataLabel: 'Project',
+ },
+ {
+ title: <IconProperty icon={<CodeBranchIcon />} value="Branch" />,
+ dataLabel: 'Branch',
+ },
+ {
+ title: <IconProperty icon={<StreamIcon />} value="Pipeline" />,
+ dataLabel: 'Pipeline',
+ },
+ {
+ title: <IconProperty icon={<CodeIcon />} value="Change" />,
+ dataLabel: 'Change',
+ },
+ {
+ title: <IconProperty icon={<OutlinedClockIcon />} value="Duration" />,
+ dataLabel: 'Duration',
+ },
+ {
+ title: (
+ <IconProperty icon={<OutlinedCalendarAltIcon />} value="Start time" />
+ ),
+ dataLabel: 'Start time',
+ },
+ {
+ title: <IconProperty icon={<PollIcon />} value="Result" />,
+ dataLabel: 'Result',
+ },
+ ]
+
+ function createBuildRow(build) {
+ // This link will be defined on each cell of the current row as this is the
+ // only way to define a valid HTML link on a table row. Although we could
+ // simply define an onClick handler on the whole row and programatically
+ // switch to the buildresult page, this wouldn't provide the same
+ // look-and-feel as a plain HTML link.
+ const buildResultLink = (
+ <Link
+ to={`${tenant.linkPrefix}/build/${build.uuid}`}
+ className="zuul-stretched-link"
+ />
+ )
+ return {
+ cells: [
+ {
+ // To allow passing anything else than simple string values to a table
+ // cell, we must use the title attribute.
+ title: (
+ <>
+ {buildResultLink}
+ <BuildResultWithIcon result={build.result} colored={build.voting}>
+ {build.job_name}
+ {!build.voting && ' (non-voting)'}
+ </BuildResultWithIcon>
+ </>
+ ),
+ },
+ {
+ title: (
+ <>
+ {buildResultLink}
+ <span>{build.project}</span>
+ </>
+ ),
+ },
+ {
+ title: (
+ <>
+ {buildResultLink}
+ <span>{build.branch ? build.branch : build.ref}</span>
+ </>
+ ),
+ },
+ {
+ title: (
+ <>
+ {buildResultLink}
+ <span>{build.pipeline}</span>
+ </>
+ ),
+ },
+ {
+ title: (
+ <>
+ {buildResultLink}
+ {build.change && (
+ <span style={{ zIndex: 1, position: 'relative' }}>
+ <ExternalLink target={build.ref_url}>
+ {build.change},{build.patchset}
+ </ExternalLink>
+ </span>
+ )}
+ </>
+ ),
+ },
+ {
+ title: (
+ <>
+ {buildResultLink}
+ <span>
+ {moment
+ .duration(build.duration, 'seconds')
+ .format('h [hr] m [min] s [sec]')}
+ </span>
+ </>
+ ),
+ },
+ {
+ title: (
+ <>
+ {buildResultLink}
+ <span>
+ {moment
+ .utc(build.start_time)
+ .tz(timezone)
+ .format('YYYY-MM-DD HH:mm:ss')}
+ </span>
+ </>
+ ),
+ },
+ {
+ title: (
+ <>
+ {buildResultLink}
+ <BuildResult result={build.result} colored={build.voting} />
+ </>
+ ),
+ },
+ ],
+ }
+ }
+
+ function createFetchingRow() {
+ const rows = [
+ {
+ heightAuto: true,
+ cells: [
+ {
+ props: { colSpan: 8 },
+ title: (
+ <center>
+ <Spinner size="xl" />
+ </center>
+ ),
+ },
+ ],
+ },
+ ]
+ return rows
+ }
+
+ let rows = []
+ if (fetching) {
+ rows = createFetchingRow()
+ // The dataLabel property is used to show the column header in a list-like
+ // format for smaller viewports. When we are fetching, we don't want the
+ // fetching row to be prepended by a "Job" column header. The other column
+ // headers are not relevant here since we only have a single cell in the
+ // fetcihng row.
+ columns[0].dataLabel = ''
+ } else {
+ rows = builds.map((build) => createBuildRow(build))
+ }
+
+ return (
+ <>
+ <Table
+ aria-label="Builds Table"
+ variant={TableVariant.compact}
+ cells={columns}
+ rows={rows}
+ className="zuul-build-table"
+ >
+ <TableHeader />
+ <TableBody />
+ </Table>
+
+ {/* Show an empty state in case we don't have any builds but are also not
+ fetching */}
+ {!fetching && builds.length === 0 && (
+ <EmptyState>
+ <EmptyStateIcon icon={BuildIcon} />
+ <Title headingLevel="h1">No builds found</Title>
+ <EmptyStateBody>
+ No builds match this filter criteria. Remove some filters or clear
+ all to show results.
+ </EmptyStateBody>
+ <EmptyStateSecondaryActions>
+ <Button variant="link" onClick={onClearFilters}>
+ Clear all filters
+ </Button>
+ </EmptyStateSecondaryActions>
+ </EmptyState>
+ )}
+ </>
+ )
+}
+
+BuildTable.propTypes = {
+ builds: PropTypes.array.isRequired,
+ fetching: PropTypes.bool.isRequired,
+ onClearFilters: PropTypes.func.isRequired,
+ tenant: PropTypes.object.isRequired,
+ timezone: PropTypes.string.isRequired,
+}
+
+export default connect((state) => ({
+ tenant: state.tenant,
+ timezone: state.timezone,
+}))(BuildTable)
diff --git a/web/src/containers/build/BuildsetTable.jsx b/web/src/containers/build/BuildsetTable.jsx
new file mode 100644
index 000000000..d5efe2308
--- /dev/null
+++ b/web/src/containers/build/BuildsetTable.jsx
@@ -0,0 +1,212 @@
+// Copyright 2020 BMW Group
+//
+// 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 React from 'react'
+import PropTypes from 'prop-types'
+import { connect } from 'react-redux'
+import { Link } from 'react-router-dom'
+import {
+ Button,
+ EmptyState,
+ EmptyStateBody,
+ EmptyStateIcon,
+ EmptyStateSecondaryActions,
+ Spinner,
+ Title,
+} from '@patternfly/react-core'
+import {
+ BuildIcon,
+ CodeBranchIcon,
+ CodeIcon,
+ CubeIcon,
+ PollIcon,
+ StreamIcon,
+} from '@patternfly/react-icons'
+import {
+ Table,
+ TableHeader,
+ TableBody,
+ TableVariant,
+} from '@patternfly/react-table'
+
+import { BuildResult, BuildResultWithIcon, IconProperty } from './Misc'
+import { ExternalLink } from '../../Misc'
+
+function BuildsetTable(props) {
+ const { buildsets, fetching, onClearFilters, tenant } = props
+ const columns = [
+ {
+ title: <IconProperty icon={<CubeIcon />} value="Project" />,
+ dataLabel: 'Project',
+ },
+ {
+ title: <IconProperty icon={<CodeBranchIcon />} value="Branch" />,
+ dataLabel: 'Branch',
+ },
+ {
+ title: <IconProperty icon={<StreamIcon />} value="Pipeline" />,
+ dataLabel: 'Pipeline',
+ },
+ {
+ title: <IconProperty icon={<CodeIcon />} value="Change" />,
+ dataLabel: 'Change',
+ },
+ {
+ title: <IconProperty icon={<PollIcon />} value="Result" />,
+ dataLabel: 'Result',
+ },
+ ]
+
+ function createBuildsetRow(buildset) {
+ // This link will be defined on each cell of the current row as this is the
+ // only way to define a valid HTML link on a table row. Although we could
+ // simply define an onClick handler on the whole row and programatically
+ // switch to the buildresult page, this wouldn't provide the same
+ // look-and-feel as a plain HTML link.
+ const buildsetResultLink = (
+ <Link
+ to={`${tenant.linkPrefix}/buildset/${buildset.uuid}`}
+ className="zuul-stretched-link"
+ />
+ )
+ return {
+ cells: [
+ {
+ // To allow passing anything else than simple string values to a table
+ // cell, we must use the title attribute.
+ title: (
+ <>
+ {buildsetResultLink}
+ <BuildResultWithIcon result={buildset.result}>
+ {buildset.project}
+ </BuildResultWithIcon>
+ </>
+ ),
+ },
+ {
+ title: (
+ <>
+ {buildsetResultLink}
+ <span>{buildset.branch ? buildset.branch : buildset.ref}</span>
+ </>
+ ),
+ },
+ {
+ title: (
+ <>
+ {buildsetResultLink}
+ <span>{buildset.pipeline}</span>
+ </>
+ ),
+ },
+ {
+ title: (
+ <>
+ {buildsetResultLink}
+ {buildset.change && (
+ <span style={{ zIndex: 1, position: 'relative' }}>
+ <ExternalLink target={buildset.ref_url}>
+ {buildset.change},{buildset.patchset}
+ </ExternalLink>
+ </span>
+ )}
+ </>
+ ),
+ },
+ {
+ title: (
+ <>
+ {buildsetResultLink}
+ <BuildResult result={buildset.result} />
+ </>
+ ),
+ },
+ ],
+ }
+ }
+
+ function createFetchingRow() {
+ const rows = [
+ {
+ heightAuto: true,
+ cells: [
+ {
+ props: { colSpan: 8 },
+ title: (
+ <center>
+ <Spinner size="xl" />
+ </center>
+ ),
+ },
+ ],
+ },
+ ]
+ return rows
+ }
+
+ let rows = []
+ if (fetching) {
+ rows = createFetchingRow()
+ // The dataLabel property is used to show the column header in a list-like
+ // format for smaller viewports. When we are fetching, we don't want the
+ // fetching row to be prepended by a "Project" column header. The other
+ // column headers are not relevant here since we only have a single cell in
+ // the fetching row.
+ columns[0].dataLabel = ''
+ } else {
+ rows = buildsets.map((buildset) => createBuildsetRow(buildset))
+ }
+
+ return (
+ <>
+ <Table
+ aria-label="Builds Table"
+ variant={TableVariant.compact}
+ cells={columns}
+ rows={rows}
+ className="zuul-build-table"
+ >
+ <TableHeader />
+ <TableBody />
+ </Table>
+
+ {/* Show an empty state in case we don't have any buildsets but are also
+ not fetching */}
+ {!fetching && buildsets.length === 0 && (
+ <EmptyState>
+ <EmptyStateIcon icon={BuildIcon} />
+ <Title headingLevel="h1">No buildsets found</Title>
+ <EmptyStateBody>
+ No buildsets match this filter criteria. Remove some filters or
+ clear all to show results.
+ </EmptyStateBody>
+ <EmptyStateSecondaryActions>
+ <Button variant="link" onClick={onClearFilters}>
+ Clear all filters
+ </Button>
+ </EmptyStateSecondaryActions>
+ </EmptyState>
+ )}
+ </>
+ )
+}
+
+BuildsetTable.propTypes = {
+ buildsets: PropTypes.array.isRequired,
+ fetching: PropTypes.bool.isRequired,
+ onClearFilters: PropTypes.func.isRequired,
+ tenant: PropTypes.object.isRequired,
+}
+
+export default connect((state) => ({ tenant: state.tenant }))(BuildsetTable)
diff --git a/web/src/index.css b/web/src/index.css
index 22185a920..8f2ba8a5e 100644
--- a/web/src/index.css
+++ b/web/src/index.css
@@ -38,16 +38,82 @@ a.refresh {
margin-left: var(--pf-global--spacer--md);
}
-/* Build Lists */
+/*
+ * Build Lists and Tables
+ */
+
+/* Improve the hover effect of selected lines in the selectable data list*/
.pf-c-data-list__item.pf-m-selectable:hover:not(.pf-m-selected),
.pf-c-data-list__item.pf-m-selectable:focus:not(.pf-m-selected) {
- /* Improve the hover/focus effect of selected lines */
--pf-c-data-list__item--before--BackgroundColor: var(
--pf-c-data-list__item--m-selected--before--BackgroundColor
);
font-weight: bold;
}
+.zuul-build-list:hover a {
+ text-decoration: none;
+}
+
+/* Keep the normal font-size for compact tables */
+.zuul-build-table td {
+ font-size: var(--pf-global--FontSize--md);
+}
+
+/* Use the same hover effect on table rows like for the selectable data list */
+.zuul-build-table tbody tr:hover {
+ box-shadow: var(--pf-global--BoxShadow--sm-top),
+ var(--pf-global--BoxShadow--sm-bottom);
+}
+
+@media screen and (max-width: 768px) {
+ /* For the small-screen table layout the before element is already used to
+ show the column names. Thus, we fall back to the border to show the hover
+ effect. The drawback with that is, that we can't show a nice transition.
+ */
+ .zuul-build-table tbody tr:hover {
+ border-left-color: var(--pf-global--active-color--100);
+ border-left-width: var(--pf-global--BorderWidth--lg);
+ border-left-style: solid;
+ /* Compensate the border width with a negative margin */
+ margin-left: -3px;
+ }
+}
+
+@media screen and (min-width: 769px) {
+ /* For the larger screens (normal table layout) we can use the before
+ element on the first table cell to show the same hover effect like for
+ the data list */
+ .zuul-build-table tbody tr td:first-child::before {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ width: var(--pf-global--BorderWidth--lg);
+ content: "";
+ background-color: transparent;
+ transition: var(--pf-global--Transition);
+ }
+
+ .zuul-build-table tbody tr:hover td:first-child::before {
+ background-color: var(--pf-global--active-color--100);
+ }
+}
+
+/* Make a link stretch the whole parent element (in this case the whole table
+ cell) */
+.zuul-stretched-link::after {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 0;
+ pointer-events: auto;
+ content: "";
+ background-color: rgba(0,0,0,0);
+}
+
/*
* Build/Buildset result page
*/
diff --git a/web/src/pages/Builds.jsx b/web/src/pages/Builds.jsx
index 6c4993a0a..144f9ab85 100644
--- a/web/src/pages/Builds.jsx
+++ b/web/src/pages/Builds.jsx
@@ -15,9 +15,6 @@
import * as React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
-import { Link } from 'react-router-dom'
-import { Table } from 'patternfly-react'
-import * as moment from 'moment-timezone'
import 'moment-duration-format'
import { PageSection, PageSectionVariants } from '@patternfly/react-core'
@@ -28,6 +25,7 @@ import {
getFiltersFromUrl,
writeFiltersToUrl,
} from '../containers/FilterToolbar'
+import BuildTable from '../containers/build/BuildTable'
class BuildsPage extends React.Component {
static propTypes = {
@@ -39,7 +37,6 @@ class BuildsPage extends React.Component {
constructor(props) {
super()
- this.prepareTableHeaders()
this.filterCategories = [
{
key: 'job_name',
@@ -87,7 +84,8 @@ class BuildsPage extends React.Component {
]
this.state = {
- builds: null,
+ builds: [],
+ fetching: false,
filters: getFiltersFromUrl(props.location, this.filterCategories),
}
}
@@ -100,9 +98,15 @@ class BuildsPage extends React.Component {
// passed as prop and doesn't change since the page itself wasn't
// re-rendered.
const queryString = buildQueryString(filters)
- this.setState({builds: null})
- fetchBuilds(this.props.tenant.apiPrefix, queryString).then(response => {
- this.setState({builds: response.data})
+ this.setState({ fetching: true })
+ // TODO (felix): What happens in case of a broken network connection? Is the
+ // fetching shows infinitely or can we catch this and show an erro state in
+ // the table instead?
+ fetchBuilds(this.props.tenant.apiPrefix, queryString).then((response) => {
+ this.setState({
+ builds: response.data,
+ fetching: false,
+ })
})
}
@@ -137,107 +141,17 @@ class BuildsPage extends React.Component {
})
}
- prepareTableHeaders() {
- const headerFormat = value => <Table.Heading>{value}</Table.Heading>
- const cellFormat = (value) => (
- <Table.Cell>{value}</Table.Cell>)
- const linkBuildFormat = (value, rowdata) => (
- <Table.Cell>
- <Link to={this.props.tenant.linkPrefix + '/build/' + rowdata.rowData.uuid}>{value}</Link>
- </Table.Cell>
- )
- const linkChangeFormat = (value, rowdata) => (
- <Table.Cell>
- <a href={rowdata.rowData.ref_url}>{value ? rowdata.rowData.change+','+rowdata.rowData.patchset : rowdata.rowData.newrev ? rowdata.rowData.newrev.substr(0, 7) : rowdata.rowData.branch}</a>
- </Table.Cell>
- )
- const durationFormat = (value) => (
- <Table.Cell>
- {moment.duration(value, 'seconds').format('h [hr] m [min] s [sec]')}
- </Table.Cell>
- )
- const timeFormat = (value) => (
- <Table.Cell>
- {moment.utc(value).tz(this.props.timezone).format('YYYY-MM-DD HH:mm:ss')}
- </Table.Cell>
- )
- this.columns = []
- this.filterTypes = []
- const myColumns = [
- 'job',
- 'project',
- 'branch',
- 'pipeline',
- 'change',
- 'duration',
- 'start time',
- 'result']
- myColumns.forEach(column => {
- let prop = column
- let formatter = cellFormat
- // Adapt column name and property name
- if (column === 'job') {
- prop = 'job_name'
- } else if (column === 'start time') {
- prop = 'start_time'
- formatter = timeFormat
- } else if (column === 'change') {
- prop = 'change'
- formatter = linkChangeFormat
- } else if (column === 'result') {
- formatter = linkBuildFormat
- } else if (column === 'duration') {
- formatter = durationFormat
- }
- const label = column.charAt(0).toUpperCase() + column.slice(1)
- this.columns.push({
- header: {label: label, formatters: [headerFormat]},
- property: prop,
- cell: {formatters: [formatter]}
- })
- if (prop !== 'start_time' && prop !== 'ref_url' && prop !== 'duration'
- && prop !== 'log_url' && prop !== 'uuid') {
- this.filterTypes.push({
- id: prop,
- title: label,
- placeholder: 'Filter by ' + label,
- filterType: 'text',
- })
- }
- })
- // Add build filter at the end
- this.filterTypes.push({
- id: 'uuid',
- title: 'Build',
- placeholder: 'Filter by Build UUID',
- filterType: 'text',
- })
- }
-
- renderTable (builds) {
- return (
- <Table.PfProvider
- striped
- bordered
- columns={this.columns}
- >
- <Table.Header/>
- <Table.Body
- rows={builds}
- rowKey='uuid'
- onRow={(row) => {
- switch (row.result) {
- case 'SUCCESS':
- return { className: 'success' }
- default:
- return { className: 'warning' }
- }
- }} />
- </Table.PfProvider>)
+ handleClearFilters = () => {
+ // Delete the values for each filter category
+ const filters = this.filterCategories.reduce((filterDict, category) => {
+ filterDict[category.key] = []
+ return filterDict
+ }, {})
+ this.handleFilterChange(filters)
}
render() {
- const { builds, filters } = this.state
+ const { builds, fetching, filters } = this.state
return (
<PageSection variant={PageSectionVariants.light}>
<FilterToolbar
@@ -245,7 +159,11 @@ class BuildsPage extends React.Component {
onFilterChange={this.handleFilterChange}
filters={filters}
/>
- {builds ? this.renderTable(builds) : <p>Loading...</p>}
+ <BuildTable
+ builds={builds}
+ fetching={fetching}
+ onClearFilters={this.handleClearFilters}
+ />
</PageSection>
)
}
diff --git a/web/src/pages/Buildsets.jsx b/web/src/pages/Buildsets.jsx
index 4c170678e..602f4d0d8 100644
--- a/web/src/pages/Buildsets.jsx
+++ b/web/src/pages/Buildsets.jsx
@@ -15,8 +15,6 @@
import * as React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
-import { Link } from 'react-router-dom'
-import { Table } from 'patternfly-react'
import { PageSection, PageSectionVariants } from '@patternfly/react-core'
import { fetchBuildsets } from '../api'
@@ -26,7 +24,7 @@ import {
getFiltersFromUrl,
writeFiltersToUrl,
} from '../containers/FilterToolbar'
-
+import BuildsetTable from '../containers/build/BuildsetTable'
class BuildsetsPage extends React.Component {
static propTypes = {
@@ -35,9 +33,8 @@ class BuildsetsPage extends React.Component {
history: PropTypes.object,
}
- constructor (props) {
+ constructor(props) {
super()
- this.prepareTableHeaders()
this.filterCategories = [
{
key: 'project',
@@ -79,10 +76,10 @@ class BuildsetsPage extends React.Component {
]
this.state = {
- buildsets: null,
+ buildsets: [],
+ fetching: false,
filters: getFiltersFromUrl(props.location, this.filterCategories),
}
-
}
updateData = (filters) => {
@@ -93,20 +90,25 @@ class BuildsetsPage extends React.Component {
// passed as prop and doesn't change since the page itself wasn't
// re-rendered.
const queryString = buildQueryString(filters)
- this.setState({buildsets: null})
- fetchBuildsets(this.props.tenant.apiPrefix, queryString).then(response => {
- this.setState({buildsets: response.data})
- })
+ this.setState({ fetching: true })
+ fetchBuildsets(this.props.tenant.apiPrefix, queryString).then(
+ (response) => {
+ this.setState({
+ buildsets: response.data,
+ fetching: false,
+ })
+ }
+ )
}
- componentDidMount () {
+ componentDidMount() {
document.title = 'Zuul Buildsets'
if (this.props.tenant.name) {
this.updateData(this.state.filters)
}
}
- componentDidUpdate (prevProps) {
+ componentDidUpdate(prevProps) {
const { filters } = this.state
if (this.props.tenant.name !== prevProps.tenant.name) {
this.updateData(filters)
@@ -127,93 +129,17 @@ class BuildsetsPage extends React.Component {
})
}
- prepareTableHeaders() {
- const headerFormat = value => <Table.Heading>{value}</Table.Heading>
- const cellFormat = (value) => <Table.Cell>{value}</Table.Cell>
- const linkChangeFormat = (value, rowdata) => (
- <Table.Cell>
- <a href={rowdata.rowData.ref_url}>
- {value ?
- rowdata.rowData.change + ',' + rowdata.rowData.patchset :
- rowdata.rowData.newrev ?
- rowdata.rowData.newrev.substr(0, 7) :
- rowdata.rowData.branch}
- </a>
- </Table.Cell>
- )
- const linkBuildsetFormat = (value, rowdata) => (
- <Table.Cell>
- <Link
- to={this.props.tenant.linkPrefix +
- '/buildset/' + rowdata.rowData.uuid}>
- {value}
- </Link>
- </Table.Cell>
- )
- this.columns = []
- this.filterTypes = []
- const myColumns = [
- 'project',
- 'branch',
- 'pipeline',
- 'change',
- 'result']
- myColumns.forEach(column => {
- let prop = column
- let formatter = cellFormat
- if (column === 'change') {
- formatter = linkChangeFormat
- } else if (column === 'result') {
- formatter = linkBuildsetFormat
- }
- const label = column.charAt(0).toUpperCase() + column.slice(1)
- this.columns.push({
- header: {label: label, formatters: [headerFormat]},
- property: prop,
- cell: {formatters: [formatter]}
- })
- if (column !== 'builds') {
- this.filterTypes.push({
- id: prop,
- title: label,
- placeholder: 'Filter by ' + label,
- filterType: 'text',
- })
- }
- })
- // Add buildset filter at the end
- this.filterTypes.push({
- id: 'uuid',
- title: 'Buildset',
- placeholder: 'Filter by Buildset UUID',
- filterType: 'text',
- })
- }
-
- renderTable (buildsets) {
- return (
- <Table.PfProvider
- striped
- bordered
- columns={this.columns}
- >
- <Table.Header/>
- <Table.Body
- rows={buildsets}
- rowKey='uuid'
- onRow={(row) => {
- switch (row.result) {
- case 'SUCCESS':
- return { className: 'success' }
- default:
- return { className: 'warning' }
- }
- }} />
- </Table.PfProvider>)
+ handleClearFilters = () => {
+ // Delete the values for each filter category
+ const filters = this.filterCategories.reduce((filterDict, category) => {
+ filterDict[category.key] = []
+ return filterDict
+ }, {})
+ this.handleFilterChange(filters)
}
render() {
- const { buildsets, filters } = this.state
+ const { buildsets, fetching, filters } = this.state
return (
<PageSection variant={PageSectionVariants.light}>
<FilterToolbar
@@ -221,10 +147,14 @@ class BuildsetsPage extends React.Component {
onFilterChange={this.handleFilterChange}
filters={filters}
/>
- {buildsets ? this.renderTable(buildsets) : <p>Loading...</p>}
+ <BuildsetTable
+ buildsets={buildsets}
+ fetching={fetching}
+ onClearFilters={this.handleClearFilters}
+ />
</PageSection>
)
}
}
-export default connect(state => ({tenant: state.tenant}))(BuildsetsPage)
+export default connect((state) => ({ tenant: state.tenant }))(BuildsetsPage)
diff --git a/web/yarn.lock b/web/yarn.lock
index f74fb7589..9d70c3f83 100644
--- a/web/yarn.lock
+++ b/web/yarn.lock
@@ -1510,33 +1510,51 @@
dependencies:
"@types/node" ">= 8"
-"@patternfly/react-core@^4.18.5":
- version "4.18.5"
- resolved "https://registry.yarnpkg.com/@patternfly/react-core/-/react-core-4.18.5.tgz#465ee3be0e58f7fdead9644ed2667f18eff0d684"
- integrity sha512-wUHLXPOklcAVA9nCnmUvGwdfTZnypxNUnA0l+eEiq1QWhQoSRdI7S/HNOelYhpRjMMwPwy3yMsJUjHsXdqv2FQ==
- dependencies:
- "@patternfly/react-icons" "^4.3.5"
- "@patternfly/react-styles" "^4.3.4"
- "@patternfly/react-tokens" "^4.4.4"
+"@patternfly/patternfly@4.31.6":
+ version "4.31.6"
+ resolved "https://registry.yarnpkg.com/@patternfly/patternfly/-/patternfly-4.31.6.tgz#ef9919df610171760cd19920a904ca9b09a74593"
+ integrity sha512-gp8tpbE4Z6C1PIQwNiWMjO5XSr/UGjXs4InL/zmxgZbToyizUxsudwJyCObtdvDNoN57ZJp0gYWYy0tIuwEyMA==
+
+"@patternfly/react-core@^4.40.4":
+ version "4.40.4"
+ resolved "https://registry.yarnpkg.com/@patternfly/react-core/-/react-core-4.40.4.tgz#e4409f89327e2fcdcd07a08833c0149e6f2f6966"
+ integrity sha512-NQuUgIVEty7BBNJMJAVRXejOGRGpRQwgQ8Rw/J/JlgkhtOrCSFX5cEbpAXMXLYWkJrz0++XfRK/FQMoQbvS2hQ==
+ dependencies:
+ "@patternfly/react-icons" "^4.7.2"
+ "@patternfly/react-styles" "^4.7.2"
+ "@patternfly/react-tokens" "^4.9.4"
focus-trap "4.0.2"
react-dropzone "9.0.0"
tippy.js "5.1.2"
tslib "^1.11.1"
-"@patternfly/react-icons@^4.3.5":
- version "4.3.5"
- resolved "https://registry.yarnpkg.com/@patternfly/react-icons/-/react-icons-4.3.5.tgz#b98c5af80f4729e6203c8e799ace2f57308b3b9a"
- integrity sha512-+GublxpFXR+y/5zygf9q00/LvIvso8jr0mxZGhVxsKmi2dUu7xAvN+T+5vjS9fiMbXf7WXsSPXST/UTiBIVTdQ==
-
-"@patternfly/react-styles@^4.3.4":
- version "4.3.4"
- resolved "https://registry.yarnpkg.com/@patternfly/react-styles/-/react-styles-4.3.4.tgz#ba62f983f1bd2586d2878d8a38912442ebca85e8"
- integrity sha512-d5W5G9g7sr7DthGPFiF6Oa33w8JFJ+ocLZDogyZcS1Oq0BJJX8j+hZNXZfhXxmHoXufxQL6RJ4dOyoa2zEZUvA==
+"@patternfly/react-icons@^4.7.2":
+ version "4.7.2"
+ resolved "https://registry.yarnpkg.com/@patternfly/react-icons/-/react-icons-4.7.2.tgz#f4ad252cb5682bd95da474ce9ce6ddf7fb3a1ac1"
+ integrity sha512-r1yCVHxUtRSblo8VwfaUM0d49z4eToZXAI0VzOnfKPRgSmGZrn6l8soQgDDtyQsSDr534Qvm55y/qLrlR9JCnw==
+
+"@patternfly/react-styles@^4.7.2":
+ version "4.7.2"
+ resolved "https://registry.yarnpkg.com/@patternfly/react-styles/-/react-styles-4.7.2.tgz#6671a243401ef55adddcb0e0922f5f5f4eea840e"
+ integrity sha512-r3zyrt1mXcqdXaEq+otl1cGsN0Ou1k8uIJSY+4EGe2A5jLGbX3vBTwUrpPKLB6tUdNL+mZriFf+3oKhWbVZDkw==
+
+"@patternfly/react-table@^4.15.5":
+ version "4.15.5"
+ resolved "https://registry.yarnpkg.com/@patternfly/react-table/-/react-table-4.15.5.tgz#7fc3fcd37a6fd4dca00cc32d24c76199ee41a7f1"
+ integrity sha512-GlyKrEDMY+yLvczj5rWpNKcUp90Ib7alKV9JK8rVLOpTsukQ0QplXxYFsnIrombcaw2V54XVdflZGjsB0GoHEw==
+ dependencies:
+ "@patternfly/patternfly" "4.31.6"
+ "@patternfly/react-core" "^4.40.4"
+ "@patternfly/react-icons" "^4.7.2"
+ "@patternfly/react-styles" "^4.7.2"
+ "@patternfly/react-tokens" "^4.9.4"
+ lodash "^4.17.19"
+ tslib "^1.11.1"
-"@patternfly/react-tokens@^4.4.4":
- version "4.4.4"
- resolved "https://registry.yarnpkg.com/@patternfly/react-tokens/-/react-tokens-4.4.4.tgz#4a6fc7234908343087ccbc8e1ae53336d7fc7311"
- integrity sha512-vhDBtwkp1PTAqXDjAsUPRf/ewBh2Asong8MPr9ZGeXAeOULW8creW7GJx+JZX9eaEJMA3ESMeZ6wZ5j/yyWMGQ==
+"@patternfly/react-tokens@^4.9.4":
+ version "4.9.4"
+ resolved "https://registry.yarnpkg.com/@patternfly/react-tokens/-/react-tokens-4.9.4.tgz#71ea3c33045fb29bcc8d98f2c0f07bfcdc89a12c"
+ integrity sha512-AJpcAvzWXvfThT2mx24rV7OJSHvZnIsOP1bVrXiubpFAJhi/Suq+LGe/lTPUnuSXaflwyDBRZDXWWmJb4yaWqg==
"@semantic-release/commit-analyzer@^6.1.0":
version "6.3.3"
@@ -8896,6 +8914,11 @@ lodash@4.17.15, "lodash@>=3.5 <5", lodash@^4.17.10, lodash@^4.17.11, lodash@^4.1
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
+lodash@^4.17.19:
+ version "4.17.20"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
+ integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
+
loglevel@^1.6.6:
version "1.6.8"
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.8.tgz#8a25fb75d092230ecd4457270d80b54e28011171"