From 54635a4de78fc07aae40f1ed6462e18ba43cc7cd Mon Sep 17 00:00:00 2001 From: Matthieu Huin Date: Fri, 19 Mar 2021 19:31:25 +0100 Subject: [PATCH] web UI: allow a privileged user to promote a change Add a "promote" button in the actions menu of a change, if the currently logged in user is an admin on the tenant and if the pipeline is dependent. Clicking the button opens a modal, so that the user can confirm her decision. Change-Id: I8262888aef9ba1a106e0b321cc4cf2e14465b90c --- web/src/actions/adminActions.js | 6 ++ web/src/api.js | 16 +++++ web/src/containers/status/Change.jsx | 81 +++++++++++++++++++++-- web/src/containers/status/ChangePanel.jsx | 2 +- web/src/containers/status/ChangeQueue.jsx | 8 +-- web/src/containers/status/Pipeline.jsx | 2 +- 6 files changed, 104 insertions(+), 11 deletions(-) diff --git a/web/src/actions/adminActions.js b/web/src/actions/adminActions.js index ccd233a6e9..8e2b71ab7a 100644 --- a/web/src/actions/adminActions.js +++ b/web/src/actions/adminActions.js @@ -15,6 +15,7 @@ export const ADMIN_DEQUEUE_FAIL = 'ADMIN_DEQUEUE_FAIL' export const ADMIN_ENQUEUE_FAIL = 'ADMIN_ENQUEUE_FAIL' export const ADMIN_AUTOHOLD_FAIL = 'ADMIN_AUTOHOLD_FAIL' +export const ADMIN_PROMOTE_FAIL = 'ADMIN_PROMOTE_FAIL' export const addDequeueError = error => ({ type: ADMIN_DEQUEUE_FAIL, @@ -30,3 +31,8 @@ export const addAutoholdError = error => ({ type: ADMIN_AUTOHOLD_FAIL, notification: error }) + +export const addPromoteError = error => ({ + type: ADMIN_PROMOTE_FAIL, + notification: error +}) \ No newline at end of file diff --git a/web/src/api.js b/web/src/api.js index 4c22e007b0..7b574a6b69 100644 --- a/web/src/api.js +++ b/web/src/api.js @@ -278,6 +278,21 @@ function autohold_delete(apiPrefix, requestId, token) { return res } +function promote(apiPrefix, pipeline, changes, token) { + const instance = Axios.create({ + baseURL: apiUrl + }) + instance.defaults.headers.common['Authorization'] = 'Bearer ' + token + let res = instance.post( + apiPrefix + '/promote', + { + pipeline: pipeline, + changes: changes, + } + ) + return res +} + export { apiUrl, @@ -310,4 +325,5 @@ export { dequeue_ref, enqueue, enqueue_ref, + promote, } diff --git a/web/src/containers/status/Change.jsx b/web/src/containers/status/Change.jsx index bf710679ed..be488423bf 100644 --- a/web/src/containers/status/Change.jsx +++ b/web/src/containers/status/Change.jsx @@ -26,12 +26,14 @@ import { ModalVariant } from '@patternfly/react-core' import { + AngleDoubleUpIcon, BanIcon, } from '@patternfly/react-icons' -import { dequeue, dequeue_ref } from '../../api' -import { addDequeueError } from '../../actions/adminActions' +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' @@ -43,7 +45,7 @@ class Change extends React.Component { change: PropTypes.object.isRequired, queue: PropTypes.object.isRequired, expanded: PropTypes.bool.isRequired, - pipeline: PropTypes.string, + pipeline: PropTypes.object, tenant: PropTypes.object, user: PropTypes.object, dispatch: PropTypes.func @@ -51,6 +53,7 @@ class Change extends React.Component { state = { showDequeueModal: false, + showPromoteModal: false, showAdminActions: false, } @@ -63,12 +66,18 @@ class Change extends React.Component { // post-merge if (/^[0-9a-f]{40}$/.test(changeId)) { dequeue_ref(tenant.apiPrefix, projectName, pipeline.name, changeRef, user.token) + .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, user.token) + .then(() => { + this.props.dispatch(fetchStatusIfNeeded(tenant)) + }) .catch(error => { this.props.dispatch(addDequeueError(error)) }) @@ -108,13 +117,59 @@ class Change extends React.Component { ) } + promoteConfirm = () => { + const { tenant, user, change, pipeline } = this.props + let changeId = change.id || 'NA' + this.setState(() => ({ showPromoteModal: false })) + if (changeId !== 'N/A') { + promote(tenant.apiPrefix, pipeline.name, [changeId,], user.token) + .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 { queue, pipeline } = this.props const dropdownCommands = [ } @@ -125,6 +180,21 @@ class Change extends React.Component { }} >Dequeue, ] + if (pipeline.manager === 'dependent') { + dropdownCommands.push( + } + description="Promote this change to the top of the queue" + onClick={(event) => { + event.preventDefault() + this.setState(() => ({ showPromoteModal: true })) + }} + >Promote + ) + } return ( {this.renderDequeueModal()} + {this.renderPromoteModal()} ) } diff --git a/web/src/containers/status/ChangePanel.jsx b/web/src/containers/status/ChangePanel.jsx index 6516163290..b7a509c2fa 100644 --- a/web/src/containers/status/ChangePanel.jsx +++ b/web/src/containers/status/ChangePanel.jsx @@ -339,7 +339,7 @@ class ChangePanel extends React.Component { {expand ? this.renderJobList(change.jobs) : ''} - + ) return ( diff --git a/web/src/containers/status/ChangeQueue.jsx b/web/src/containers/status/ChangeQueue.jsx index 319381f4ec..84040ab700 100644 --- a/web/src/containers/status/ChangeQueue.jsx +++ b/web/src/containers/status/ChangeQueue.jsx @@ -20,15 +20,15 @@ import Change from './Change' class ChangeQueue extends React.Component { static propTypes = { - pipeline: PropTypes.string.isRequired, + pipeline: PropTypes.object.isRequired, queue: PropTypes.object.isRequired, expanded: PropTypes.bool.isRequired } - render () { + render() { const { queue, pipeline, expanded } = this.props let fullName = queue.name - if(queue.branch) { + if (queue.branch) { fullName = `${fullName} (${queue.branch})` } let shortName = fullName @@ -49,7 +49,7 @@ class ChangeQueue extends React.Component { }) }) return ( -
+

Queue: {shortName}

{changesList}
) diff --git a/web/src/containers/status/Pipeline.jsx b/web/src/containers/status/Pipeline.jsx index b429477862..00c79ef254 100644 --- a/web/src/containers/status/Pipeline.jsx +++ b/web/src/containers/status/Pipeline.jsx @@ -214,7 +214,7 @@ class Pipeline extends React.Component { ))}