debian-image-builder-frontend/src/Components/ImagesTable/ImagesTable.js
Jacob Kozol c230d41d88 ImagesTable: add title to Instance column
Also, test the table has expected headers.
2021-11-30 12:08:52 +01:00

210 lines
8.6 KiB
JavaScript

import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { actions } from '../../store/actions';
import { Link } from 'react-router-dom';
import { Table, TableHeader, TableBody } from '@patternfly/react-table';
import { Button,
EmptyState, EmptyStateVariant, EmptyStateIcon, EmptyStateBody, EmptyStateSecondaryActions,
Pagination,
Toolbar, ToolbarContent, ToolbarItem,
Title } from '@patternfly/react-core';
import { ExternalLinkAltIcon, PlusCircleIcon } from '@patternfly/react-icons';
import ImageBuildStatus from './ImageBuildStatus';
import Release from './Release';
import Upload from './Upload';
import ImageLink from './ImageLink';
class ImagesTable extends Component {
constructor(props) {
super(props);
this.state = {
page: 1,
perPage: 10,
};
this.pollComposeStatuses = this.pollComposeStatuses.bind(this);
this.onSetPage = this.onSetPage.bind(this);
this.onPerPageSelect = this.onPerPageSelect.bind(this);
this.timestampToDisplayString = this.timestampToDisplayString.bind(this);
}
componentDidMount() {
this.props.composesGet(this.state.perPage, 0);
this.interval = setInterval(() => this.pollComposeStatuses(), 8000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
pollComposeStatuses() {
let { composes } = this.props;
Object.entries(composes.byId).map(([ id, compose ]) => {
/* Skip composes that have been complete */
if (compose.image_status.status === 'success' || compose.image_status.status === 'failure') {
return;
}
this.props.composeGetStatus(id);
});
}
onSetPage(_, page) {
// if the next page's composes haven't been fetched from api yet
// then fetch them with proper page index and offset
if (this.props.composes.count > this.props.composes.allIds.length) {
const pageIndex = page - 1;
const offset = pageIndex * this.state.perPage;
this.props.composesGet(this.state.perPage, offset);
}
this.setState({ page });
}
onPerPageSelect(_, perPage) {
// if the new per page quantity is greater than the number of already fetched composes fetch more composes
// if all composes haven't already been fetched
if (this.props.composes.count > this.props.composes.allIds.length && perPage > this.props.composes.allIds.length) {
this.props.composesGet(perPage, 0);
}
// page should be reset to the first page when the page size is changed.
this.setState({ perPage, page: 1 });
}
timestampToDisplayString(ts) {
// timestamp has format 2021-04-27 12:31:12.794809 +0000 UTC
// must be converted to ms timestamp and then reformatted to Apr 27, 2021
if (!ts) {
return '';
}
// get YYYY-MM-DD format
const date = ts.slice(0, 10);
const ms = Date.parse(date);
const options = { month: 'short', day: 'numeric', year: 'numeric' };
const tsDisplay = new Intl.DateTimeFormat('en-US', options).format(ms);
return tsDisplay;
}
render() {
let { composes } = this.props;
const columns = [
'Image',
'Created',
'Release',
'Target',
'Status',
'Instance'
];
// the state.page is not an index so must be reduced by 1 get the starting index
const itemsStartInclusive = (this.state.page - 1) * this.state.perPage;
const itemsEndExlcusive = itemsStartInclusive + this.state.perPage;
// only display the current pages section of composes. slice is inclusive, exclusive.
const rows = composes.allIds.slice(itemsStartInclusive, itemsEndExlcusive).map(id => {
const compose = composes.byId[id];
return {
cells: [
id,
this.timestampToDisplayString(compose.created_at),
{ title: <Release release={ compose.request.distribution } /> },
{ title: <Upload uploadType={ compose.request.image_requests[0].upload_request.type } /> },
{ title: <ImageBuildStatus status={ compose.image_status ? compose.image_status.status : '' } /> },
{ title: <ImageLink
imageStatus={ compose.image_status }
uploadOptions={ compose.request.image_requests[0].upload_request.options } /> },
]
};
});
return (
<React.Fragment>
{ composes.allIds.length === 0 && (
<EmptyState variant={ EmptyStateVariant.large } data-testid="empty-state">
<EmptyStateIcon icon={ PlusCircleIcon } />
<Title headingLevel="h4" size="lg">
Create an image
</Title>
<EmptyStateBody>
Create OS images for deployment in Amazon Web Services,
Microsoft Azure and Google Cloud Platform. Images can
include a custom package set and an activation key to
automate the registration process.
</EmptyStateBody>
<Link to="/imagewizard" className="pf-c-button pf-m-primary" data-testid="create-image-action">
Create image
</Link>
<EmptyStateSecondaryActions>
<Button
component="a"
target="_blank"
variant="link"
icon={ <ExternalLinkAltIcon /> }
iconPosition="right"
isInline
href="
https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/uploading_a_customized_rhel_system_image_to_cloud_environments/index
">
Documentation
</Button>
</EmptyStateSecondaryActions>
</EmptyState>
) || (
<React.Fragment>
<Toolbar>
<ToolbarContent>
<ToolbarItem>
<Link to="/imagewizard" className="pf-c-button pf-m-primary" data-testid="create-image-action">
Create image
</Link>
</ToolbarItem>
<ToolbarItem variant="pagination" align={ { default: 'alignRight' } }>
<Pagination
itemCount={ this.props.composes.count }
perPage={ this.state.perPage }
page={ this.state.page }
onSetPage={ this.onSetPage }
onPerPageSelect={ this.onPerPageSelect }
widgetId="compose-pagination"
data-testid="images-pagination"
isCompact />
</ToolbarItem>
</ToolbarContent>
</Toolbar>
<Table
aria-label="Images"
rows={ rows }
cells={ columns }
data-testid="images-table">
<TableHeader />
<TableBody />
</Table>
</React.Fragment>
)}
</React.Fragment>
);
}
}
function mapStateToProps(state) {
return {
composes: state.composes,
};
}
function mapDispatchToProps(dispatch) {
return {
composesGet: (limit, offset) => dispatch(actions.composesGet(limit, offset)),
composeGetStatus: (id) => dispatch(actions.composeGetStatus(id)),
};
}
ImagesTable.propTypes = {
composes: PropTypes.object,
composesGet: PropTypes.func,
composeGetStatus: PropTypes.func,
};
export default connect(mapStateToProps, mapDispatchToProps)(ImagesTable);