Skip to content

Commit df80c33

Browse files
115046: Fixed multiple edit relationship bugs
- Fixed issue making it impossible to add new relationships until the page is refreshed after deleting an existing one (only when you refreshed the page after creating the initial relationship) - Fixed NPE in DsDynamicLookupRelationModalComponent - Grouped buttons on relationship page in order to assure that they always have the same behaviour
1 parent 1338712 commit df80c33

12 files changed

Lines changed: 86 additions & 76 deletions

src/app/item-page/edit-item-page/abstract-item-update/abstract-item-update.component.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ export class AbstractItemUpdateComponent extends AbstractTrackableComponent impl
5555
*/
5656
updates$: Observable<FieldUpdates>;
5757

58+
hasChanges$: Observable<boolean>;
59+
60+
isReinstatable$: Observable<boolean>;
61+
5862
/**
5963
* Route to the item's page
6064
*/
@@ -101,10 +105,9 @@ export class AbstractItemUpdateComponent extends AbstractTrackableComponent impl
101105
}
102106

103107
this.discardTimeOut = environment.item.edit.undoTimeout;
104-
this.url = this.router.url;
105-
if (this.url.indexOf('?') > 0) {
106-
this.url = this.url.substr(0, this.url.indexOf('?'));
107-
}
108+
this.url = this.router.url.split('?')[0];
109+
this.hasChanges$ = this.hasChanges();
110+
this.isReinstatable$ = this.isReinstatable();
108111
this.hasChanges().pipe(first()).subscribe((hasChanges) => {
109112
if (!hasChanges) {
110113
this.initializeOriginalFields();

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,21 @@
66
class="fas fa-upload"></i>
77
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.bitstreams.upload-button" | translate}}</span>
88
</button>
9-
<button class="btn btn-warning" *ngIf="isReinstatable() | async"
9+
<button class="btn btn-warning" *ngIf="isReinstatable$ | async"
1010
[attr.aria-label]="'item.edit.bitstreams.reinstate-button' | translate"
1111
(click)="reinstate()"><i
1212
class="fas fa-undo-alt"></i>
1313
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.bitstreams.reinstate-button" | translate}}</span>
1414
</button>
15-
<button class="btn btn-primary" [disabled]="(hasChanges() | async) !== true || submitting"
15+
<button class="btn btn-primary" [disabled]="(hasChanges$ | async) !== true || submitting"
1616
[attr.aria-label]="'item.edit.bitstreams.save-button' | translate"
1717
(click)="submit()"><i
1818
class="fas fa-save"></i>
1919
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.bitstreams.save-button" | translate}}</span>
2020
</button>
21-
<button class="btn btn-danger" *ngIf="(isReinstatable() | async) !== true"
21+
<button class="btn btn-danger" *ngIf="(isReinstatable$ | async) !== true"
2222
[attr.aria-label]="'item.edit.bitstreams.discard-button' | translate"
23-
[disabled]="(hasChanges() | async) !== true || submitting"
23+
[disabled]="(hasChanges$ | async) !== true || submitting"
2424
(click)="discard()"><i
2525
class="fas fa-times"></i>
2626
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.bitstreams.discard-button" | translate}}</span>
@@ -52,21 +52,21 @@
5252

5353
<div class="button-row bottom">
5454
<div class="mt-4 float-right space-children-mr ml-gap">
55-
<button class="btn btn-warning" *ngIf="isReinstatable() | async"
55+
<button class="btn btn-warning" *ngIf="isReinstatable$ | async"
5656
[attr.aria-label]="'item.edit.bitstreams.reinstate-button' | translate"
5757
(click)="reinstate()"><i
5858
class="fas fa-undo-alt"></i>
5959
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.bitstreams.reinstate-button" | translate}}</span>
6060
</button>
61-
<button class="btn btn-primary" [disabled]="(hasChanges() | async) !== true || submitting"
61+
<button class="btn btn-primary" [disabled]="(hasChanges$ | async) !== true || submitting"
6262
[attr.aria-label]="'item.edit.bitstreams.save-button' | translate"
6363
(click)="submit()"><i
6464
class="fas fa-save"></i>
6565
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.bitstreams.save-button" | translate}}</span>
6666
</button>
67-
<button class="btn btn-danger" *ngIf="(isReinstatable() | async) !== true"
67+
<button class="btn btn-danger" *ngIf="(isReinstatable$ | async) !== true"
6868
[attr.aria-label]="'item.edit.bitstreams.discard-button' | translate"
69-
[disabled]="(hasChanges() | async) !== true || submitting"
69+
[disabled]="(hasChanges$ | async) !== true || submitting"
7070
(click)="discard()"><i
7171
class="fas fa-times"></i>
7272
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.bitstreams.discard-button" | translate}}</span>

src/app/item-page/edit-item-page/item-relationships/edit-item-relationships.service.spec.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,6 @@ describe('EditItemRelationshipsService', () => {
185185

186186
expect(itemService.invalidateByHref).toHaveBeenCalledWith(currentItem.self);
187187
expect(itemService.invalidateByHref).toHaveBeenCalledWith(relationshipItem1.self);
188-
// TODO currently this isn't done yet
189-
// expect(itemService.invalidateByHref).toHaveBeenCalledWith(relationshipItem2.self);
190188

191189
expect(notificationsService.success).toHaveBeenCalledTimes(1);
192190
});
Lines changed: 38 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,7 @@
11
<div class="item-relationships">
2-
<ng-container *ngIf="entityType$ | async as entityType">
3-
<div class="button-row top d-flex space-children-mr">
4-
<button class="btn btn-danger ml-auto" *ngIf="(isReinstatable() | async) !== true"
5-
[disabled]="(hasChanges() | async) !== true"
6-
(click)="discard()"><i
7-
class="fas fa-times"></i>
8-
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.discard-button" | translate}}</span>
9-
</button>
10-
<button class="btn btn-warning ml-auto" *ngIf="isReinstatable() | async"
11-
(click)="reinstate()"><i
12-
class="fas fa-undo-alt"></i>
13-
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.reinstate-button" | translate}}</span>
14-
</button>
15-
<button class="btn btn-primary" [disabled]="(hasChanges() | async) !== true"
16-
(click)="submit()"><span *ngIf="isSaving$ | async" class="spinner-border spinner-border-sm" role="status"
17-
aria-hidden="true"></span>
18-
<i *ngIf="(isSaving$ | async) !== true" class="fas fa-save"></i>
19-
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.save-button" | translate}}</span>
20-
</button>
2+
<ng-container *ngIf="entityType$ | async as entityType; else noEntityType">
3+
<div class="button-row top d-flex justify-content-end">
4+
<ng-container *ngTemplateOutlet="buttons"></ng-container>
215
</div>
226
<div *ngIf="relationshipTypes$ | async as relationshipTypes; else loading" class="mb-4">
237
<div *ngFor="let relationshipType of relationshipTypes; trackBy: trackById" class="mb-4">
@@ -26,36 +10,46 @@
2610
[item]="item"
2711
[itemType]="entityType"
2812
[relationshipType]="relationshipType"
29-
[hasChanges]="hasChanges()"
13+
[hasChanges]="hasChanges$"
3014
></ds-edit-relationship-list>
3115
</div>
3216
</div>
33-
<ng-template #loading>
34-
<ds-loading></ds-loading>
35-
</ng-template>
3617
<div class="button-row bottom">
37-
<div class="float-right space-children-mr ml-gap">
38-
<button class="btn btn-danger" *ngIf="(isReinstatable() | async) !== true"
39-
[disabled]="(hasChanges() | async) !== true"
40-
(click)="discard()"><i
41-
class="fas fa-times"></i>
42-
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.discard-button" | translate}}</span>
43-
</button>
44-
<button class="btn btn-warning" *ngIf="isReinstatable() | async"
45-
(click)="reinstate()"><i
46-
class="fas fa-undo-alt"></i>
47-
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.reinstate-button" | translate}}</span>
48-
</button>
49-
<button class="btn btn-primary" [disabled]="(hasChanges() | async) !== true"
50-
(click)="submit()"><i
51-
class="fas fa-save"></i>
52-
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.save-button" | translate}}</span>
53-
</button>
18+
<div class="float-right ml-gap">
19+
<ng-container *ngTemplateOutlet="buttons"></ng-container>
5420
</div>
5521
</div>
56-
<div *ngIf="!entityType"
57-
class="alert alert-info mt-2" role="alert">
58-
{{ 'item.edit.relationships.no-entity-type' | translate }}
59-
</div>
6022
</ng-container>
6123
</div>
24+
25+
<ng-template #noEntityType>
26+
<ds-alert [type]="AlertType.Info" class="d-block mt-2">
27+
{{ 'item.edit.relationships.no-entity-type' | translate }}
28+
</ds-alert>
29+
</ng-template>
30+
31+
<ng-template #loading>
32+
<ds-loading></ds-loading>
33+
</ng-template>
34+
35+
<ng-template #buttons>
36+
<div class="d-flex space-children-mr justify-content-end">
37+
<button class="btn btn-danger" *ngIf="(isReinstatable$ | async) !== true"
38+
[disabled]="(hasChanges$ | async) !== true"
39+
(click)="discard()">
40+
<i aria-hidden="true" class="fas fa-times"></i>
41+
<span class="d-none d-sm-inline">&nbsp;{{ 'item.edit.metadata.discard-button' | translate }}</span>
42+
</button>
43+
<button class="btn btn-warning" *ngIf="isReinstatable$ | async" (click)="reinstate()">
44+
<i aria-hidden="true" class="fas fa-undo-alt"></i>
45+
<span class="d-none d-sm-inline">&nbsp;{{ 'item.edit.metadata.reinstate-button' | translate }}</span>
46+
</button>
47+
<button class="btn btn-primary"
48+
[disabled]="(hasChanges$ | async) !== true || (isSaving$ | async) === true"
49+
(click)="submit()">
50+
<span *ngIf="isSaving$ | async" aria-hidden="true" class="spinner-border spinner-border-sm" role="status"></span>
51+
<i *ngIf="(isSaving$ | async) !== true" aria-hidden="true" class="fas fa-save"></i>
52+
<span class="d-none d-sm-inline">&nbsp;{{ 'item.edit.metadata.save-button' | translate }}</span>
53+
</button>
54+
</div>
55+
</ng-template>

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,10 @@ import {
1313
Router,
1414
} from '@angular/router';
1515
import { TranslateModule } from '@ngx-translate/core';
16-
import { getTestScheduler } from 'jasmine-marbles';
1716
import {
1817
combineLatest as observableCombineLatest,
1918
of as observableOf,
2019
} from 'rxjs';
21-
import { TestScheduler } from 'rxjs/testing';
2220

2321
import { ObjectCacheService } from '../../../core/cache/object-cache.service';
2422
import { RestResponse } from '../../../core/cache/response.models';
@@ -33,6 +31,7 @@ import { Item } from '../../../core/shared/item.model';
3331
import { ItemType } from '../../../core/shared/item-relationships/item-type.model';
3432
import { Relationship } from '../../../core/shared/item-relationships/relationship.model';
3533
import { RelationshipType } from '../../../core/shared/item-relationships/relationship-type.model';
34+
import { AlertComponent } from '../../../shared/alert/alert.component';
3635
import { getMockThemeService } from '../../../shared/mocks/theme-service.mock';
3736
import {
3837
INotification,
@@ -78,7 +77,6 @@ let itemService: ItemDataServiceStub;
7877
const url = 'http://test-url.com/test-url';
7978
router.url = url;
8079

81-
let scheduler: TestScheduler;
8280
let item;
8381
let author1;
8482
let author2;
@@ -226,7 +224,6 @@ describe('ItemRelationshipsComponent', () => {
226224
},
227225
);
228226

229-
scheduler = getTestScheduler();
230227
TestBed.configureTestingModule({
231228
imports: [TranslateModule.forRoot(), ItemRelationshipsComponent],
232229
providers: [
@@ -245,6 +242,12 @@ describe('ItemRelationshipsComponent', () => {
245242
], schemas: [
246243
NO_ERRORS_SCHEMA,
247244
],
245+
}).overrideComponent(ItemRelationshipsComponent, {
246+
remove: {
247+
imports: [
248+
AlertComponent,
249+
],
250+
},
248251
}).compileComponents();
249252
}));
250253

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
AsyncPipe,
33
NgForOf,
44
NgIf,
5+
NgTemplateOutlet,
56
} from '@angular/common';
67
import {
78
ChangeDetectorRef,
@@ -39,6 +40,8 @@ import {
3940
getFirstSucceededRemoteData,
4041
getRemoteDataPayload,
4142
} from '../../../core/shared/operators';
43+
import { AlertComponent } from '../../../shared/alert/alert.component';
44+
import { AlertType } from '../../../shared/alert/alert-type';
4245
import { ThemedLoadingComponent } from '../../../shared/loading/themed-loading.component';
4346
import { NotificationsService } from '../../../shared/notifications/notifications.service';
4447
import { followLink } from '../../../shared/utils/follow-link-config.model';
@@ -53,12 +56,14 @@ import { EditRelationshipListComponent } from './edit-relationship-list/edit-rel
5356
styleUrls: ['./item-relationships.component.scss'],
5457
templateUrl: './item-relationships.component.html',
5558
imports: [
56-
ThemedLoadingComponent,
59+
AlertComponent,
5760
AsyncPipe,
58-
TranslateModule,
59-
NgIf,
6061
EditRelationshipListComponent,
6162
NgForOf,
63+
NgIf,
64+
NgTemplateOutlet,
65+
ThemedLoadingComponent,
66+
TranslateModule,
6267
VarDirective,
6368
],
6469
standalone: true,
@@ -83,6 +88,8 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent {
8388
return this.editItemRelationshipsService.isSaving$;
8489
}
8590

91+
readonly AlertType = AlertType;
92+
8693
constructor(
8794
public itemService: ItemDataService,
8895
public objectUpdatesService: ObjectUpdatesService,

src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ <h4 class="modal-title" id="modal-title">{{ ('submission.sections.describe.relat
3232
</ng-template>
3333
</li>
3434
<li ngbNavItem *ngFor="let source of (externalSourcesRD$ | async); let idx = index" role="presentation">
35-
<a ngbNavLink>{{'submission.sections.describe.relationship-lookup.search-tab.tab-title.' + source.id | translate : { count: (totalExternal$ | async)[idx] } }}</a>
35+
<a ngbNavLink>{{'submission.sections.describe.relationship-lookup.search-tab.tab-title.' + source.id | translate : { count: (totalExternal$ | async)?.[idx] } }}</a>
3636
<ng-template ngbNavContent>
3737
<ds-dynamic-lookup-relation-external-source-tab
3838
[label]="label"

src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import { PaginatedList } from '../../../../../core/data/paginated-list.model';
4141
import { RelationshipDataService } from '../../../../../core/data/relationship-data.service';
4242
import { RelationshipTypeDataService } from '../../../../../core/data/relationship-type-data.service';
4343
import { Context } from '../../../../../core/shared/context.model';
44+
import { DSpaceObject } from '../../../../../core/shared/dspace-object.model';
4445
import { ExternalSource } from '../../../../../core/shared/external-source.model';
4546
import { Item } from '../../../../../core/shared/item.model';
4647
import { RelationshipType } from '../../../../../core/shared/item-relationships/relationship-type.model';
@@ -283,7 +284,7 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
283284
* Select (a list of) objects and add them to the store
284285
* @param selectableObjects
285286
*/
286-
select(...selectableObjects: SearchResult<Item>[]) {
287+
select(...selectableObjects: SearchResult<DSpaceObject>[]) {
287288
this.zone.runOutsideAngular(
288289
() => {
289290
const obs: Observable<any[]> = observableCombineLatest([...selectableObjects.map((sri: SearchResult<Item>) => {
@@ -326,11 +327,11 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
326327
* Deselect (a list of) objects and remove them from the store
327328
* @param selectableObjects
328329
*/
329-
deselect(...selectableObjects: SearchResult<Item>[]) {
330+
deselect(...selectableObjects: SearchResult<DSpaceObject>[]) {
330331
this.zone.runOutsideAngular(
331332
() => selectableObjects.forEach((object) => {
332333
this.subMap[object.indexableObject.uuid].unsubscribe();
333-
this.store.dispatch(new RemoveRelationshipAction(this.item, object.indexableObject, this.relationshipOptions.relationshipType, this.submissionId));
334+
this.store.dispatch(new RemoveRelationshipAction(this.item, object.indexableObject as Item, this.relationshipOptions.relationshipType, this.submissionId));
334335
}),
335336
);
336337
}

src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/search-tab/dynamic-lookup-relation-search-tab.component.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,12 +132,12 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest
132132
/**
133133
* Send an event to deselect an object from the list
134134
*/
135-
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
135+
@Output() deselectObject: EventEmitter<SearchResult<DSpaceObject>> = new EventEmitter();
136136

137137
/**
138138
* Send an event to select an object from the list
139139
*/
140-
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
140+
@Output() selectObject: EventEmitter<SearchResult<DSpaceObject>> = new EventEmitter();
141141

142142
/**
143143
* Search results
@@ -214,7 +214,7 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest
214214
this.selection$
215215
.pipe(take(1))
216216
.subscribe((selection: SearchResult<Item>[]) => {
217-
const filteredPage = page.filter((pageItem) => selection.findIndex((selected) => selected.equals(pageItem)) < 0);
217+
const filteredPage: SearchResult<DSpaceObject>[] = page.filter((pageItem: SearchResult<DSpaceObject>) => selection.findIndex((selected: SearchResult<Item>) => selected.equals(pageItem)) < 0);
218218
this.selectObject.emit(...filteredPage);
219219
});
220220
this.selectableListService.select(this.listId, page);

src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/search-tab/themed-dynamic-lookup-relation-search-tab.component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@ export class ThemedDynamicLookupRelationSearchTabComponent extends ThemedCompone
5151

5252
@Input() isEditRelationship: boolean;
5353

54-
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter();
54+
@Output() deselectObject: EventEmitter<SearchResult<DSpaceObject>> = new EventEmitter();
5555

56-
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter();
56+
@Output() selectObject: EventEmitter<SearchResult<DSpaceObject>> = new EventEmitter();
5757

5858
@Output() resultFound: EventEmitter<SearchObjects<DSpaceObject>> = new EventEmitter();
5959

0 commit comments

Comments
 (0)