From 5bfdbe9867666ef31f4f9faddfa9afaa10d6bdfd Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Tue, 21 Jan 2020 16:06:01 -0500 Subject: [PATCH] LP1860460 Copy delete override repairs, perm failed handler * Teach the Angular holdings module vol/copy delete dialog to correctly report failure events to the user and handle permission overrides. * Add support for automatically launching the op-change dialog when a permission failed event is returned by an API call for any /eg2/staff/ interface. Signed-off-by: Bill Erickson Signed-off-by: Jennifer Weston Signed-off-by: Jane Sandberg --- Open-ILS/src/eg2/src/app/core/auth.service.ts | 5 ++ Open-ILS/src/eg2/src/app/core/net.service.ts | 2 +- .../src/eg2/src/app/staff/nav.component.ts | 24 ++++++++- .../delete-volcopy-dialog.component.html | 51 ++++++++++--------- .../delete-volcopy-dialog.component.ts | 49 +++++++++++++++--- .../share/op-change/op-change.component.ts | 40 ++++++++++++++- 6 files changed, 135 insertions(+), 36 deletions(-) diff --git a/Open-ILS/src/eg2/src/app/core/auth.service.ts b/Open-ILS/src/eg2/src/app/core/auth.service.ts index a173d6304a..9ad471fb02 100644 --- a/Open-ILS/src/eg2/src/app/core/auth.service.ts +++ b/Open-ILS/src/eg2/src/app/core/auth.service.ts @@ -130,6 +130,11 @@ export class AuthService { let service = 'open-ils.auth'; let method = 'open-ils.auth.login'; + if (isOpChange && this.opChangeIsActive()) { + // Enforce one op-change at a time. + this.undoOpChange(); + } + return this.net.request( 'open-ils.auth_proxy', 'open-ils.auth_proxy.enabled') diff --git a/Open-ILS/src/eg2/src/app/core/net.service.ts b/Open-ILS/src/eg2/src/app/core/net.service.ts index dd2bebbd64..42dae19b1c 100644 --- a/Open-ILS/src/eg2/src/app/core/net.service.ts +++ b/Open-ILS/src/eg2/src/app/core/net.service.ts @@ -73,7 +73,7 @@ export class NetService { permFailed$: EventEmitter; authExpired$: EventEmitter; - // If true, permission failures are emitted via permFailed$ + // If true, permission failures are emitted via permFailed // and the active request is marked as superseded. permFailedHasHandler: Boolean = false; diff --git a/Open-ILS/src/eg2/src/app/staff/nav.component.ts b/Open-ILS/src/eg2/src/app/staff/nav.component.ts index f143727a33..5627f762ba 100644 --- a/Open-ILS/src/eg2/src/app/staff/nav.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/nav.component.ts @@ -1,12 +1,15 @@ -import {Component, OnInit, ViewChild} from '@angular/core'; +import {Component, OnInit, OnDestroy, ViewChild} from '@angular/core'; import {ActivatedRoute, Router} from '@angular/router'; import {Location} from '@angular/common'; +import {Subscription} from 'rxjs'; import {OrgService} from '@eg/core/org.service'; import {AuthService} from '@eg/core/auth.service'; import {PcrudService} from '@eg/core/pcrud.service'; import {LocaleService} from '@eg/core/locale.service'; import {PrintService} from '@eg/share/print/print.service'; import {StoreService} from '@eg/core/store.service'; +import {NetRequest, NetService} from '@eg/core/net.service'; +import {OpChangeComponent} from '@eg/staff/share/op-change/op-change.component'; @Component({ selector: 'eg-staff-nav-bar', @@ -14,7 +17,7 @@ import {StoreService} from '@eg/core/store.service'; templateUrl: 'nav.component.html' }) -export class StaffNavComponent implements OnInit { +export class StaffNavComponent implements OnInit, OnDestroy { // Locales that have Angular staff translations locales: any[]; @@ -23,9 +26,13 @@ export class StaffNavComponent implements OnInit { // When active, show a link to the experimental Angular staff catalog showAngularCatalog: boolean; + @ViewChild('navOpChange', {static: false}) opChange: OpChangeComponent; + permFailedSub: Subscription; + constructor( private router: Router, private store: StoreService, + private net: NetService, private org: OrgService, private auth: AuthService, private pcrud: PcrudService, @@ -54,6 +61,19 @@ export class StaffNavComponent implements OnInit { .then(settings => this.showAngularCatalog = Boolean(settings['ui.staff.angular_catalog.enabled'])); } + + // Wire up our op-change component as the general purpose + // permission failed handler. + this.net.permFailedHasHandler = true; + this.permFailedSub = + this.net.permFailed$.subscribe( + (req: NetRequest) => this.opChange.escalateRequest(req)); + } + + ngOnDestroy() { + if (this.permFailedSub) { + this.permFailedSub.unsubscribe(); + } } user() { diff --git a/Open-ILS/src/eg2/src/app/staff/share/holdings/delete-volcopy-dialog.component.html b/Open-ILS/src/eg2/src/app/staff/share/holdings/delete-volcopy-dialog.component.html index 5bde3a7928..f7709cda1c 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/holdings/delete-volcopy-dialog.component.html +++ b/Open-ILS/src/eg2/src/app/staff/share/holdings/delete-volcopy-dialog.component.html @@ -1,33 +1,36 @@ - + + - + + - - - - + + + + diff --git a/Open-ILS/src/eg2/src/app/staff/share/holdings/delete-volcopy-dialog.component.ts b/Open-ILS/src/eg2/src/app/staff/share/holdings/delete-volcopy-dialog.component.ts index 3941d34881..76fbd58ede 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/holdings/delete-volcopy-dialog.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/share/holdings/delete-volcopy-dialog.component.ts @@ -2,13 +2,14 @@ import {Component, OnInit, Input, ViewChild, Renderer2} from '@angular/core'; import {Observable, throwError} from 'rxjs'; import {IdlObject} from '@eg/core/idl.service'; import {NetService} from '@eg/core/net.service'; -import {EventService} from '@eg/core/event.service'; +import {EgEvent, EventService} from '@eg/core/event.service'; import {PcrudService} from '@eg/core/pcrud.service'; import {ToastService} from '@eg/share/toast/toast.service'; import {AuthService} from '@eg/core/auth.service'; import {NgbModal, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap'; import {DialogComponent} from '@eg/share/dialog/dialog.component'; import {StringComponent} from '@eg/share/string/string.component'; +import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component'; /** @@ -38,6 +39,7 @@ export class DeleteHoldingDialogComponent numCopies: number; numSucceeded: number; numFailed: number; + deleteEventDesc: string; @ViewChild('successMsg', { static: true }) private successMsg: StringComponent; @@ -45,6 +47,9 @@ export class DeleteHoldingDialogComponent @ViewChild('errorMsg', { static: true }) private errorMsg: StringComponent; + @ViewChild('confirmOverride', {static: false}) + private confirmOverride: ConfirmDialogComponent; + constructor( private modal: NgbModal, // required for passing to parent private toast: ToastService, @@ -89,23 +94,28 @@ export class DeleteHoldingDialogComponent return super.open(args); } - deleteHoldings() { + deleteHoldings(override?: boolean) { + + this.deleteEventDesc = ''; - const flags = { + const flags: any = { force_delete_copies: this.forceDeleteCopies }; + let method = 'open-ils.cat.asset.volume.fleshed.batch.update'; + if (override) { + method = `${method}.override`; + flags.events = ['TITLE_LAST_COPY', 'COPY_DELETE_WARNING']; + } + this.net.request( - 'open-ils.cat', - 'open-ils.cat.asset.volume.fleshed.batch.update.override', + 'open-ils.cat', method, this.auth.token(), this.callNums, 1, flags ).toPromise().then( result => { const evt = this.evt.parse(result); if (evt) { - console.warn(evt); - this.errorMsg.current().then(msg => this.toast.warning(msg)); - this.numFailed++; + this.handleDeleteEvent(evt, override); } else { this.numSucceeded++; this.close(this.numSucceeded > 0); @@ -118,6 +128,29 @@ export class DeleteHoldingDialogComponent } ); } + + handleDeleteEvent(evt: EgEvent, override?: boolean): Promise { + + if (override) { // override failed + console.warn(evt); + this.numFailed++; + return this.errorMsg.current().then(msg => this.toast.warning(msg)); + } + + this.deleteEventDesc = evt.desc; + + return this.confirmOverride.open().toPromise().then(confirmed => { + if (confirmed) { + return this.deleteHoldings(true); + + } else { + // User canceled the delete confirmation dialog + this.numFailed++; + this.errorMsg.current().then(msg => this.toast.warning(msg)); + this.close(this.numSucceeded > 0); + } + }); + } } diff --git a/Open-ILS/src/eg2/src/app/staff/share/op-change/op-change.component.ts b/Open-ILS/src/eg2/src/app/staff/share/op-change/op-change.component.ts index 95d4db87e7..7b854ab599 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/op-change/op-change.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/share/op-change/op-change.component.ts @@ -3,6 +3,7 @@ import {ToastService} from '@eg/share/toast/toast.service'; import {AuthService} from '@eg/core/auth.service'; import {DialogComponent} from '@eg/share/dialog/dialog.component'; import {NgbModal} from '@ng-bootstrap/ng-bootstrap'; +import {NetRequest, NetService} from '@eg/core/net.service'; @Component({ selector: 'eg-op-change', @@ -19,16 +20,18 @@ export class OpChangeComponent @Input() successMessage: string; @Input() failMessage: string; + requestToEscalate: NetRequest; + constructor( private modal: NgbModal, // required for passing to parent private renderer: Renderer2, private toast: ToastService, + private net: NetService, private auth: AuthService) { super(modal); } ngOnInit() { - // Focus the username any time the dialog is opened. this.onOpen$.subscribe( val => this.renderer.selectRootElement('#username').focus() @@ -56,6 +59,10 @@ export class OpChangeComponent ok2 => { this.close(); this.toast.success(this.successMessage); + if (this.requestToEscalate) { + // Allow a breath for the dialog to clean up. + setTimeout(() => this.sendEscalatedRequest()); + } } ); }, @@ -72,6 +79,37 @@ export class OpChangeComponent err => this.toast.danger(this.failMessage) ); } + + escalateRequest(req: NetRequest) { + this.requestToEscalate = req; + this.open({}); + } + + // Resend a net request using the credentials just created + // via operator change. + sendEscalatedRequest() { + const sourceReq = this.requestToEscalate; + delete this.requestToEscalate; + + console.debug('Op-Change escalating request', sourceReq); + + // Clone the source request, modifying the params to + // use the op-change'd authtoken + const req = new NetRequest( + sourceReq.service, + sourceReq.method, + [this.auth.token()].concat(sourceReq.params.splice(1)) + ); + + // Relay responses received for our escalated request to + // the caller via the original request observer. + this.net.requestCompiled(req) + .subscribe( + res => sourceReq.observer.next(res), + err => sourceReq.observer.error(err), + () => sourceReq.observer.complete() + ).add(_ => this.auth.undoOpChange()); + } } -- 2.43.2