Skip to content

Commit 57bf254

Browse files
committed
fix: confirm dialog for group deletion
Introduced a confirmation modal before deleting groups in both the Group Registry and Comcol Role components. This ensures users explicitly confirm deletion, reducing accidental data loss. Updated relevant tests and added new translations for modal text.
1 parent db81d5e commit 57bf254

7 files changed

Lines changed: 121 additions & 11 deletions

File tree

src/app/access-control/group-registry/groups-registry.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ <h2 id="search" class="border-bottom pb-2">{{messagePrefix + 'search.head' | tra
8585
}
8686
@if (!groupDto.group?.permanent && groupDto.ableToDelete) {
8787
<button
88-
(click)="deleteGroup(groupDto)" class="btn btn-outline-danger btn-sm btn-delete"
88+
(click)="confirmDelete(groupDto)" class="btn btn-outline-danger btn-sm btn-delete"
8989
title="{{messagePrefix + 'table.edit.buttons.remove' | translate: {name: dsoNameService.getName(groupDto.group) } }}">
9090
<i class="fas fa-trash-alt fa-fw"></i>
9191
</button>

src/app/access-control/group-registry/groups-registry.component.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,8 @@ describe('GroupsRegistryComponent', () => {
381381
deleteButton.click();
382382
fixture.detectChanges();
383383

384+
(document as any).querySelector('.modal-footer .confirm').click();
385+
384386
expect(groupsDataServiceStub.delete).toHaveBeenCalledWith(mockGroups[0].id);
385387
});
386388
});

