diff --git a/src/app/access-control/group-registry/groups-registry.component.html b/src/app/access-control/group-registry/groups-registry.component.html index 510a156476..2c6c2590e2 100644 --- a/src/app/access-control/group-registry/groups-registry.component.html +++ b/src/app/access-control/group-registry/groups-registry.component.html @@ -85,7 +85,7 @@ } @if (!groupDto.group?.permanent && groupDto.ableToDelete) { diff --git a/src/app/access-control/group-registry/groups-registry.component.spec.ts b/src/app/access-control/group-registry/groups-registry.component.spec.ts index 81af022055..673d650723 100644 --- a/src/app/access-control/group-registry/groups-registry.component.spec.ts +++ b/src/app/access-control/group-registry/groups-registry.component.spec.ts @@ -381,6 +381,8 @@ describe('GroupsRegistryComponent', () => { deleteButton.click(); fixture.detectChanges(); + (document as any).querySelector('.modal-footer .confirm').click(); + expect(groupsDataServiceStub.delete).toHaveBeenCalledWith(mockGroups[0].id); }); }); diff --git a/src/app/access-control/group-registry/groups-registry.component.ts b/src/app/access-control/group-registry/groups-registry.component.ts index 9a7924f210..e846c5bbb9 100644 --- a/src/app/access-control/group-registry/groups-registry.component.ts +++ b/src/app/access-control/group-registry/groups-registry.component.ts @@ -9,7 +9,10 @@ import { UntypedFormBuilder, } from '@angular/forms'; import { RouterLink } from '@angular/router'; -import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'; +import { + NgbModal, + NgbTooltipModule, +} from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule, TranslateService, @@ -27,6 +30,7 @@ import { defaultIfEmpty, map, switchMap, + takeUntil, tap, } from 'rxjs/operators'; @@ -57,6 +61,7 @@ import { } from '../../core/shared/operators'; import { PageInfo } from '../../core/shared/page-info.model'; import { BtnDisabledDirective } from '../../shared/btn-disabled.directive'; +import { ConfirmationModalComponent } from '../../shared/confirmation-modal/confirmation-modal.component'; import { hasValue } from '../../shared/empty.util'; import { ThemedLoadingComponent } from '../../shared/loading/themed-loading.component'; import { NotificationsService } from '../../shared/notifications/notifications.service'; @@ -142,6 +147,7 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy { private paginationService: PaginationService, public requestService: RequestService, public dsoNameService: DSONameService, + private modalService: NgbModal, ) { this.currentSearchQuery = ''; this.searchForm = this.formBuilder.group(({ @@ -314,4 +320,30 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy { this.paginationService.clearPagination(this.config.id); } + confirmDelete(group: GroupDtoModel): void { + const modalRef = this.modalService.open(ConfirmationModalComponent); + modalRef.componentInstance.name = this.dsoNameService.getName(group.group); + modalRef.componentInstance.headerLabel = 'admin.access-control.epeople.table.edit.buttons.remove.modal.header'; + modalRef.componentInstance.infoLabel = 'admin.access-control.epeople.table.edit.buttons.remove.modal.info'; + modalRef.componentInstance.cancelLabel = 'admin.access-control.epeople.table.edit.buttons.remove.modal.cancel'; + modalRef.componentInstance.confirmLabel = 'admin.access-control.epeople.table.edit.buttons.remove.modal.confirm'; + modalRef.componentInstance.brandColor = 'danger'; + modalRef.componentInstance.confirmIcon = 'fas fa-trash'; + + const modalSub: Subscription = modalRef.componentInstance.response.pipe( + takeUntil(modalRef.closed), + ).subscribe((result: boolean) => { + if (result === true) { + this.deleteGroup(group); + } + }); + + void modalRef.result.then().finally(() => { + modalRef.close(); + if (modalSub && !modalSub.closed) { + modalSub.unsubscribe(); + } + }); + } + } diff --git a/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.html b/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.html index b136f83dc5..0570691479 100644 --- a/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.html +++ b/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.html @@ -51,7 +51,7 @@ @if (hasCustomGroup$ | async) { } diff --git a/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.spec.ts b/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.spec.ts index 2972cf1656..ff5435c7a1 100644 --- a/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.spec.ts +++ b/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.spec.ts @@ -10,6 +10,7 @@ import { import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule } from '@ngx-translate/core'; import { of } from 'rxjs'; @@ -36,17 +37,22 @@ describe('ComcolRoleComponent', () => { let comcolRole; let notificationsService; - const requestService = { hasByHref$: () => of(true) }; + const requestService = { + hasByHref$: () => of(true), + setStaleByHrefSubstring: () => of(true), + }; const groupService = { findByHref: jasmine.createSpy('findByHref'), createComcolGroup: jasmine.createSpy('createComcolGroup').and.returnValue(of({})), deleteComcolGroup: jasmine.createSpy('deleteComcolGroup').and.returnValue(of({})), + clearGroupsRequests: jasmine.createSpy('clearGroupsRequests'), }; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [ + NgbModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NoopAnimationsModule, @@ -174,17 +180,18 @@ describe('ComcolRoleComponent', () => { name: 'custom group name', }; statusCode = 200; - comp.comcolRole = comcolRole; + comp.comcolRole = { + name: 'test role name' + Math.random(), + href: 'test role link', + }; + comp.roleName$ = of(comcolRole.name); fixture.detectChanges(); }); it('should have a delete button but no create or restrict button', (done) => { - expect(de.query(By.css('.btn.create'))) - .toBeNull(); - expect(de.query(By.css('.btn.restrict'))) - .toBeNull(); - expect(de.query(By.css('.btn.delete'))) - .toBeTruthy(); + expect(de.query(By.css('.btn.create'))).toBeNull(); + expect(de.query(By.css('.btn.restrict'))).toBeNull(); + expect(de.query(By.css('.btn.delete'))).toBeTruthy(); done(); }); @@ -194,7 +201,16 @@ describe('ComcolRoleComponent', () => { de.query(By.css('.btn.delete')).nativeElement.click(); }); + afterEach(() => { + const modal = document.querySelector('ds-confirmation-modal'); + if (modal) { + modal.remove(); + } + }); + it('should call the groupService delete method', (done) => { + (document as any).querySelector('.modal-footer .confirm').click(); + fixture.detectChanges(); expect(groupService.deleteComcolGroup).toHaveBeenCalled(); done(); }); @@ -204,12 +220,24 @@ describe('ComcolRoleComponent', () => { beforeEach(() => { groupService.deleteComcolGroup.and.returnValue(createFailedRemoteDataObject$()); de.query(By.css('.btn.delete')).nativeElement.click(); + fixture.detectChanges(); + }); + + afterEach(() => { + const modal = document.querySelector('ds-confirmation-modal'); + if (modal) { + modal.remove(); + } }); it('should show an error notification', (done) => { + (document as any).querySelector('.modal-footer .confirm').click(); + fixture.detectChanges(); + expect(notificationsService.error).toHaveBeenCalled(); done(); }); }); + }); }); diff --git a/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.ts b/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.ts index c0ddbd6cc2..0519760019 100644 --- a/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.ts +++ b/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.ts @@ -5,6 +5,7 @@ import { OnInit, } from '@angular/core'; import { RouterLink } from '@angular/router'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule, TranslateService, @@ -12,11 +13,13 @@ import { import { BehaviorSubject, Observable, + Subscription, } from 'rxjs'; import { filter, map, switchMap, + takeUntil, } from 'rxjs/operators'; import { getGroupEditRoute } from '../../../../../access-control/access-control-routing-paths'; @@ -34,6 +37,7 @@ import { getFirstCompletedRemoteData, } from '../../../../../core/shared/operators'; import { AlertComponent } from '../../../../alert/alert.component'; +import { ConfirmationModalComponent } from '../../../../confirmation-modal/confirmation-modal.component'; import { hasNoValue, hasValue, @@ -115,6 +119,7 @@ export class ComcolRoleComponent implements OnInit { protected notificationsService: NotificationsService, protected translateService: TranslateService, public dsoNameService: DSONameService, + private modalService: NgbModal, ) { } @@ -215,4 +220,31 @@ export class ComcolRoleComponent implements OnInit { this.roleName$ = this.translateService.get(`comcol-role.edit.${this.comcolRole.name}.name`); } + + confirmDelete(groupName: string): void { + const modalRef = this.modalService.open(ConfirmationModalComponent); + + modalRef.componentInstance.name = groupName; + modalRef.componentInstance.headerLabel = 'comcol-role.edit.delete.modal.header'; + modalRef.componentInstance.infoLabel = 'comcol-role.edit.delete.modal.info'; + modalRef.componentInstance.cancelLabel = 'comcol-role.edit.delete.modal.cancel'; + modalRef.componentInstance.confirmLabel = 'comcol-role.edit.delete.modal.confirm'; + modalRef.componentInstance.brandColor = 'danger'; + modalRef.componentInstance.confirmIcon = 'fas fa-trash'; + + const modalSub: Subscription = modalRef.componentInstance.response.pipe( + takeUntil(modalRef.closed), + ).subscribe((result: boolean) => { + if (result === true) { + this.delete(); + } + }); + + void modalRef.result.then().finally(() => { + modalRef.close(); + if (modalSub && !modalSub.closed) { + modalSub.unsubscribe(); + } + }); + } } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index c262850745..8488b8f373 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -305,6 +305,14 @@ "admin.access-control.epeople.table.edit.buttons.remove": "Delete \"{{name}}\"", + "admin.access-control.epeople.table.edit.buttons.remove.modal.header": "Delete Group \"{{ dsoName }}\"", + + "admin.access-control.epeople.table.edit.buttons.remove.modal.info": "Are you sure you want to delete Group \"{{ dsoName }}\" and all its associated policies?", + + "admin.access-control.epeople.table.edit.buttons.remove.modal.cancel": "Cancel", + + "admin.access-control.epeople.table.edit.buttons.remove.modal.confirm": "Delete", + "admin.access-control.epeople.no-items": "No EPeople to show.", "admin.access-control.epeople.form.create": "Create EPerson", @@ -1523,6 +1531,14 @@ "comcol-role.edit.delete.error.title": "Failed to delete the '{{ role }}' role's group", + "comcol-role.edit.delete.modal.header": "Delete Group \"{{ dsoName }}\"", + + "comcol-role.edit.delete.modal.info": "Are you sure you want to delete Group \"{{ dsoName }}\" and all its associated policies?", + + "comcol-role.edit.delete.modal.cancel": "Cancel", + + "comcol-role.edit.delete.modal.confirm": "Delete", + "comcol-role.edit.community-admin.name": "Administrators", "comcol-role.edit.collection-admin.name": "Administrators",