Skip to content

Commit c98ffd2

Browse files
authored
Merge pull request #1503 from atmire/fix-delete-item-with-empty-entity-type
Fix deleting an item with an empty or invalid entity type
2 parents 5f90b29 + 1688468 commit c98ffd2

File tree

2 files changed

+93
-40
lines changed

2 files changed

+93
-40
lines changed

src/app/item-page/edit-item-page/item-delete/item-delete.component.spec.ts

Lines changed: 55 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { ItemType } from '../../../core/shared/item-relationships/item-type.mode
33
import { Relationship } from '../../../core/shared/item-relationships/relationship.model';
44
import { Item } from '../../../core/shared/item.model';
55
import { RouterStub } from '../../../shared/testing/router.stub';
6-
import { of as observableOf } from 'rxjs';
6+
import { of as observableOf, EMPTY } from 'rxjs';
77
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
88
import { CommonModule } from '@angular/common';
99
import { FormsModule } from '@angular/forms';
@@ -24,6 +24,8 @@ import { RelationshipType } from '../../../core/shared/item-relationships/relati
2424
import { EntityTypeService } from '../../../core/data/entity-type.service';
2525
import { getItemEditRoute } from '../../item-page-routing-paths';
2626
import { createPaginatedList } from '../../../shared/testing/utils.test';
27+
import { RelationshipTypeService } from '../../../core/data/relationship-type.service';
28+
import { LinkService } from '../../../core/cache/builders/link.service';
2729

2830
let comp: ItemDeleteComponent;
2931
let fixture: ComponentFixture<ItemDeleteComponent>;
@@ -40,6 +42,7 @@ let mockItemDataService: ItemDataService;
4042
let routeStub;
4143
let objectUpdatesServiceStub;
4244
let relationshipService;
45+
let linkService;
4346
let entityTypeService;
4447
let notificationsServiceStub;
4548
let typesSelection;
@@ -52,7 +55,12 @@ describe('ItemDeleteComponent', () => {
5255
uuid: 'fake-uuid',
5356
handle: 'fake/handle',
5457
lastModified: '2018',
55-
isWithdrawn: true
58+
isWithdrawn: true,
59+
metadata: {
60+
'dspace.entity.type': [
61+
{ value: 'Person' }
62+
]
63+
}
5664
});
5765

