Skip to content

Commit a5b9477

Browse files
authored
Merge pull request DSpace#3079 from alexandrevryghem/w2p-115427_fixed-delete-item-page-freezing_contribute-main
Fixed delete item page freezing when having relationships
2 parents 3814734 + 672219b commit a5b9477

10 files changed

Lines changed: 107 additions & 75 deletions

src/app/core/data/base/delete-data.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,11 @@ describe('DeleteDataImpl', () => {
209209
method: RestRequestMethod.DELETE,
210210
href: 'some-href?copyVirtualMetadata=a&copyVirtualMetadata=b&copyVirtualMetadata=c',
211211
}));
212+
213+
const callback = (rdbService.buildFromRequestUUIDAndAwait as jasmine.Spy).calls.argsFor(0)[1];
214+
callback();
215+
expect(service.invalidateByHref).toHaveBeenCalledWith('some-href');
216+
212217
done();
213218
});
214219
});

src/app/core/data/base/delete-data.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,15 +75,16 @@ export class DeleteDataImpl<T extends CacheableObject> extends IdentifiableDataS
7575
deleteByHref(href: string, copyVirtualMetadata?: string[]): Observable<RemoteData<NoContent>> {
7676
const requestId = this.requestService.generateRequestId();
7777

78+
let deleteHref: string = href;
7879
if (copyVirtualMetadata) {
7980
copyVirtualMetadata.forEach((id) =>
80-
href += (href.includes('?') ? '&' : '?')
81+
deleteHref += (deleteHref.includes('?') ? '&' : '?')
8182
+ 'copyVirtualMetadata='
8283
+ id,
8384
);
8485
}
8586

86-
const request = new DeleteRequest(requestId, href);
87+
const request = new DeleteRequest(requestId, deleteHref);
8788
if (hasValue(this.responseMsToLive)) {
8889
request.responseMsToLive = this.responseMsToLive;
8990
}

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

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,29 @@ <h1>{{headerMessage | translate: {id: item.handle} }}</h1>
66
<p>{{descriptionMessage | translate}}</p>
77
<ds-modify-item-overview [item]="item"></ds-modify-item-overview>
88

9-
<ng-container *ngVar="(types$ | async) as types">
9+
<ng-container *ngVar="(typeDTOs$ | async) as types">
1010

1111
<div *ngIf="types && types.length > 0" class="mb-4">
1212

1313
{{'virtual-metadata.delete-item.info' | translate}}
1414

15-
<div *ngFor="let type of types" class="mb-4">
16-
17-
<div *ngVar="(isSelected(type) | async) as selected"
15+
<div *ngFor="let typeDto of types" class="mb-4">
16+
<div *ngVar="(typeDto.isSelected$ | async) as selected"
1817
class="d-flex flex-row">
1918

20-
<div class="m-2" (click)="setSelected(type, !selected)">
19+
<div class="m-2" (click)="setSelected(typeDto.relationshipType, !selected)">
2120
<label>
22-
<input type="checkbox" [checked]="selected">
21+
<input type="checkbox" [checked]="selected" [disabled]="isDeleting$ | async">
2322
</label>
2423
</div>
2524

2625
<div class="flex-column flex-grow-1">
27-
<h5 (click)="setSelected(type, !selected)">
28-
{{getRelationshipMessageKey(getLabel(type) | async) | translate}}
26+
<h5 (click)="setSelected(typeDto.relationshipType, !selected)">
27+
{{getRelationshipMessageKey(typeDto.label$ | async) | translate}}
2928
</h5>
30-
<div *ngFor="let relationship of (getRelationships(type) | async)"
29+
<div *ngFor="let relationshipDto of (typeDto.relationshipDTOs$ | async)"
3130
class="d-flex flex-row">
32-
<ng-container *ngVar="(getRelatedItem(relationship) | async) as relatedItem">
31+
<ng-container *ngVar="(relationshipDto.relatedItem$ | async) as relatedItem">
3332

3433
<ds-listable-object-component-loader
3534
*ngIf="relatedItem"
@@ -46,7 +45,7 @@ <h5 (click)="setSelected(type, !selected)">
4645
</div>
4746

4847
<ng-template #virtualMetadataModal>
49-
<div>
48+
<div class="thumb-font-1">
5049
<div class="modal-header">
5150
{{'virtual-metadata.delete-item.modal-head' | translate}}
5251
<button type="button" class="close"
@@ -60,7 +59,7 @@ <h5 (click)="setSelected(type, !selected)">
6059
[object]="relatedItem"
6160
[viewMode]="viewMode">
6261
</ds-listable-object-component-loader>
63-
<div *ngFor="let metadata of (getVirtualMetadata(relationship) | async)">
62+
<div *ngFor="let metadata of (relationshipDto.virtualMetadata$ | async)">
6463
<div>
6564
<div class="font-weight-bold">
6665
{{metadata.metadataField}}
@@ -87,10 +86,11 @@ <h5 (click)="setSelected(type, !selected)">
8786
</ng-container>
8887

8988
<div class="space-children-mr">
90-
<button (click)="performAction()"
89+
<button [disabled]="isDeleting$ | async" (click)="performAction()"
9190
class="btn btn-outline-secondary perform-action">{{confirmMessage | translate}}
9291
</button>
93-
<button [routerLink]="[itemPageRoute, 'edit']" class="btn btn-outline-secondary cancel">
92+
<button [disabled]="isDeleting$ | async" [routerLink]="[itemPageRoute, 'edit']"
93+
class="btn btn-outline-secondary cancel">
9494
{{cancelMessage| translate}}
9595
</button>
9696
</div>

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

Lines changed: 65 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// eslint-disable-next-line max-classes-per-file
12
import {
23
AsyncPipe,
34
NgForOf,
@@ -68,6 +69,34 @@ import { ModifyItemOverviewComponent } from '../modify-item-overview/modify-item
6869
import { AbstractSimpleItemActionComponent } from '../simple-item-action/abstract-simple-item-action.component';
6970
import { VirtualMetadata } from '../virtual-metadata/virtual-metadata.component';
7071

72+
/**
73+
* Data Transfer Object used to prevent the HTML template to call function returning Observables
74+
*/
75+
class RelationshipTypeDTO {
76+
77+
relationshipType: RelationshipType;
78+
79+
isSelected$: Observable<boolean>;
80+
81+
label$: Observable<string>;
82+
83+
relationshipDTOs$: Observable<RelationshipDTO[]>;
84+
85+
}
86+
87+
/**
88+
* Data Transfer Object used to prevent the HTML template to call function returning Observables
89+
*/
90+
class RelationshipDTO {
91+
92+
relationship: Relationship;
93+
94+
relatedItem$: Observable<Item>;
95+
96+
virtualMetadata$: Observable<VirtualMetadata[]>;
97+
98+
}
99+
71100
@Component({
72101
selector: 'ds-item-delete',
73102
templateUrl: '../item-delete/item-delete.component.html',
@@ -106,7 +135,7 @@ export class ItemDeleteComponent
106135
* A list of the relationship types for which this item has relations as an observable.
107136
* The list doesn't contain duplicates.
108137
*/
109-
types$: BehaviorSubject<RelationshipType[]> = new BehaviorSubject([]);
138+
typeDTOs$: BehaviorSubject<RelationshipTypeDTO[]> = new BehaviorSubject([]);
110139

111140
/**
112141
* A map which stores the relationships of this item for each type as observable lists
@@ -135,6 +164,8 @@ export class ItemDeleteComponent
135164
*/
136165
private subs: Subscription[] = [];
137166

167+
public isDeleting$: BehaviorSubject<boolean> = new BehaviorSubject(false);
168+
138169
constructor(protected route: ActivatedRoute,
139170
protected router: Router,
140171
protected notificationsService: NotificationsService,
@@ -189,13 +220,24 @@ export class ItemDeleteComponent
189220
),
190221
);
191222
}),
192-
).subscribe((types: RelationshipType[]) => this.types$.next(types)));
223+
).subscribe((types: RelationshipType[]) => this.typeDTOs$.next(types.map((relationshipType: RelationshipType) => Object.assign(new RelationshipTypeDTO(), {
224+
relationshipType: relationshipType,
225+
isSelected$: this.isSelected(relationshipType),
226+
label$: this.getLabel(relationshipType),
227+
relationshipDTOs$: this.getRelationships(relationshipType).pipe(
228+
map((relationships: Relationship[]) => relationships.map((relationship: Relationship) => Object.assign(new RelationshipDTO(), {
229+
relationship: relationship,
230+
relatedItem$: this.getRelatedItem(relationship),
231+
virtualMetadata$: this.getVirtualMetadata(relationship),
232+
} as RelationshipDTO))),
233+
),
234+
})))));
193235
}
194236

