Skip to content

Commit 43e358b

Browse files
authored
Merge pull request #4570 from 4Science/DURACOM-383-BatchDeletingOfDSpaceObjects
Angular: Performance issue when deleting Communities, Collections, or Items with many Bitstreams
2 parents 3bc1e85 + 3aac39e commit 43e358b

File tree

13 files changed

+261
-133
lines changed

13 files changed

+261
-133
lines changed

src/app/collection-page/delete-collection-page/delete-collection-page.component.spec.ts

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,61 @@ import {
55
TestBed,
66
waitForAsync,
77
} from '@angular/core/testing';
8-
import { ActivatedRoute } from '@angular/router';
8+
import {
9+
ActivatedRoute,
10+
Router,
11+
} from '@angular/router';
912
import { RouterTestingModule } from '@angular/router/testing';
1013
import { DSONameService } from '@dspace/core/breadcrumbs/dso-name.service';
1114
import { CollectionDataService } from '@dspace/core/data/collection-data.service';
12-
import { RequestService } from '@dspace/core/data/request.service';
1315
import { NotificationsService } from '@dspace/core/notification-system/notifications.service';
16+
import { ProcessParameter } from '@dspace/core/processes/process-parameter.model';
1417
import { DSONameServiceMock } from '@dspace/core/testing/dso-name.service.mock';
18+
import { NotificationsServiceStub } from '@dspace/core/testing/notifications-service.stub';
19+
import {
20+
createFailedRemoteDataObject$,
21+
createSuccessfulRemoteDataObject$,
22+
} from '@dspace/core/utilities/remote-data.utils';
1523
import { TranslateModule } from '@ngx-translate/core';
1624
import { of } from 'rxjs';
1725

26+
import {
27+
DSPACE_OBJECT_DELETION_SCRIPT_NAME,
28+
ScriptDataService,
29+
} from '../../core/data/processes/script-data.service';
30+
import { Collection } from '../../core/shared/collection.model';
31+
import { getProcessDetailRoute } from '../../process-page/process-page-routing.paths';
1832
import { DeleteCollectionPageComponent } from './delete-collection-page.component';
1933

