diff options
Diffstat (limited to 'web/src/pages/Autohold.jsx')
-rw-r--r-- | web/src/pages/Autohold.jsx | 251 |
1 files changed, 251 insertions, 0 deletions
diff --git a/web/src/pages/Autohold.jsx b/web/src/pages/Autohold.jsx new file mode 100644 index 000000000..0d0198b45 --- /dev/null +++ b/web/src/pages/Autohold.jsx @@ -0,0 +1,251 @@ +// Copyright 2021 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 { + EmptyState, + EmptyStateIcon, + EmptyStateVariant, + PageSection, + PageSectionVariants, + Title, + Flex, + FlexItem, + List, + ListItem, +} from '@patternfly/react-core' +import { + LockIcon, + BuildIcon, + CubeIcon, + CodeIcon, + HashtagIcon, + OutlinedClockIcon, + OutlinedCommentDotsIcon, + TrashIcon, +} from '@patternfly/react-icons' +import { IconProperty } from '../Misc' + +import { Link } from 'react-router-dom' +import * as moment from 'moment' + +import { fetchAutohold } from '../actions/autoholds' +import { EmptyPage } from '../containers/Errors' +import { Fetching } from '../containers/Fetching' +import HeldBuildList from '../containers/autohold/HeldBuildList' + + +// This is hard-coded in zuul/executor/server.py#3035 +const EXPIRED_HOLD_REQUEST_TTL = 24 * 60 * 60 + + +class AutoholdPage extends React.Component { + static propTypes = { + match: PropTypes.object.isRequired, + tenant: PropTypes.object.isRequired, + autohold: PropTypes.object, + isFetching: PropTypes.bool.isRequired, + fetchAutohold: PropTypes.func.isRequired, + } + + updateData = () => { + if (!this.props.autohold) { + this.props.fetchAutohold( + this.props.tenant, + this.props.match.params.requestId + ) + } + } + + componentDidMount() { + document.title = 'Zuul Autohold Request' + if (this.props.tenant.name) { + this.updateData() + } + } + + componentDidUpdate(prevProps) { + if (this.props.tenant.name !== prevProps.tenant.name) { + this.updateData() + } + } + + render() { + const { autohold, isFetching, tenant } = this.props + + // Initial page load + if (autohold === undefined || isFetching) { + return <Fetching /> + } + + // Fetching finished, but no autohold found + if (!autohold) { + return ( + <EmptyPage + title="This autohold request does not exist" + icon={LockIcon} + linkTarget={`${tenant.linkPrefix}/autoholds`} + linkText="Show all autohold requests" + /> + ) + } + + // Return the build list or an empty state if no builds triggered the autohold. + const buildsContent = autohold.nodes.length > 0 ? ( + <span ><HeldBuildList nodes={autohold.nodes} /></span> + ) : ( + <> + {/* Using an hr above the empty state ensures that the space between + heading (builds) and empty state is filled and the empty state + doesn't look like it's lost in space. */} + <hr /> + <EmptyState variant={EmptyStateVariant.small}> + <EmptyStateIcon icon={BuildIcon} /> + <Title headingLevel="h4" size="lg"> + This autohold request has not triggered yet. + </Title> + </EmptyState> + </> + ) + + const node_expiration = (autohold.node_expiration === 0) ? 'Indefinitely' : moment.duration(autohold.node_expiration, 'seconds').humanize() + console.log(autohold.expired) + const elapsed = autohold.expired ? (Date.now() / 1000 - autohold.expired) : false + console.log(elapsed) + const timeToDeletion = autohold.node_expiration + EXPIRED_HOLD_REQUEST_TTL - elapsed + console.log(timeToDeletion) + + + let deletionInfo, deletionInfoMsg + if (autohold.node_expiration !== 0 && elapsed) { + deletionInfoMsg = timeToDeletion > 0 ? + (<> + <strong>Deletion scheduled in </strong> {moment.duration(timeToDeletion, 'seconds').humanize()} + </>) : + <span>This request is scheduled to be deleted automatically.</span> + deletionInfo = <IconProperty + WrapElement={ListItem} + icon={<TrashIcon />} + value={deletionInfoMsg} + /> + } else { + deletionInfo = <></> + } + + return ( + <> + <PageSection variant={PageSectionVariants.light}> + <Title headingLevel="h2">Autohold Request {autohold.id}</Title> + + <Flex className="zuul-autohold-attributes"> + <Flex flex={{ default: 'flex_1' }}> + <FlexItem> + <List style={{ listStyle: 'none' }}> + <IconProperty + WrapElement={ListItem} + icon={<CubeIcon />} + value={ + <> + <strong>Project </strong> <Link to={`${tenant.linkPrefix}/project/${autohold.project}`}>{autohold.project}</Link> + </> + } + /> + <IconProperty + WrapElement={ListItem} + icon={<CodeIcon />} + value={ + <> + <strong>Filter </strong> {autohold.ref_filter} + </> + } + /> + <IconProperty + WrapElement={ListItem} + icon={<BuildIcon />} + value={ + <> + <strong>Job </strong> <Link to={`${tenant.linkPrefix}/job/${autohold.job}`}>{autohold.job}</Link> + </> + } + /> + <IconProperty + WrapElement={ListItem} + icon={<HashtagIcon />} + value={ + <> + <strong>Trigger Count </strong> {autohold.current_count} out of {autohold.max_count} + </> + } + /> + <IconProperty + WrapElement={ListItem} + icon={<OutlinedClockIcon />} + value={ + <> + <strong>Hold Duration </strong> <span title={autohold.node_expiration + ' seconds'} >{node_expiration}</span> + </> + } + /> + </List> + </FlexItem> + </Flex> + <Flex flex={{ default: 'flex_1' }}> + <FlexItem> + <List style={{ listStyle: 'none' }}> + <IconProperty + WrapElement={ListItem} + icon={<OutlinedCommentDotsIcon />} + value={ + <> + <strong>Reason:</strong> + <pre>{autohold.reason}</pre> + </> + } + /> + {deletionInfo} + </List> + </FlexItem> + </Flex> + </Flex> + </PageSection> + <PageSection variant={PageSectionVariants.light}> + <Title headingLevel="h3"> + <BuildIcon + style={{ + marginRight: 'var(--pf-global--spacer--sm)', + verticalAlign: '-0.1em', + }} + />{' '} + Held Builds + </Title> + {buildsContent} + </PageSection> + </> + ) + } +} + +function mapStateToProps(state) { + return { + autohold: state.autoholds.autohold, + tenant: state.tenant, + isFetching: state.autoholds.isFetching, + } +} + +const mapDispatchToProps = { fetchAutohold } + +export default connect(mapStateToProps, mapDispatchToProps)(AutoholdPage) |