forked from hazza/dspace-angular
Merge pull request #1503 from atmire/fix-delete-item-with-empty-entity-type
Fix deleting an item with an empty or invalid entity type
This commit is contained in:
@@ -3,7 +3,7 @@ import { ItemType } from '../../../core/shared/item-relationships/item-type.mode
|
|||||||
import { Relationship } from '../../../core/shared/item-relationships/relationship.model';
|
import { Relationship } from '../../../core/shared/item-relationships/relationship.model';
|
||||||
import { Item } from '../../../core/shared/item.model';
|
import { Item } from '../../../core/shared/item.model';
|
||||||
import { RouterStub } from '../../../shared/testing/router.stub';
|
import { RouterStub } from '../../../shared/testing/router.stub';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf, EMPTY } from 'rxjs';
|
||||||
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
|
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
@@ -24,6 +24,8 @@ import { RelationshipType } from '../../../core/shared/item-relationships/relati
|
|||||||
import { EntityTypeService } from '../../../core/data/entity-type.service';
|
import { EntityTypeService } from '../../../core/data/entity-type.service';
|
||||||
import { getItemEditRoute } from '../../item-page-routing-paths';
|
import { getItemEditRoute } from '../../item-page-routing-paths';
|
||||||
import { createPaginatedList } from '../../../shared/testing/utils.test';
|
import { createPaginatedList } from '../../../shared/testing/utils.test';
|
||||||
|
import { RelationshipTypeService } from '../../../core/data/relationship-type.service';
|
||||||
|
import { LinkService } from '../../../core/cache/builders/link.service';
|
||||||
|
|
||||||
let comp: ItemDeleteComponent;
|
let comp: ItemDeleteComponent;
|
||||||
let fixture: ComponentFixture<ItemDeleteComponent>;
|
let fixture: ComponentFixture<ItemDeleteComponent>;
|
||||||
@@ -40,6 +42,7 @@ let mockItemDataService: ItemDataService;
|
|||||||
let routeStub;
|
let routeStub;
|
||||||
let objectUpdatesServiceStub;
|
let objectUpdatesServiceStub;
|
||||||
let relationshipService;
|
let relationshipService;
|
||||||
|
let linkService;
|
||||||
let entityTypeService;
|
let entityTypeService;
|
||||||
let notificationsServiceStub;
|
let notificationsServiceStub;
|
||||||
let typesSelection;
|
let typesSelection;
|
||||||
@@ -52,7 +55,12 @@ describe('ItemDeleteComponent', () => {
|
|||||||
uuid: 'fake-uuid',
|
uuid: 'fake-uuid',
|
||||||
handle: 'fake/handle',
|
handle: 'fake/handle',
|
||||||
lastModified: '2018',
|
lastModified: '2018',
|
||||||
isWithdrawn: true
|
isWithdrawn: true,
|
||||||
|
metadata: {
|
||||||
|
'dspace.entity.type': [
|
||||||
|
{ value: 'Person' }
|
||||||
|
]
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
itemType = Object.assign(new ItemType(), {
|
itemType = Object.assign(new ItemType(), {
|
||||||
@@ -129,6 +137,12 @@ describe('ItemDeleteComponent', () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
linkService = jasmine.createSpyObj('linkService',
|
||||||
|
{
|
||||||
|
resolveLinks: relationships[0],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
notificationsServiceStub = new NotificationsServiceStub();
|
notificationsServiceStub = new NotificationsServiceStub();
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
@@ -142,6 +156,8 @@ describe('ItemDeleteComponent', () => {
|
|||||||
{ provide: ObjectUpdatesService, useValue: objectUpdatesServiceStub },
|
{ provide: ObjectUpdatesService, useValue: objectUpdatesServiceStub },
|
||||||
{ provide: RelationshipService, useValue: relationshipService },
|
{ provide: RelationshipService, useValue: relationshipService },
|
||||||
{ provide: EntityTypeService, useValue: entityTypeService },
|
{ provide: EntityTypeService, useValue: entityTypeService },
|
||||||
|
{ provide: RelationshipTypeService, useValue: {} },
|
||||||
|
{ provide: LinkService, useValue: linkService },
|
||||||
], schemas: [
|
], schemas: [
|
||||||
CUSTOM_ELEMENTS_SCHEMA
|
CUSTOM_ELEMENTS_SCHEMA
|
||||||
]
|
]
|
||||||
@@ -166,25 +182,45 @@ describe('ItemDeleteComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('performAction', () => {
|
describe('performAction', () => {
|
||||||
it('should call delete function from the ItemDataService', () => {
|
describe(`when there are entitytypes`, () => {
|
||||||
spyOn(comp, 'notify');
|
it('should call delete function from the ItemDataService', () => {
|
||||||
comp.performAction();
|
spyOn(comp, 'notify');
|
||||||
expect(mockItemDataService.delete)
|
comp.performAction();
|
||||||
.toHaveBeenCalledWith(mockItem.id, types.filter((type) => typesSelection[type]).map((type) => type.id));
|
expect(mockItemDataService.delete)
|
||||||
expect(comp.notify).toHaveBeenCalled();
|
.toHaveBeenCalledWith(mockItem.id, types.filter((type) => typesSelection[type]).map((type) => type.id));
|
||||||
|
expect(comp.notify).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call delete function from the ItemDataService with empty types', () => {
|
||||||
|
|
||||||
|
spyOn(comp, 'notify');
|
||||||
|
jasmine.getEnv().allowRespy(true);
|
||||||
|
spyOn(entityTypeService, 'getEntityTypeRelationships').and.returnValue([]);
|
||||||
|
comp.ngOnInit();
|
||||||
|
|
||||||
|
comp.performAction();
|
||||||
|
|
||||||
|
expect(mockItemDataService.delete).toHaveBeenCalledWith(mockItem.id, []);
|
||||||
|
expect(comp.notify).toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call delete function from the ItemDataService with empty types', () => {
|
describe(`when there are no entity types`, () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
(comp as any).entityTypeService = jasmine.createSpyObj('entityTypeService',
|
||||||
|
{
|
||||||
|
getEntityTypeByLabel: EMPTY,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
spyOn(comp, 'notify');
|
it('should call delete function from the ItemDataService', () => {
|
||||||
jasmine.getEnv().allowRespy(true);
|
spyOn(comp, 'notify');
|
||||||
spyOn(entityTypeService, 'getEntityTypeRelationships').and.returnValue([]);
|
comp.performAction();
|
||||||
comp.ngOnInit();
|
expect(mockItemDataService.delete)
|
||||||
|
.toHaveBeenCalledWith(mockItem.id, types.filter((type) => typesSelection[type]).map((type) => type.id));
|
||||||
comp.performAction();
|
expect(comp.notify).toHaveBeenCalled();
|
||||||
|
});
|
||||||
expect(mockItemDataService.delete).toHaveBeenCalledWith(mockItem.id, []);
|
|
||||||
expect(comp.notify).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('notify', () => {
|
describe('notify', () => {
|
||||||
|
@@ -1,12 +1,14 @@
|
|||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { Component, Input, OnInit, OnDestroy } from '@angular/core';
|
||||||
import {defaultIfEmpty, filter, map, switchMap, take} from 'rxjs/operators';
|
import { defaultIfEmpty, filter, map, switchMap, take } from 'rxjs/operators';
|
||||||
import { AbstractSimpleItemActionComponent } from '../simple-item-action/abstract-simple-item-action.component';
|
import {
|
||||||
|
AbstractSimpleItemActionComponent
|
||||||
|
} from '../simple-item-action/abstract-simple-item-action.component';
|
||||||
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import {
|
import {
|
||||||
combineLatest as observableCombineLatest,
|
combineLatest as observableCombineLatest,
|
||||||
combineLatest,
|
combineLatest,
|
||||||
Observable,
|
Observable,
|
||||||
of as observableOf
|
of as observableOf, Subscription
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import { RelationshipType } from '../../../core/shared/item-relationships/relationship-type.model';
|
import { RelationshipType } from '../../../core/shared/item-relationships/relationship-type.model';
|
||||||
import { VirtualMetadata } from '../virtual-metadata/virtual-metadata.component';
|
import { VirtualMetadata } from '../virtual-metadata/virtual-metadata.component';
|
||||||
@@ -32,6 +34,7 @@ import { followLink } from '../../../shared/utils/follow-link-config.model';
|
|||||||
import { getItemEditRoute } from '../../item-page-routing-paths';
|
import { getItemEditRoute } from '../../item-page-routing-paths';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
import { NoContent } from '../../../core/shared/NoContent.model';
|
import { NoContent } from '../../../core/shared/NoContent.model';
|
||||||
|
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-item-delete',
|
selector: 'ds-item-delete',
|
||||||
@@ -42,7 +45,7 @@ import { NoContent } from '../../../core/shared/NoContent.model';
|
|||||||
*/
|
*/
|
||||||
export class ItemDeleteComponent
|
export class ItemDeleteComponent
|
||||||
extends AbstractSimpleItemActionComponent
|
extends AbstractSimpleItemActionComponent
|
||||||
implements OnInit {
|
implements OnInit, OnDestroy {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current url of this page
|
* The current url of this page
|
||||||
@@ -60,7 +63,7 @@ export class ItemDeleteComponent
|
|||||||
* A list of the relationship types for which this item has relations as an observable.
|
* A list of the relationship types for which this item has relations as an observable.
|
||||||
* The list doesn't contain duplicates.
|
* The list doesn't contain duplicates.
|
||||||
*/
|
*/
|
||||||
types$: Observable<RelationshipType[]>;
|
types$: BehaviorSubject<RelationshipType[]> = new BehaviorSubject([]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A map which stores the relationships of this item for each type as observable lists
|
* A map which stores the relationships of this item for each type as observable lists
|
||||||
@@ -84,6 +87,11 @@ export class ItemDeleteComponent
|
|||||||
*/
|
*/
|
||||||
public modalRef: NgbModalRef;
|
public modalRef: NgbModalRef;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array to track all subscriptions and unsubscribe them onDestroy
|
||||||
|
*/
|
||||||
|
private subs: Subscription[] = [];
|
||||||
|
|
||||||
constructor(protected route: ActivatedRoute,
|
constructor(protected route: ActivatedRoute,
|
||||||
protected router: Router,
|
protected router: Router,
|
||||||
protected notificationsService: NotificationsService,
|
protected notificationsService: NotificationsService,
|
||||||
@@ -113,8 +121,8 @@ export class ItemDeleteComponent
|
|||||||
this.url = this.router.url;
|
this.url = this.router.url;
|
||||||
|
|
||||||
const label = this.item.firstMetadataValue('dspace.entity.type');
|
const label = this.item.firstMetadataValue('dspace.entity.type');
|
||||||
if (label !== undefined) {
|
if (isNotEmpty(label)) {
|
||||||
this.types$ = this.entityTypeService.getEntityTypeByLabel(label).pipe(
|
this.subs.push(this.entityTypeService.getEntityTypeByLabel(label).pipe(
|
||||||
getFirstSucceededRemoteData(),
|
getFirstSucceededRemoteData(),
|
||||||
getRemoteDataPayload(),
|
getRemoteDataPayload(),
|
||||||
switchMap((entityType) => this.entityTypeService.getEntityTypeRelationships(entityType.id)),
|
switchMap((entityType) => this.entityTypeService.getEntityTypeRelationships(entityType.id)),
|
||||||
@@ -138,16 +146,14 @@ export class ItemDeleteComponent
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
);
|
).subscribe((types: RelationshipType[]) => this.types$.next(types)));
|
||||||
} else {
|
|
||||||
this.types$ = observableOf([]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.types$.pipe(
|
this.subs.push(this.types$.pipe(
|
||||||
take(1),
|
take(1),
|
||||||
).subscribe((types) =>
|
).subscribe((types) =>
|
||||||
this.objectUpdatesService.initialize(this.url, types, this.item.lastModified)
|
this.objectUpdatesService.initialize(this.url, types, this.item.lastModified)
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -327,7 +333,7 @@ export class ItemDeleteComponent
|
|||||||
*/
|
*/
|
||||||
performAction() {
|
performAction() {
|
||||||
|
|
||||||
this.types$.pipe(
|
this.subs.push(this.types$.pipe(
|
||||||
switchMap((types) =>
|
switchMap((types) =>
|
||||||
combineLatest(
|
combineLatest(
|
||||||
types.map((type) => this.isSelected(type))
|
types.map((type) => this.isSelected(type))
|
||||||
@@ -339,13 +345,14 @@ export class ItemDeleteComponent
|
|||||||
map((selectedTypes) => selectedTypes.map((type) => type.id)),
|
map((selectedTypes) => selectedTypes.map((type) => type.id)),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
).subscribe((types) => {
|
switchMap((types) =>
|
||||||
this.itemDataService.delete(this.item.id, types).pipe(getFirstCompletedRemoteData()).subscribe(
|
this.itemDataService.delete(this.item.id, types).pipe(getFirstCompletedRemoteData())
|
||||||
(rd: RemoteData<NoContent>) => {
|
)
|
||||||
this.notify(rd.hasSucceeded);
|
).subscribe(
|
||||||
}
|
(rd: RemoteData<NoContent>) => {
|
||||||
);
|
this.notify(rd.hasSucceeded);
|
||||||
});
|
}
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -361,4 +368,14 @@ export class ItemDeleteComponent
|
|||||||
this.router.navigate([getItemEditRoute(this.item)]);
|
this.router.navigate([getItemEditRoute(this.item)]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribe from all subscriptions
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.subs
|
||||||
|
.filter((sub) => hasValue(sub))
|
||||||
|
.forEach((sub) => sub.unsubscribe());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user