5866
itemType = Object.assign(new ItemType(), {
@@ -129,6 +137,12 @@ describe('ItemDeleteComponent', () => {
129137
}
130138
);
131139

140+
linkService = jasmine.createSpyObj('linkService',
141+
{
142+
resolveLinks: relationships[0],
143+
}
144+
);
145+
132146
notificationsServiceStub = new NotificationsServiceStub();
133147

134148
TestBed.configureTestingModule({
@@ -142,6 +156,8 @@ describe('ItemDeleteComponent', () => {
142156
{ provide: ObjectUpdatesService, useValue: objectUpdatesServiceStub },
143157
{ provide: RelationshipService, useValue: relationshipService },
144158
{ provide: EntityTypeService, useValue: entityTypeService },
159+
{ provide: RelationshipTypeService, useValue: {} },
160+
{ provide: LinkService, useValue: linkService },
145161
], schemas: [
146162
CUSTOM_ELEMENTS_SCHEMA
147163
]
@@ -166,25 +182,45 @@ describe('ItemDeleteComponent', () => {
166182
});
167183

168184
describe('performAction', () => {
169-
it('should call delete function from the ItemDataService', () => {
170-
spyOn(comp, 'notify');
171-
comp.performAction();
172-
expect(mockItemDataService.delete)
173-
.toHaveBeenCalledWith(mockItem.id, types.filter((type) => typesSelection[type]).map((type) => type.id));
174-
expect(comp.notify).toHaveBeenCalled();
185+
describe(`when there are entitytypes`, () => {
186+
it('should call delete function from the ItemDataService', () => {
187+
spyOn(comp, 'notify');
188+
comp.performAction();
189+
expect(mockItemDataService.delete)
190+
.toHaveBeenCalledWith(mockItem.id, types.filter((type) => typesSelection[type]).map((type) => type.id));
191+
expect(comp.notify).toHaveBeenCalled();
192+
});
193+
194+
it('should call delete function from the ItemDataService with empty types', () => {
195+
196+
spyOn(comp, 'notify');
197+
jasmine.getEnv().allowRespy(true);
198+
spyOn(entityTypeService, 'getEntityTypeRelationships').and.returnValue([]);
199+
comp.ngOnInit();
200+
201+
comp.performAction();
202+
203+
expect(mockItemDataService.delete).toHaveBeenCalledWith(mockItem.id, []);
204+
expect(comp.notify).toHaveBeenCalled();
205+
});
175206
});
176207

177-
it('should call delete function from the ItemDataService with empty types', () => {
178-
179-
spyOn(comp, 'notify');
180-
jasmine.getEnv().allowRespy(true);
181-
spyOn(entityTypeService, 'getEntityTypeRelationships').and.returnValue([]);
182-
comp.ngOnInit();
183-
184-
comp.performAction();
185-
186-
expect(mockItemDataService.delete).toHaveBeenCalledWith(mockItem.id, []);
187-
expect(comp.notify).toHaveBeenCalled();
208+
describe(`when there are no entity types`, () => {
209+
beforeEach(() => {
210+
(comp as any).entityTypeService = jasmine.createSpyObj('entityTypeService',
211+
{
212+
getEntityTypeByLabel: EMPTY,
213+
}
214+
);
215+
});
216+
217+
it('should call delete function from the ItemDataService', () => {
218+
spyOn(comp, 'notify');
219+
comp.performAction();
220+
expect(mockItemDataService.delete)
221+
.toHaveBeenCalledWith(mockItem.id, types.filter((type) => typesSelection[type]).map((type) => type.id));
222+
expect(comp.notify).toHaveBeenCalled();
223+
});
188224
});
189225
});
190226
describe('notify', () => {

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

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
import { Component, Input, OnInit } from '@angular/core';
2-
import {defaultIfEmpty, filter, map, switchMap, take} from 'rxjs/operators';
3-
import { AbstractSimpleItemActionComponent } from '../simple-item-action/abstract-simple-item-action.component';
1+
import { Component, Input, OnInit, OnDestroy } from '@angular/core';
2+
import { defaultIfEmpty, filter, map, switchMap, take } from 'rxjs/operators';
3+
import {
4+
AbstractSimpleItemActionComponent
5+
} from '../simple-item-action/abstract-simple-item-action.component';
46
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
57
import {
68
combineLatest as observableCombineLatest,
79
combineLatest,
810
Observable,
9-
of as observableOf
11+
of as observableOf, Subscription
1012
} from 'rxjs';
1113
import { RelationshipType } from '../../../core/shared/item-relationships/relationship-type.model';
1214
import { VirtualMetadata } from '../virtual-metadata/virtual-metadata.component';
@@ -32,6 +34,7 @@ import { followLink } from '../../../shared/utils/follow-link-config.model';
3234
import { getItemEditRoute } from '../../item-page-routing-paths';
3335
import { RemoteData } from '../../../core/data/remote-data';
3436
import { NoContent } from '../../../core/shared/NoContent.model';
37+
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
3538

3639
@Component({
3740
selector: 'ds-item-delete',
@@ -42,7 +45,7 @@ import { NoContent } from '../../../core/shared/NoContent.model';
4245
*/
4346
export class ItemDeleteComponent
4447
extends AbstractSimpleItemActionComponent
45-
implements OnInit {
48+
implements OnInit, OnDestroy {
4649

4750
/**
4851
* The current url of this page
@@ -60,7 +63,7 @@ export class ItemDeleteComponent
6063
* A list of the relationship types for which this item has relations as an observable.
6164
* The list doesn't contain duplicates.
6265
*/
63-
types$: Observable<RelationshipType[]>;
66+
types$: BehaviorSubject<RelationshipType[]> = new BehaviorSubject([]);
6467

6568
/**
6669
* A map which stores the relationships of this item for each type as observable lists
@@ -84,6 +87,11 @@ export class ItemDeleteComponent
8487
*/
8588
public modalRef: NgbModalRef;
8689

90+
/**
91+
* Array to track all subscriptions and unsubscribe them onDestroy
92+
*/
93+
private subs: Subscription[] = [];
94+
8795
constructor(protected route: ActivatedRoute,
8896
protected router: Router,
8997
protected notificationsService: NotificationsService,
@@ -113,8 +121,8 @@ export class ItemDeleteComponent
113121
this.url = this.router.url;
114122

115123
const label = this.item.firstMetadataValue('dspace.entity.type');
116-
if (label !== undefined) {
117-
this.types$ = this.entityTypeService.getEntityTypeByLabel(label).pipe(
124+
if (isNotEmpty(label)) {
125+
this.subs.push(this.entityTypeService.getEntityTypeByLabel(label).pipe(
118126
getFirstSucceededRemoteData(),
119127
getRemoteDataPayload(),
120128
switchMap((entityType) => this.entityTypeService.getEntityTypeRelationships(entityType.id)),
@@ -138,16 +146,14 @@ export class ItemDeleteComponent
138146
),
139147
);
140148
})
141-
);
142-
} else {
143-
this.types$ = observableOf([]);
149+
).subscribe((types: RelationshipType[]) => this.types$.next(types)));
144150
}
145151

146-
this.types$.pipe(
152+
this.subs.push(this.types$.pipe(
147153
take(1),
148154
).subscribe((types) =>
149155
this.objectUpdatesService.initialize(this.url, types, this.item.lastModified)
150-
);
156+
));
151157
}
152158

153159
/**
@@ -327,7 +333,7 @@ export class ItemDeleteComponent
327333
*/
328334
performAction() {
329335

330-
this.types$.pipe(
336+
this.subs.push(this.types$.pipe(
331337
switchMap((types) =>
332338
combineLatest(
333339
types.map((type) => this.isSelected(type))
@@ -339,13 +345,14 @@ export class ItemDeleteComponent
339345
map((selectedTypes) => selectedTypes.map((type) => type.id)),
340346
)
341347
),
342-
).subscribe((types) => {
343-
this.itemDataService.delete(this.item.id, types).pipe(getFirstCompletedRemoteData()).subscribe(
344-
(rd: RemoteData<NoContent>) => {
345-
this.notify(rd.hasSucceeded);
346-
}
347-
);
348-
});
348+
switchMap((types) =>
349+
this.itemDataService.delete(this.item.id, types).pipe(getFirstCompletedRemoteData())
350+
)
351+
).subscribe(
352+
(rd: RemoteData<NoContent>) => {
353+
this.notify(rd.hasSucceeded);
354+
}
355+
));
349356
}
350357

351358
/**
@@ -361,4 +368,14 @@ export class ItemDeleteComponent
361368
this.router.navigate([getItemEditRoute(this.item)]);
362369
}
363370
}
371+
372+
/**
373+
* Unsubscribe from all subscriptions
374+
*/
375+
ngOnDestroy(): void {
376+
this.subs
377+
.filter((sub) => hasValue(sub))
378+
.forEach((sub) => sub.unsubscribe());
379+
}
380+
364381
}

0 commit comments

Comments
 (0)