src/app/access-control/group-registry/groups-registry.component.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ import {
99
UntypedFormBuilder,
1010
} from '@angular/forms';
1111
import { RouterLink } from '@angular/router';
12-
import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
12+
import {
13+
NgbModal,
14+
NgbTooltipModule,
15+
} from '@ng-bootstrap/ng-bootstrap';
1316
import {
1417
TranslateModule,
1518
TranslateService,
@@ -27,6 +30,7 @@ import {
2730
defaultIfEmpty,
2831
map,
2932
switchMap,
33+
takeUntil,
3034
tap,
3135
} from 'rxjs/operators';
3236

@@ -57,6 +61,7 @@ import {
5761
} from '../../core/shared/operators';
5862
import { PageInfo } from '../../core/shared/page-info.model';
5963
import { BtnDisabledDirective } from '../../shared/btn-disabled.directive';
64+
import { ConfirmationModalComponent } from '../../shared/confirmation-modal/confirmation-modal.component';
6065
import { hasValue } from '../../shared/empty.util';
6166
import { ThemedLoadingComponent } from '../../shared/loading/themed-loading.component';
6267
import { NotificationsService } from '../../shared/notifications/notifications.service';
@@ -142,6 +147,7 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
142147
private paginationService: PaginationService,
143148
public requestService: RequestService,
144149
public dsoNameService: DSONameService,
150+
private modalService: NgbModal,
145151
) {
146152
this.currentSearchQuery = '';
147153
this.searchForm = this.formBuilder.group(({
@@ -314,4 +320,30 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
314320
this.paginationService.clearPagination(this.config.id);
315321
}
316322

323+
confirmDelete(group: GroupDtoModel): void {
324+
const modalRef = this.modalService.open(ConfirmationModalComponent);
325+
modalRef.componentInstance.name = this.dsoNameService.getName(group.group);
326+
modalRef.componentInstance.headerLabel = 'admin.access-control.epeople.table.edit.buttons.remove.modal.header';
327+
modalRef.componentInstance.infoLabel = 'admin.access-control.epeople.table.edit.buttons.remove.modal.info';
328+
modalRef.componentInstance.cancelLabel = 'admin.access-control.epeople.table.edit.buttons.remove.modal.cancel';
329+
modalRef.componentInstance.confirmLabel = 'admin.access-control.epeople.table.edit.buttons.remove.modal.confirm';
330+
modalRef.componentInstance.brandColor = 'danger';
331+
modalRef.componentInstance.confirmIcon = 'fas fa-trash';
332+
333+
const modalSub: Subscription = modalRef.componentInstance.response.pipe(
334+
takeUntil(modalRef.closed),
335+
).subscribe((result: boolean) => {
336+
if (result === true) {
337+
this.deleteGroup(group);
338+
}
339+
});
340+
341+
void modalRef.result.then().finally(() => {
342+
modalRef.close();
343+
if (modalSub && !modalSub.closed) {
344+
modalSub.unsubscribe();
345+
}
346+
});
347+
}
348+
317349
}

src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ <h2 class="h4 w-100">
5151
@if (hasCustomGroup$ | async) {
5252
<button
5353
class="btn btn-danger delete"
54-
(click)="delete()">
54+
(click)="confirmDelete(dsoNameService.getName(group))">
5555
<i class="fas fa-trash" aria-hidden="true"></i> {{'comcol-role.edit.delete' | translate}}
5656
</button>
5757
}

src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.spec.ts

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
import { By } from '@angular/platform-browser';
1111
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
1212
import { RouterTestingModule } from '@angular/router/testing';
13+
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
1314
import { TranslateModule } from '@ngx-translate/core';
1415
import { of } from 'rxjs';
1516

@@ -36,17 +37,22 @@ describe('ComcolRoleComponent', () => {
3637
let comcolRole;
3738
let notificationsService;
3839

39-
const requestService = { hasByHref$: () => of(true) };
40+
const requestService = {
41+
hasByHref$: () => of(true),
42+
setStaleByHrefSubstring: () => of(true),
43+
};
4044

4145
const groupService = {
4246
findByHref: jasmine.createSpy('findByHref'),
4347
createComcolGroup: jasmine.createSpy('createComcolGroup').and.returnValue(of({})),
4448
deleteComcolGroup: jasmine.createSpy('deleteComcolGroup').and.returnValue(of({})),
49+
clearGroupsRequests: jasmine.createSpy('clearGroupsRequests'),
4550
};
4651

4752
beforeEach(waitForAsync(() => {
4853
TestBed.configureTestingModule({
4954
imports: [
55+
NgbModule,
5056
RouterTestingModule.withRoutes([]),
5157
TranslateModule.forRoot(),
5258
NoopAnimationsModule,
@@ -174,17 +180,18 @@ describe('ComcolRoleComponent', () => {
174180
name: 'custom group name',
175181
};
176182
statusCode = 200;
177-
comp.comcolRole = comcolRole;
183+
comp.comcolRole = {
184+
name: 'test role name' + Math.random(),
185+
href: 'test role link',
186+
};
187+
comp.roleName$ = of(comcolRole.name);
178188
fixture.detectChanges();
179189
});
180190

181191
it('should have a delete button but no create or restrict button', (done) => {
182-
expect(de.query(By.css('.btn.create')))
183-
.toBeNull();
184-
expect(de.query(By.css('.btn.restrict')))
185-
.toBeNull();
186-
expect(de.query(By.css('.btn.delete')))
187-
.toBeTruthy();
192+
expect(de.query(By.css('.btn.create'))).toBeNull();
193+
expect(de.query(By.css('.btn.restrict'))).toBeNull();
194+
expect(de.query(By.css('.btn.delete'))).toBeTruthy();
188195
done();
189196
});
190197

@@ -194,7 +201,16 @@ describe('ComcolRoleComponent', () => {
194201
de.query(By.css('.btn.delete')).nativeElement.click();
195202
});
196203

204+
afterEach(() => {
205+
const modal = document.querySelector('ds-confirmation-modal');
206+
if (modal) {
207+
modal.remove();
208+
}
209+
});
210+
197211
it('should call the groupService delete method', (done) => {
212+
(document as any).querySelector('.modal-footer .confirm').click();
213+
fixture.detectChanges();
198214
expect(groupService.deleteComcolGroup).toHaveBeenCalled();
199215
done();
200216
});
@@ -204,12 +220,24 @@ describe('ComcolRoleComponent', () => {
204220
beforeEach(() => {
205221
groupService.deleteComcolGroup.and.returnValue(createFailedRemoteDataObject$());
206222
de.query(By.css('.btn.delete')).nativeElement.click();
223+
fixture.detectChanges();
224+
});
225+
226+
afterEach(() => {
227+
const modal = document.querySelector('ds-confirmation-modal');
228+
if (modal) {
229+
modal.remove();
230+
}
207231
});
208232

209233
it('should show an error notification', (done) => {
234+
(document as any).querySelector('.modal-footer .confirm').click();
235+
fixture.detectChanges();
236+
210237
expect(notificationsService.error).toHaveBeenCalled();
211238
done();
212239
});
213240
});
241+
214242
});
215243
});

src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,21 @@ import {
55
OnInit,
66
} from '@angular/core';
77
import { RouterLink } from '@angular/router';
8+
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
89
import {
910
TranslateModule,
1011
TranslateService,
1112
} from '@ngx-translate/core';
1213
import {
1314
BehaviorSubject,
1415
Observable,
16+
Subscription,
1517
} from 'rxjs';
1618
import {
1719
filter,
1820
map,
1921
switchMap,
22+
takeUntil,
2023
} from 'rxjs/operators';
2124

2225
import { getGroupEditRoute } from '../../../../../access-control/access-control-routing-paths';
@@ -34,6 +37,7 @@ import {
3437
getFirstCompletedRemoteData,
3538
} from '../../../../../core/shared/operators';
3639
import { AlertComponent } from '../../../../alert/alert.component';
40+
import { ConfirmationModalComponent } from '../../../../confirmation-modal/confirmation-modal.component';
3741
import {
3842
hasNoValue,
3943
hasValue,
@@ -115,6 +119,7 @@ export class ComcolRoleComponent implements OnInit {
115119
protected notificationsService: NotificationsService,
116120
protected translateService: TranslateService,
117121
public dsoNameService: DSONameService,
122+
private modalService: NgbModal,
118123
) {
119124
}
120125

@@ -215,4 +220,31 @@ export class ComcolRoleComponent implements OnInit {
215220

216221
this.roleName$ = this.translateService.get(`comcol-role.edit.${this.comcolRole.name}.name`);
217222
}
223+
224+
confirmDelete(groupName: string): void {
225+
const modalRef = this.modalService.open(ConfirmationModalComponent);
226+
227+
modalRef.componentInstance.name = groupName;
228+
modalRef.componentInstance.headerLabel = 'comcol-role.edit.delete.modal.header';
229+
modalRef.componentInstance.infoLabel = 'comcol-role.edit.delete.modal.info';
230+
modalRef.componentInstance.cancelLabel = 'comcol-role.edit.delete.modal.cancel';
231+
modalRef.componentInstance.confirmLabel = 'comcol-role.edit.delete.modal.confirm';
232+
modalRef.componentInstance.brandColor = 'danger';
233+
modalRef.componentInstance.confirmIcon = 'fas fa-trash';
234+
235+
const modalSub: Subscription = modalRef.componentInstance.response.pipe(
236+
takeUntil(modalRef.closed),
237+
).subscribe((result: boolean) => {
238+
if (result === true) {
239+
this.delete();
240+
}
241+
});
242+
243+
void modalRef.result.then().finally(() => {
244+
modalRef.close();
245+
if (modalSub && !modalSub.closed) {
246+
modalSub.unsubscribe();
247+
}
248+
});
249+
}
218250
}

src/assets/i18n/en.json5

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,14 @@
305305

306306
"admin.access-control.epeople.table.edit.buttons.remove": "Delete \"{{name}}\"",
307307

308+
"admin.access-control.epeople.table.edit.buttons.remove.modal.header": "Delete Group \"{{ dsoName }}\"",
309+
310+
"admin.access-control.epeople.table.edit.buttons.remove.modal.info": "Are you sure you want to delete Group \"{{ dsoName }}\" and all its associated policies?",
311+
312+
"admin.access-control.epeople.table.edit.buttons.remove.modal.cancel": "Cancel",
313+
314+
"admin.access-control.epeople.table.edit.buttons.remove.modal.confirm": "Delete",
315+
308316
"admin.access-control.epeople.no-items": "No EPeople to show.",
309317

310318
"admin.access-control.epeople.form.create": "Create EPerson",
@@ -1523,6 +1531,14 @@
15231531

15241532
"comcol-role.edit.delete.error.title": "Failed to delete the '{{ role }}' role's group",
15251533

1534+
"comcol-role.edit.delete.modal.header": "Delete Group \"{{ dsoName }}\"",
1535+
1536+
"comcol-role.edit.delete.modal.info": "Are you sure you want to delete Group \"{{ dsoName }}\" and all its associated policies?",
1537+
1538+
"comcol-role.edit.delete.modal.cancel": "Cancel",
1539+
1540+
"comcol-role.edit.delete.modal.confirm": "Delete",
1541+
15261542
"comcol-role.edit.community-admin.name": "Administrators",
15271543

15281544
"comcol-role.edit.collection-admin.name": "Administrators",

0 commit comments

Comments
 (0)