diff options
author | Felix Edel <felix.edel@bmw.de> | 2020-08-11 14:47:50 +0200 |
---|---|---|
committer | Felix Edel <felix.edel@bmw.de> | 2020-09-23 13:07:07 +0200 |
commit | 28ecbf42be30cbfe61b22eefecf3a521e05a1f9d (patch) | |
tree | 186610e77770d64f6adaab2fb16da9136543fa55 | |
parent | fe877d6052be01b02f31c5923ba62f3d6eb5dfed (diff) | |
download | zuul-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.json | 3 | ||||
-rw-r--r-- | web/src/containers/build/BuildTable.jsx | 268 | ||||
-rw-r--r-- | web/src/containers/build/BuildsetTable.jsx | 212 | ||||
-rw-r--r-- | web/src/index.css | 70 | ||||
-rw-r--r-- | web/src/pages/Builds.jsx | 132 | ||||
-rw-r--r-- | web/src/pages/Buildsets.jsx | 128 | ||||
-rw-r--r-- | web/yarn.lock | 65 |
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" |