| /** |
| * @license |
| * Copyright 2022 Google LLC |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| import {customElement, state, query} from 'lit/decorators.js'; |
| import {LitElement, html, css} from 'lit'; |
| import {resolve} from '../../../models/dependency'; |
| import {bulkActionsModelToken} from '../../../models/bulk-actions/bulk-actions-model'; |
| import {NumericChangeId, ChangeInfo, ChangeStatus} from '../../../api/rest-api'; |
| import {subscribe} from '../../lit/subscription-controller'; |
| import {ProgressStatus} from '../../../constants/constants'; |
| import '../../shared/gr-dialog/gr-dialog'; |
| import {fireAlert, fireReload} from '../../../utils/event-util'; |
| import {modalStyles} from '../../../styles/gr-modal-styles'; |
| |
| @customElement('gr-change-list-bulk-abandon-flow') |
| export class GrChangeListBulkAbandonFlow extends LitElement { |
| private readonly getBulkActionsModel = resolve(this, bulkActionsModelToken); |
| |
| @state() selectedChanges: ChangeInfo[] = []; |
| |
| @state() progress: Map<NumericChangeId, ProgressStatus> = new Map(); |
| |
| @query('#actionModal') actionModal!: HTMLDialogElement; |
| |
| static override get styles() { |
| return [ |
| modalStyles, |
| css` |
| section { |
| padding: var(--spacing-l); |
| } |
| `, |
| ]; |
| } |
| |
| constructor() { |
| super(); |
| subscribe( |
| this, |
| () => this.getBulkActionsModel().selectedChanges$, |
| selectedChanges => (this.selectedChanges = selectedChanges) |
| ); |
| } |
| |
| override render() { |
| return html` |
| <gr-button |
| id="abandon" |
| flatten |
| .disabled=${!this.isEnabled()} |
| @click=${() => this.actionModal.showModal()} |
| >Abandon</gr-button |
| > |
| <dialog id="actionModal" tabindex="-1"> |
| <gr-dialog |
| .disableCancel=${!this.isCancelEnabled()} |
| .disabled=${this.isDisabled()} |
| @confirm=${() => this.handleConfirm()} |
| @cancel=${() => this.handleClose()} |
| .cancelLabel=${'Close'} |
| > |
| <div slot="header"> |
| ${this.selectedChanges.length} changes to abandon |
| </div> |
| <div slot="main"> |
| <table> |
| <thead> |
| <tr> |
| <th>Subject</th> |
| <th>Status</th> |
| </tr> |
| </thead> |
| <tbody> |
| ${this.selectedChanges.map( |
| change => html` |
| <tr> |
| <td>Change: ${change.subject}</td> |
| <td id="status"> |
| Status: ${this.getStatus(change._number)} |
| </td> |
| </tr> |
| ` |
| )} |
| </tbody> |
| </table> |
| </div> |
| </gr-dialog> |
| </dialog> |
| `; |
| } |
| |
| private getStatus(changeNum: NumericChangeId) { |
| return this.progress.has(changeNum) |
| ? this.progress.get(changeNum) |
| : ProgressStatus.NOT_STARTED; |
| } |
| |
| private isEnabled() { |
| return this.selectedChanges.every( |
| change => |
| !!change.actions?.abandon || change.status === ChangeStatus.ABANDONED |
| ); |
| } |
| |
| private isDisabled() { |
| // Action is allowed if none of the changes have any bulk action performed |
| // on them. In case an error happens then we keep the button disabled. |
| for (const status of this.progress.values()) { |
| if (status !== ProgressStatus.NOT_STARTED) return true; |
| } |
| return false; |
| } |
| |
| private isCancelEnabled() { |
| for (const status of this.progress.values()) { |
| if (status === ProgressStatus.RUNNING) return false; |
| } |
| return true; |
| } |
| |
| private handleConfirm() { |
| this.progress.clear(); |
| for (const change of this.selectedChanges) { |
| this.progress.set(change._number, ProgressStatus.RUNNING); |
| } |
| this.requestUpdate(); |
| const errFn = (changeNum: NumericChangeId) => { |
| throw new Error(`request for ${changeNum} failed`); |
| }; |
| const promises = this.getBulkActionsModel().abandonChanges('', errFn); |
| for (let index = 0; index < promises.length; index++) { |
| const changeNum = this.selectedChanges[index]._number; |
| promises[index] |
| .then(() => { |
| this.progress.set(changeNum, ProgressStatus.SUCCESSFUL); |
| this.requestUpdate(); |
| }) |
| .catch(() => { |
| this.progress.set(changeNum, ProgressStatus.FAILED); |
| this.requestUpdate(); |
| }); |
| } |
| } |
| |
| private handleClose() { |
| this.actionModal.close(); |
| fireAlert(this, 'Reloading page..'); |
| fireReload(this, true); |
| } |
| } |
| |
| declare global { |
| interface HTMLElementTagNameMap { |
| 'gr-change-list-bulk-abandon-flow': GrChangeListBulkAbandonFlow; |
| } |
| } |