195-
this.subs.push(this.types$.pipe(
237+
this.subs.push(this.typeDTOs$.pipe(
196238
take(1),
197-
).subscribe((types) =>
198-
this.objectUpdatesService.initialize(this.url, types, this.item.lastModified),
239+
).subscribe((types: RelationshipTypeDTO[]) =>
240+
this.objectUpdatesService.initialize(this.url, types.map((relationshipTypeDto: RelationshipTypeDTO) => relationshipTypeDto.relationshipType), this.item.lastModified),
199241
));
200242
}
201243

@@ -368,34 +410,33 @@ export class ItemDeleteComponent
368410
* @param selected whether the type should be selected
369411
*/
370412
setSelected(type: RelationshipType, selected: boolean): void {
371-
this.objectUpdatesService.setSelectedVirtualMetadata(this.url, this.item.uuid, type.uuid, selected);
413+
if (this.isDeleting$.value === false) {
414+
this.objectUpdatesService.setSelectedVirtualMetadata(this.url, this.item.uuid, type.uuid, selected);
415+
}
372416
}
373417

374418
/**
375419
* Perform the delete operation
376420
*/
377-
performAction() {
378-
379-
this.subs.push(this.types$.pipe(
380-
switchMap((types) =>
421+
performAction(): void {
422+
this.isDeleting$.next(true);
423+
this.subs.push(this.typeDTOs$.pipe(
424+
switchMap((types: RelationshipTypeDTO[]) =>
381425
combineLatest(
382-
types.map((type) => this.isSelected(type)),
426+
types.map((type: RelationshipTypeDTO) => type.isSelected$),
383427
).pipe(
384428
defaultIfEmpty([]),
385-
map((selection) => types.filter(
386-
(type, index) => selection[index],
429+
map((selection: boolean[]) => types.filter(
430+
(type: RelationshipTypeDTO, index: number) => selection[index],
387431
)),
388-
map((selectedTypes) => selectedTypes.map((type) => type.id)),
432+
map((selectedDtoTypes: RelationshipTypeDTO[]) => selectedDtoTypes.map((typeDto: RelationshipTypeDTO) => typeDto.relationshipType.id)),
389433
),
390434
),
391-
switchMap((types) =>
392-
this.itemDataService.delete(this.item.id, types).pipe(getFirstCompletedRemoteData()),
393-
),
394-
).subscribe(
395-
(rd: RemoteData<NoContent>) => {
396-
this.notify(rd.hasSucceeded);
397-
},
398-
));
435+
switchMap((types: string[]) => this.itemDataService.delete(this.item.id, types)),
436+
getFirstCompletedRemoteData(),
437+
).subscribe((rd: RemoteData<NoContent>) => {
438+
this.notify(rd.hasSucceeded);
439+
}));
399440
}
400441

401442
/**
@@ -405,10 +446,10 @@ export class ItemDeleteComponent
405446
notify(succeeded: boolean) {
406447
if (succeeded) {
407448
this.notificationsService.success(this.translateService.get('item.edit.' + this.messageKey + '.success'));
408-
this.router.navigate(['']);
449+
void this.router.navigate(['']);
409450
} else {
410451
this.notificationsService.error(this.translateService.get('item.edit.' + this.messageKey + '.error'));
411-
this.router.navigate([getItemEditRoute(this.item)]);
452+
void this.router.navigate([getItemEditRoute(this.item)]);
412453
}
413454
}
414455

src/app/item-page/full/full-item-page.component.spec.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import {
2222
of as observableOf,
2323
} from 'rxjs';
2424

25-
import { AuthService } from '../../core/auth/auth.service';
2625
import { NotifyInfoService } from '../../core/coar-notify/notify-info/notify-info.service';
2726
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
2827
import { ItemDataService } from '../../core/data/item-data.service';
@@ -79,7 +78,6 @@ describe('FullItemPageComponent', () => {
7978
let comp: FullItemPageComponent;
8079
let fixture: ComponentFixture<FullItemPageComponent>;
8180

82-
let authService: AuthService;
8381
let routeStub: ActivatedRouteStub;
8482
let routeData;
8583
let authorizationDataService: AuthorizationDataService;
@@ -102,11 +100,6 @@ describe('FullItemPageComponent', () => {
102100
};
103101

104102
beforeEach(waitForAsync(() => {
105-
authService = jasmine.createSpyObj('authService', {
106-
isAuthenticated: observableOf(true),
107-
setRedirectUrl: {},
108-
});
109-
110103
routeData = {
111104
dso: createSuccessfulRemoteDataObject(mockItem),
112105
};
@@ -151,7 +144,6 @@ describe('FullItemPageComponent', () => {
151144
{ provide: ActivatedRoute, useValue: routeStub },
152145
{ provide: ItemDataService, useValue: {} },
153146
{ provide: HeadTagService, useValue: headTagService },
154-
{ provide: AuthService, useValue: authService },
155147
{ provide: AuthorizationDataService, useValue: authorizationDataService },
156148
{ provide: ServerResponseService, useValue: serverResponseService },
157149
{ provide: SignpostingDataService, useValue: signpostingDataService },

src/app/item-page/full/full-item-page.component.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import {
2929
map,
3030
} from 'rxjs/operators';
3131

32-
import { AuthService } from '../../core/auth/auth.service';
3332
import { NotifyInfoService } from '../../core/coar-notify/notify-info/notify-info.service';
3433
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
3534
import { ItemDataService } from '../../core/data/item-data.service';
@@ -103,7 +102,6 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit,
103102
protected route: ActivatedRoute,
104103
protected router: Router,
105104
protected items: ItemDataService,
106-
protected authService: AuthService,
107105
protected authorizationService: AuthorizationDataService,
108106
protected _location: Location,
109107
protected responseService: ServerResponseService,
@@ -112,7 +110,7 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit,
112110
protected notifyInfoService: NotifyInfoService,
113111
@Inject(PLATFORM_ID) protected platformId: string,
114112
) {
115-
super(route, router, items, authService, authorizationService, responseService, signpostingDataService, linkHeadService, notifyInfoService, platformId);
113+
super(route, router, items, authorizationService, responseService, signpostingDataService, linkHeadService, notifyInfoService, platformId);
116114
}
117115

118116
/*** AoT inheritance fix, will hopefully be resolved in the near future **/

src/app/item-page/item-page.resolver.spec.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
import { TestBed } from '@angular/core/testing';
2-
import { Router } from '@angular/router';
3-
import { RouterTestingModule } from '@angular/router/testing';
2+
import {
3+
Router,
4+
RouterModule,
5+
} from '@angular/router';
46
import { first } from 'rxjs/operators';
57

68
import { DSpaceObject } from '../core/shared/dspace-object.model';
79
import { MetadataValueFilter } from '../core/shared/metadata.models';
810
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
11+
import { AuthServiceStub } from '../shared/testing/auth-service.stub';
912
import { itemPageResolver } from './item-page.resolver';
1013

1114
describe('itemPageResolver', () => {
1215
beforeEach(() => {
1316
TestBed.configureTestingModule({
14-
imports: [RouterTestingModule.withRoutes([{
17+
imports: [RouterModule.forRoot([{
1518
path: 'entities/:entity-type/:id',
1619
component: {} as any,
1720
}])],
@@ -22,7 +25,8 @@ describe('itemPageResolver', () => {
2225
let resolver: any;
2326
let itemService: any;
2427
let store: any;
25-
let router: any;
28+
let router: Router;
29+
let authService: AuthServiceStub;
2630

2731
const uuid = '1234-65487-12354-1235';
2832
let item: DSpaceObject;
@@ -42,6 +46,7 @@ describe('itemPageResolver', () => {
4246
store = jasmine.createSpyObj('store', {
4347
dispatch: {},
4448
});
49+
authService = new AuthServiceStub();
4550
resolver = itemPageResolver;
4651
});
4752

@@ -54,6 +59,7 @@ describe('itemPageResolver', () => {
5459
router,
5560
itemService,
5661
store,
62+
authService,
5763
).pipe(first())
5864
.subscribe(
5965
() => {
@@ -73,6 +79,7 @@ describe('itemPageResolver', () => {
7379
router,
7480
itemService,
7581
store,
82+
authService,
7683
).pipe(first())
7784
.subscribe(
7885
() => {

0 commit comments

Comments
 (0)