// Copyright 2018 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 PropTypes from 'prop-types' import { connect } from 'react-redux' import { Link } from 'react-router-dom' import { Button, Dropdown, DropdownItem, KebabToggle, Modal, ModalVariant } from '@patternfly/react-core' import { AngleDoubleUpIcon, BanIcon, } from '@patternfly/react-icons' import { dequeue, dequeue_ref, promote } from '../../api' import { addDequeueError, addPromoteError } from '../../actions/adminActions' import { addNotification } from '../../actions/notifications' import { fetchStatusIfNeeded } from '../../actions/status' import LineAngleImage from '../../images/line-angle.png' import LineTImage from '../../images/line-t.png' import ChangePanel from './ChangePanel' class Change extends React.Component { static propTypes = { change: PropTypes.object.isRequired, queue: PropTypes.object.isRequired, expanded: PropTypes.bool.isRequired, pipeline: PropTypes.object, tenant: PropTypes.object, user: PropTypes.object, dispatch: PropTypes.func } state = { showDequeueModal: false, showPromoteModal: false, showAdminActions: false, } dequeueConfirm = () => { const { tenant, change, pipeline } = this.props let projectName = change.project let changeId = change.id || 'N/A' let changeRef = change.ref this.setState(() => ({ showDequeueModal: false })) // post-merge if (/^[0-9a-f]{40}$/.test(changeId)) { dequeue_ref(tenant.apiPrefix, projectName, pipeline.name, changeRef) .then(() => { this.props.dispatch(fetchStatusIfNeeded(tenant)) }) .catch(error => { this.props.dispatch(addDequeueError(error)) }) // pre-merge, ie we have a change id } else if (changeId !== 'N/A') { dequeue(tenant.apiPrefix, projectName, pipeline.name, changeId) .then(() => { this.props.dispatch(fetchStatusIfNeeded(tenant)) }) .catch(error => { this.props.dispatch(addDequeueError(error)) }) } else { this.props.dispatch(addNotification({ url: null, status: 'Invalid change ' + changeRef + ' on project ' + projectName, text: '', type: 'error', })) } } dequeueCancel = () => { this.setState(() => ({ showDequeueModal: false })) } renderDequeueModal() { const { showDequeueModal } = this.state const { change } = this.props let projectName = change.project let changeId = change.id || change.ref const title = 'You are about to dequeue a change' return ( Confirm, , ]}>

Please confirm that you want to cancel all ongoing builds on change {changeId} for project {projectName}.

) } promoteConfirm = () => { const { tenant, change, pipeline } = this.props let changeId = change.id || 'NA' this.setState(() => ({ showPromoteModal: false })) if (changeId !== 'N/A') { promote(tenant.apiPrefix, pipeline.name, [changeId,]) .then(() => { this.props.dispatch(fetchStatusIfNeeded(tenant)) }) .catch(error => { this.props.dispatch(addPromoteError(error)) }) } else { this.props.dispatch(addNotification({ url: null, status: 'Invalid change ' + changeId + ' for promotion', text: '', type: 'error' })) } } promoteCancel = () => { this.setState(() => ({ showPromoteModal: false })) } renderPromoteModal() { const { showPromoteModal } = this.state const { change } = this.props let changeId = change.id || 'N/A' const title = 'You are about to promote a change' return ( Confirm, , ]}>

Please confirm that you want to promote change {changeId}.

) } renderAdminCommands(idx) { const { showAdminActions } = this.state const { queue } = this.props const dropdownCommands = [ } description="Stop all jobs for this change" onClick={(event) => { event.preventDefault() this.setState(() => ({ showDequeueModal: true })) }} >Dequeue, } description="Promote this change to the top of the queue" onClick={(event) => { event.preventDefault() this.setState(() => ({ showPromoteModal: true })) }} >Promote ] return ( { this.setState({ showAdminActions: !showAdminActions }) const element = document.getElementById('toggle-id-' + idx + '-' + queue.uuid) element.focus() }} dropdownItems={dropdownCommands} isPlain toggle={ { this.setState({ showAdminActions }) }} id={'toggle-id-' + idx + '-' + queue.uuid} /> } /> ) } renderStatusIcon(change) { let iconGlyph = 'pficon pficon-ok' let iconTitle = 'Succeeding' if (change.active !== true) { iconGlyph = 'pficon pficon-pending' iconTitle = 'Waiting until closer to head of queue to' + ' start jobs' } else if (change.live !== true) { iconGlyph = 'pficon pficon-info' iconTitle = 'Dependent change required for testing' } else if (change.failing_reasons && change.failing_reasons.length > 0) { let reason = change.failing_reasons.join(', ') iconTitle = 'Failing because ' + reason if (reason.match(/merge conflict/)) { iconGlyph = 'pficon pficon-error-circle-o zuul-build-merge-conflict' } else { iconGlyph = 'pficon pficon-error-circle-o' } } const icon = ( ) if (change.live) { return ( {icon} ) } else { return icon } } renderLineImg(change, i) { let image = LineTImage if (change._tree_branches.indexOf(i) === change._tree_branches.length - 1) { // Angle line image = LineAngleImage } return Line } render() { const { change, queue, expanded, pipeline, user, tenant } = this.props let row = [] let adminMenuWidth = 15 let i for (i = 0; i < queue._tree_columns; i++) { let className = '' if (i < change._tree.length && change._tree[i] !== null) { className = ' zuul-change-row-line' } row.push( {i === change._tree_index ? this.renderStatusIcon(change) : ''} {change._tree_branches.indexOf(i) !== -1 ? ( this.renderLineImg(change, i)) : ''} ) } let changeWidth = (user.isAdmin && user.scope.indexOf(tenant.name) !== -1) ? 360 - adminMenuWidth - 16 * queue._tree_columns : 360 - 16 * queue._tree_columns row.push( ) if (user.isAdmin && user.scope.indexOf(tenant.name) !== -1) { row.push( {this.renderAdminCommands(i + 2)} ) } return ( <> {row}
{this.renderDequeueModal()} {this.renderPromoteModal()} ) } } export default connect(state => ({ tenant: state.tenant, user: state.user, }))(Change)