From be72a596d18fac13f35ffc14e9c62d559a3a3f10 Mon Sep 17 00:00:00 2001 From: Jane Sandberg Date: Thu, 2 Feb 2023 16:15:32 -0800 Subject: [PATCH] LP2002435: Add optional undelete action to basic admin page By default, this undelete action is only enabled on the shelving location editor. Also, only show admin page delete button if everything selected is deleteable Signed-off-by: Jane Sandberg Signed-off-by: Michele Morgan --- .../staff/admin/basic-admin-page.component.spec.ts | 9 ++- .../app/staff/admin/basic-admin-page.component.ts | 4 ++ .../src/app/staff/admin/local/routing.module.ts | 1 + .../share/admin-page/admin-page.component.html | 12 +++- .../share/admin-page/admin-page.component.spec.ts | 65 ++++++++++++++++++++++ .../staff/share/admin-page/admin-page.component.ts | 50 +++++++++++++++++ 6 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.spec.ts diff --git a/Open-ILS/src/eg2/src/app/staff/admin/basic-admin-page.component.spec.ts b/Open-ILS/src/eg2/src/app/staff/admin/basic-admin-page.component.spec.ts index 7a5cb9ddfe..764c473b4d 100644 --- a/Open-ILS/src/eg2/src/app/staff/admin/basic-admin-page.component.spec.ts +++ b/Open-ILS/src/eg2/src/app/staff/admin/basic-admin-page.component.spec.ts @@ -34,6 +34,7 @@ class MockAdminPageComponent { @Input() idlClass: string; @Input() persistKeyPfx: string; @Input() readonlyFields: string; + @Input() enableUndelete: boolean; } describe('Component: BasicAdminPage', () => { @@ -68,7 +69,8 @@ describe('Component: BasicAdminPage', () => { const data = [{ schema: 'schema1', table: 'table1', - defaultNewRecord: { field1: 'value1' } + defaultNewRecord: { field1: 'value1' }, + enableUndelete: true }]; const parentRoute = { url: of('') }; const snapshot = { parent: { url: [{ path: '' }] } }; @@ -103,4 +105,9 @@ describe('Component: BasicAdminPage', () => { By.directive(MockAdminPageComponent)).componentInstance; expect(adminPage.defaultNewRecord.a[0]).toEqual('value1'); }); + it('sets enableUndelete from routing data', () => { + const adminPage: MockAdminPageComponent = fixture.debugElement.query( + By.directive(MockAdminPageComponent)).componentInstance; + expect(adminPage.enableUndelete).toEqual(true); + }); }); diff --git a/Open-ILS/src/eg2/src/app/staff/admin/basic-admin-page.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/basic-admin-page.component.ts index 1f63253b7f..cd6dbde0d4 100644 --- a/Open-ILS/src/eg2/src/app/staff/admin/basic-admin-page.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/admin/basic-admin-page.component.ts @@ -20,6 +20,7 @@ import {tap, switchMap} from 'rxjs/operators'; fieldOrder="{{fieldOrder}}" readonlyFields="{{readonlyFields}}" [defaultNewRecord]="defaultNewRecordIdl" + [enableUndelete]="enableUndelete" [disableOrgFilter]="disableOrgFilter"> ` @@ -38,6 +39,8 @@ export class BasicAdminPageComponent implements OnInit { // Tell the admin page to disable and hide the automagic org unit filter disableOrgFilter: boolean; + enableUndelete: boolean; + private getParams$: Observable; private getRouteData$: Observable; private getParentUrl$: Observable; @@ -73,6 +76,7 @@ export class BasicAdminPageComponent implements OnInit { this.table = data['table']; } this.disableOrgFilter = data['disableOrgFilter']; + this.enableUndelete = data['enableUndelete']; this.fieldOrder = data['fieldOrder']; this.readonlyFields = data['readonlyFields']; this.defaultNewRecord = data['defaultNewRecord']; diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/routing.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/local/routing.module.ts index 22e40f67c7..95139c74d6 100644 --- a/Open-ILS/src/eg2/src/app/staff/admin/local/routing.module.ts +++ b/Open-ILS/src/eg2/src/app/staff/admin/local/routing.module.ts @@ -28,6 +28,7 @@ const routes: Routes = [{ data: [{ schema: 'asset', table: 'copy_location', + enableUndelete: true, readonlyFields: 'deleted', fieldOrder: 'owning_lib,name,opac_visible,circulate,holdable,hold_verify,checkin_alert,deleted,label_prefix,label_suffix,url,id'}] }, { diff --git a/Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.html b/Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.html index 817a0181fd..01b20167a7 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.html +++ b/Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.html @@ -10,6 +10,12 @@ {{idlClassDef.label}} Successfully Deleted +Undelete of {{idlClassDef.label}} failed or was not allowed + + +{{idlClassDef.label}} Successfully undeleted + + {{idlClassDef.label}} Successfully Created @@ -64,7 +70,11 @@ - + + + diff --git a/Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.spec.ts b/Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.spec.ts new file mode 100644 index 0000000000..28139b5ce5 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.spec.ts @@ -0,0 +1,65 @@ +import { ActivatedRoute } from "@angular/router"; +import { AdminPageComponent } from "./admin-page.component"; +import { Location } from '@angular/common'; +import { FormatService } from "@eg/core/format.service"; +import { IdlService } from "@eg/core/idl.service"; +import { OrgService } from "@eg/core/org.service"; +import { AuthService } from "@eg/core/auth.service"; +import { PcrudService } from "@eg/core/pcrud.service"; +import { PermService } from "@eg/core/perm.service"; +import { ToastService } from "@eg/share/toast/toast.service"; + +describe('CopyAttrsComponent', () => { + let component: AdminPageComponent; + + const routeMock = jasmine.createSpyObj(['snapshot']); + const locationMock = jasmine.createSpyObj(['prepareExternalUrl']); + const formatMock = jasmine.createSpyObj(['transform']); + const idlMock = jasmine.createSpyObj(['classes']); + const orgMock = jasmine.createSpyObj(['get']); + const authMock = jasmine.createSpyObj(['user']); + const pcrudMock = jasmine.createSpyObj(['retrieveAll']); + const permMock = jasmine.createSpyObj(['hasWorkPermAt']); + const toastMock = jasmine.createSpyObj(['success']); + beforeEach(() => { + component = new AdminPageComponent(routeMock, locationMock, formatMock, + idlMock, orgMock, authMock, pcrudMock, permMock, toastMock); + }) + + describe('#shouldDisableDelete', () => { + it('returns true if one of the rows is already deleted', () => { + const rows = [ + {isdeleted: () => true, a: [], classname: '', _isfieldmapper: true }, + {isdeleted: () => false, a: [], classname: '', _isfieldmapper: true } + ]; + expect(component.shouldDisableDelete(rows)).toBe(true); + }); + it('returns true if no rows selected', () => { + expect(component.shouldDisableDelete([])).toBe(true); + }) + it('returns false (i.e. you _should_ display delete) if no selected rows are deleted', () => { + const rows = [ + {isdeleted: () => false, deleted: () => 'f', a: [], classname: '', _isfieldmapper: true } + ]; + expect(component.shouldDisableDelete(rows)).toBe(false); + }) + }); + describe('#shouldDisableUndelete', () => { + it('returns true if none of the rows are deleted', () => { + const rows = [ + {isdeleted: () => false, a: [], classname: '', _isfieldmapper: true }, + {deleted: () => 'f', a: [], classname: '', _isfieldmapper: true } + ]; + expect(component.shouldDisableUndelete(rows)).toBe(true); + }); + it('returns true if no rows selected', () => { + expect(component.shouldDisableUndelete([])).toBe(true); + }) + it('returns false (i.e. you _should_ display undelete) if all selected rows are deleted', () => { + const rows = [ + {deleted: () => 't', a: [], classname: '', _isfieldmapper: true } + ]; + expect(component.shouldDisableUndelete(rows)).toBe(false); + }) + }); +}); diff --git a/Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.ts b/Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.ts index 7098c70f2d..d9de7bde3d 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.ts @@ -60,6 +60,9 @@ export class AdminPageComponent implements OnInit { // Disable the auto-matic org unit field filter @Input() disableOrgFilter: boolean; + // Give the grid an option to undelete any deleted rows + @Input() enableUndelete: boolean; + // Include objects linking to org units which are ancestors // of the selected org unit. @Input() includeOrgAncestors: boolean; @@ -106,6 +109,8 @@ export class AdminPageComponent implements OnInit { @ViewChild('updateFailedString', { static: true }) updateFailedString: StringComponent; @ViewChild('deleteFailedString', { static: true }) deleteFailedString: StringComponent; @ViewChild('deleteSuccessString', { static: true }) deleteSuccessString: StringComponent; + @ViewChild('undeleteFailedString', { static: true }) undeleteFailedString: StringComponent; + @ViewChild('undeleteSuccessString', { static: true }) undeleteSuccessString: StringComponent; @ViewChild('translator', { static: true }) translator: TranslateComponent; idlClassDef: any; @@ -340,6 +345,21 @@ export class AdminPageComponent implements OnInit { editOneThing(idlThings.shift()); } + undeleteSelected(idlThings: IdlObject[]) { + idlThings.forEach(idlThing => idlThing.deleted(false)); + this.pcrud.update(idlThings).subscribe( + val => { + this.undeleteSuccessString.current() + .then(str => this.toast.success(str)); + }, + err => { + this.undeleteFailedString.current() + .then(str => this.toast.danger(str)); + }, + () => this.grid.reload() + ); + } + deleteSelected(idlThings: IdlObject[]) { idlThings.forEach(idlThing => idlThing.isdeleted(true)); this.pcrud.autoApply(idlThings).subscribe( @@ -355,6 +375,36 @@ export class AdminPageComponent implements OnInit { ); } + shouldDisableDelete(rows: IdlObject[]): boolean { + if (rows.length === 0) { + return true; + } else { + const deletedRows = rows.filter((row) => { + if (row.deleted && row.deleted() === 't') { + return true; + } else if (row.isdeleted) { + return row.isdeleted(); + } + }); + return deletedRows.length > 0; + } + } + + shouldDisableUndelete(rows: IdlObject[]): boolean { + if (rows.length === 0) { + return true; + } else { + const deletedRows = rows.filter((row) => { + if (row.deleted && row.deleted() === 't') { + return true; + } else if (row.isdeleted) { + return row.isdeleted(); + } + }); + return deletedRows.length !== rows.length; + } + } + createNew() { this.editDialog.mode = 'create'; // We reuse the same editor for all actions. Be sure -- 2.11.0