2034
describe('DeleteCollectionPageComponent', () => {
2135
let comp: DeleteCollectionPageComponent;
2236
let fixture: ComponentFixture<DeleteCollectionPageComponent>;
37+
let scriptService;
38+
let notificationService: NotificationsServiceStub;
39+
let router;
40+
41+
const mockCollection: Collection = Object.assign(new Collection(), {
42+
uuid: 'test-uuid',
43+
id: 'test-uuid',
44+
name: 'Test Collection',
45+
type: 'collection',
46+
});
2347

2448
beforeEach(waitForAsync(() => {
49+
notificationService = new NotificationsServiceStub();
50+
scriptService = jasmine.createSpyObj('scriptService', {
51+
invoke: createSuccessfulRemoteDataObject$({ processId: '123' }),
52+
});
53+
router = jasmine.createSpyObj('router', ['navigateByUrl', 'navigate']);
2554
TestBed.configureTestingModule({
2655
imports: [TranslateModule.forRoot(), CommonModule, RouterTestingModule, DeleteCollectionPageComponent],
2756
providers: [
2857
{ provide: DSONameService, useValue: new DSONameServiceMock() },
2958
{ provide: CollectionDataService, useValue: {} },
30-
{ provide: ActivatedRoute, useValue: { data: of({ dso: { payload: {} } }) } },
31-
{ provide: NotificationsService, useValue: {} },
32-
{ provide: RequestService, useValue: {} },
59+
{ provide: ActivatedRoute, useValue: { data: of({ dso: { payload: mockCollection } }) } },
60+
{ provide: NotificationsService, useValue: notificationService },
61+
{ provide: ScriptDataService, useValue: scriptService },
62+
{ provide: Router, useValue: router },
3363
],
3464
schemas: [NO_ERRORS_SCHEMA],
3565
}).compileComponents();
@@ -41,9 +71,38 @@ describe('DeleteCollectionPageComponent', () => {
4171
fixture.detectChanges();
4272
});
4373

44-
describe('frontendURL', () => {
45-
it('should have the right frontendURL set', () => {
46-
expect((comp as any).frontendURL).toEqual('/collections/');
74+
it('should create', () => {
75+
expect(comp).toBeTruthy();
76+
});
77+
78+
it('should have the right frontendURL set', () => {
79+
expect((comp as any).frontendURL).toEqual('/collections/');
80+
});
81+
82+
describe('onConfirm', () => {
83+
it('should invoke the deletion script with correct params, show success notification and redirect on success', (done) => {
84+
const parameterValues: ProcessParameter[] = [
85+
Object.assign(new ProcessParameter(), { name: '-i', value: mockCollection.uuid }),
86+
];
87+
(scriptService.invoke as jasmine.Spy).and.returnValue(createSuccessfulRemoteDataObject$({ processId: '123' }));
88+
comp.onConfirm(mockCollection);
89+
setTimeout(() => {
90+
expect(scriptService.invoke).toHaveBeenCalledWith(DSPACE_OBJECT_DELETION_SCRIPT_NAME, parameterValues, []);
91+
expect(notificationService.success).toHaveBeenCalledWith('collection.delete.notification.success');
92+
expect(router.navigateByUrl).toHaveBeenCalledWith(getProcessDetailRoute('123'));
93+
done();
94+
}, 0);
95+
});
96+
97+
it('error notification is shown', (done) => {
98+
(scriptService.invoke as jasmine.Spy).and.returnValue(createFailedRemoteDataObject$('Error', 500));
99+
comp.onConfirm(mockCollection);
100+
setTimeout(() => {
101+
expect(notificationService.error).toHaveBeenCalledWith('collection.delete.notification.fail');
102+
expect(router.navigate).toHaveBeenCalledWith(['/']);
103+
done();
104+
}, 0);
47105
});
48106
});
107+
49108
});

src/app/collection-page/delete-collection-page/delete-collection-page.component.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
TranslateService,
1414
} from '@ngx-translate/core';
1515

16+
import { ScriptDataService } from '../../core/data/processes/script-data.service';
1617
import { BtnDisabledDirective } from '../../shared/btn-disabled.directive';
1718
import { DeleteComColPageComponent } from '../../shared/comcol/comcol-forms/delete-comcol-page/delete-comcol-page.component';
1819
import { VarDirective } from '../../shared/utils/var.directive';
@@ -41,7 +42,8 @@ export class DeleteCollectionPageComponent extends DeleteComColPageComponent<Col
4142
protected route: ActivatedRoute,
4243
protected notifications: NotificationsService,
4344
protected translate: TranslateService,
45+
protected scriptDataService: ScriptDataService,
4446
) {
45-
super(dsoDataService, dsoNameService, router, route, notifications, translate);
47+
super(dsoDataService, dsoNameService, router, route, notifications, translate, scriptDataService);
4648
}
4749
}

src/app/community-page/delete-community-page/delete-community-page.component.spec.ts

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,61 @@ import {
55
TestBed,
66
waitForAsync,
77
} from '@angular/core/testing';
8-
import { ActivatedRoute } from '@angular/router';
8+
import {
9+
ActivatedRoute,
10+
Router,
11+
} from '@angular/router';
912
import { RouterTestingModule } from '@angular/router/testing';
1013
import { DSONameService } from '@dspace/core/breadcrumbs/dso-name.service';
1114
import { CommunityDataService } from '@dspace/core/data/community-data.service';
12-
import { RequestService } from '@dspace/core/data/request.service';
1315
import { NotificationsService } from '@dspace/core/notification-system/notifications.service';
16+
import { ProcessParameter } from '@dspace/core/processes/process-parameter.model';
1417
import { DSONameServiceMock } from '@dspace/core/testing/dso-name.service.mock';
18+
import { NotificationsServiceStub } from '@dspace/core/testing/notifications-service.stub';
19+
import {
20+
createFailedRemoteDataObject$,
21+
createSuccessfulRemoteDataObject$,
22+
} from '@dspace/core/utilities/remote-data.utils';
1523
import { TranslateModule } from '@ngx-translate/core';
1624
import { of } from 'rxjs';
1725

26+
import {
27+
DSPACE_OBJECT_DELETION_SCRIPT_NAME,
28+
ScriptDataService,
29+
} from '../../core/data/processes/script-data.service';
30+
import { Community } from '../../core/shared/community.model';
31+
import { getProcessDetailRoute } from '../../process-page/process-page-routing.paths';
1832
import { DeleteCommunityPageComponent } from './delete-community-page.component';
1933

2034
describe('DeleteCommunityPageComponent', () => {
2135
let comp: DeleteCommunityPageComponent;
2236
let fixture: ComponentFixture<DeleteCommunityPageComponent>;
37+
let scriptService;
38+
let notificationService: NotificationsServiceStub;
39+
let router;
40+
41+
const mockCommunity: Community = Object.assign(new Community(), {
42+
uuid: 'test-uuid',
43+
id: 'test-uuid',
44+
name: 'Test Community',
45+
type: 'community',
46+
});
2347

2448
beforeEach(waitForAsync(() => {
49+
notificationService = new NotificationsServiceStub();
50+
scriptService = jasmine.createSpyObj('scriptService', {
51+
invoke: createSuccessfulRemoteDataObject$({ processId: '123' }),
52+
});
53+
router = jasmine.createSpyObj('router', ['navigateByUrl', 'navigate']);
2554
TestBed.configureTestingModule({
2655
imports: [TranslateModule.forRoot(), CommonModule, RouterTestingModule, DeleteCommunityPageComponent],
2756
providers: [
2857
{ provide: DSONameService, useValue: new DSONameServiceMock() },
2958
{ provide: CommunityDataService, useValue: {} },
30-
{ provide: ActivatedRoute, useValue: { data: of({ dso: { payload: {} } }) } },
31-
{ provide: NotificationsService, useValue: {} },
32-
{ provide: RequestService, useValue: {} },
59+
{ provide: ActivatedRoute, useValue: { data: of({ dso: { payload: mockCommunity } }) } },
60+
{ provide: NotificationsService, useValue: notificationService },
61+
{ provide: ScriptDataService, useValue: scriptService },
62+
{ provide: Router, useValue: router },
3363
],
3464
schemas: [NO_ERRORS_SCHEMA],
3565
}).compileComponents();
@@ -41,9 +71,38 @@ describe('DeleteCommunityPageComponent', () => {
4171
fixture.detectChanges();
4272
});
4373

44-
describe('frontendURL', () => {
45-
it('should have the right frontendURL set', () => {
46-
expect((comp as any).frontendURL).toEqual('/communities/');
74+
it('should create', () => {
75+
expect(comp).toBeTruthy();
76+
});
77+
78+
it('should have the right frontendURL set', () => {
79+
expect((comp as any).frontendURL).toEqual('/communities/');
80+
});
81+
82+
describe('onConfirm', () => {
83+
it('should invoke the deletion script with correct params, show success notification and redirect on success', (done) => {
84+
const parameterValues: ProcessParameter[] = [
85+
Object.assign(new ProcessParameter(), { name: '-i', value: mockCommunity.uuid }),
86+
];
87+
(scriptService.invoke as jasmine.Spy).and.returnValue(createSuccessfulRemoteDataObject$({ processId: '123' }));
88+
comp.onConfirm(mockCommunity);
89+
setTimeout(() => {
90+
expect(scriptService.invoke).toHaveBeenCalledWith(DSPACE_OBJECT_DELETION_SCRIPT_NAME, parameterValues, []);
91+
expect(notificationService.success).toHaveBeenCalledWith('community.delete.notification.success');
92+
expect(router.navigateByUrl).toHaveBeenCalledWith(getProcessDetailRoute('123'));
93+
done();
94+
}, 0);
95+
});
96+
97+
it('error notification is shown', (done) => {
98+
(scriptService.invoke as jasmine.Spy).and.returnValue(createFailedRemoteDataObject$('Error', 500));
99+
comp.onConfirm(mockCommunity);
100+
setTimeout(() => {
101+
expect(notificationService.error).toHaveBeenCalledWith('community.delete.notification.fail');
102+
expect(router.navigate).toHaveBeenCalledWith(['/']);
103+
done();
104+
}, 0);
47105
});
48106
});
107+
49108
});

src/app/community-page/delete-community-page/delete-community-page.component.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
TranslateService,
1414
} from '@ngx-translate/core';
1515

16+
import { ScriptDataService } from '../../core/data/processes/script-data.service';
1617
import { BtnDisabledDirective } from '../../shared/btn-disabled.directive';
1718
import { DeleteComColPageComponent } from '../../shared/comcol/comcol-forms/delete-comcol-page/delete-comcol-page.component';
1819
import { VarDirective } from '../../shared/utils/var.directive';
@@ -41,8 +42,9 @@ export class DeleteCommunityPageComponent extends DeleteComColPageComponent<Comm
4142
protected route: ActivatedRoute,
4243
protected notifications: NotificationsService,
4344
protected translate: TranslateService,
45+
protected scriptDataService: ScriptDataService,
4446
) {
45-
super(dsoDataService, dsoNameService, router, route, notifications, translate);
47+
super(dsoDataService, dsoNameService, router, route, notifications, translate, scriptDataService);
4648
}
4749

4850
}

src/app/core/data/processes/script-data.service.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export const METADATA_IMPORT_SCRIPT_NAME = 'metadata-import';
3131
export const METADATA_EXPORT_SCRIPT_NAME = 'metadata-export';
3232
export const BATCH_IMPORT_SCRIPT_NAME = 'import';
3333
export const BATCH_EXPORT_SCRIPT_NAME = 'export';
34+
export const DSPACE_OBJECT_DELETION_SCRIPT_NAME = 'object-deletion';
3435

3536
@Injectable({ providedIn: 'root' })
3637
export class ScriptDataService extends IdentifiableDataService<Script> implements FindAllData<Script> {

src/app/item-page/edit-item-page/item-delete/item-delete.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ <h5 (click)="setSelected(typeDto.relationshipType, !selected)">
8686

8787
<div class="space-children-mr">
8888
<button [dsBtnDisabled]="isDeleting$ | async" (click)="performAction()"
89-
class="btn btn-outline-secondary perform-action">{{confirmMessage | translate}}
89+
class="btn btn-danger perform-action">{{confirmMessage | translate}}
9090
</button>
9191
<button [dsBtnDisabled]="isDeleting$ | async" [routerLink]="[itemPageRoute, 'edit']"
9292
class="btn btn-outline-secondary cancel">

0 commit comments

Comments
 (0)