From 33b59c739dc0ee541e800f0af5fa31726c8014fb Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Tue, 28 May 2024 15:53:00 +0200 Subject: [PATCH 1/5] 115284: Fix issue with same type relationships --- .../edit-item-page/edit-item-page.module.ts | 4 + .../edit-item-relationships.service.spec.ts | 89 +++++++++++++++ .../edit-item-relationships.service.ts | 49 +++++++- ...t-relationship-list-wrapper.component.html | 30 +++++ ...t-relationship-list-wrapper.component.scss | 0 ...elationship-list-wrapper.component.spec.ts | 108 ++++++++++++++++++ ...dit-relationship-list-wrapper.component.ts | 91 +++++++++++++++ .../edit-relationship-list.component.spec.ts | 6 +- .../edit-relationship-list.component.ts | 42 ++----- .../item-relationships.component.html | 4 +- ...namic-lookup-relation-modal.component.html | 1 + ...dynamic-lookup-relation-modal.component.ts | 5 + ...-lookup-relation-search-tab.component.html | 1 + ...ic-lookup-relation-search-tab.component.ts | 5 + ...ic-lookup-relation-search-tab.component.ts | 4 +- .../shared/search/search.component.spec.ts | 2 + src/app/shared/search/search.component.ts | 20 +++- .../shared/search/themed-search.component.ts | 4 +- 18 files changed, 425 insertions(+), 40 deletions(-) create mode 100644 src/app/item-page/edit-item-page/item-relationships/edit-relationship-list-wrapper/edit-relationship-list-wrapper.component.html create mode 100644 src/app/item-page/edit-item-page/item-relationships/edit-relationship-list-wrapper/edit-relationship-list-wrapper.component.scss create mode 100644 src/app/item-page/edit-item-page/item-relationships/edit-relationship-list-wrapper/edit-relationship-list-wrapper.component.spec.ts create mode 100644 src/app/item-page/edit-item-page/item-relationships/edit-relationship-list-wrapper/edit-relationship-list-wrapper.component.ts diff --git a/src/app/item-page/edit-item-page/edit-item-page.module.ts b/src/app/item-page/edit-item-page/edit-item-page.module.ts index 0a75394ddd..60aa2828bd 100644 --- a/src/app/item-page/edit-item-page/edit-item-page.module.ts +++ b/src/app/item-page/edit-item-page/edit-item-page.module.ts @@ -46,6 +46,9 @@ import { ResultsBackButtonModule } from '../../shared/results-back-button/result import { AccessControlFormModule } from '../../shared/access-control-form-container/access-control-form.module'; +import { + EditRelationshipListWrapperComponent +} from './item-relationships/edit-relationship-list-wrapper/edit-relationship-list-wrapper.component'; /** * Module that contains all components related to the Edit Item page administrator functionality @@ -94,6 +97,7 @@ import { ItemRegisterDoiComponent, ItemCurateComponent, ItemAccessControlComponent, + EditRelationshipListWrapperComponent, ], providers: [ BundleDataService, diff --git a/src/app/item-page/edit-item-page/item-relationships/edit-item-relationships.service.spec.ts b/src/app/item-page/edit-item-page/item-relationships/edit-item-relationships.service.spec.ts index f969416783..6ca7f57739 100644 --- a/src/app/item-page/edit-item-page/item-relationships/edit-item-relationships.service.spec.ts +++ b/src/app/item-page/edit-item-page/item-relationships/edit-item-relationships.service.spec.ts @@ -265,6 +265,95 @@ describe('EditItemRelationshipsService', () => { }); }); + describe('isProvidedItemTypeLeftType', () => { + it('should return true if the provided item corresponds to the left type of the relationship', (done) => { + const relationshipType = Object.assign(new RelationshipType(), { + leftType: createSuccessfulRemoteDataObject$({id: 'leftType'}), + rightType: createSuccessfulRemoteDataObject$({id: 'rightType'}), + }); + const itemType = Object.assign(new ItemType(), {id: 'leftType'} ); + const item = Object.assign(new Item(), {uuid: 'item-uuid'}); + + const result = service.isProvidedItemTypeLeftType(relationshipType, itemType, item); + result.subscribe((resultValue) => { + expect(resultValue).toBeTrue(); + done(); + }); + }); + + it('should return false if the provided item corresponds to the right type of the relationship', (done) => { + const relationshipType = Object.assign(new RelationshipType(), { + leftType: createSuccessfulRemoteDataObject$({id: 'leftType'}), + rightType: createSuccessfulRemoteDataObject$({id: 'rightType'}), + }); + const itemType = Object.assign(new ItemType(), {id: 'rightType'} ); + const item = Object.assign(new Item(), {uuid: 'item-uuid'}); + + const result = service.isProvidedItemTypeLeftType(relationshipType, itemType, item); + result.subscribe((resultValue) => { + expect(resultValue).toBeFalse(); + done(); + }); + }); + + it('should return undefined if the provided item corresponds does not match any of the relationship types', (done) => { + const relationshipType = Object.assign(new RelationshipType(), { + leftType: createSuccessfulRemoteDataObject$({id: 'leftType'}), + rightType: createSuccessfulRemoteDataObject$({id: 'rightType'}), + }); + const itemType = Object.assign(new ItemType(), {id: 'something-else'} ); + const item = Object.assign(new Item(), {uuid: 'item-uuid'}); + + const result = service.isProvidedItemTypeLeftType(relationshipType, itemType, item); + result.subscribe((resultValue) => { + expect(resultValue).toBeUndefined(); + done(); + }); + }); + }); + + describe('relationshipMatchesBothSameTypes', () => { + it('should return true if both left and right type of the relationship type are the same and match the provided itemtype', (done) => { + const relationshipType = Object.assign(new RelationshipType(), { + leftType: createSuccessfulRemoteDataObject$({id: 'sameType'}), + rightType: createSuccessfulRemoteDataObject$({id:'sameType'}), + }); + const itemType = Object.assign(new ItemType(), {id: 'sameType'} ); + + const result = service.relationshipMatchesBothSameTypes(relationshipType, itemType); + result.subscribe((resultValue) => { + expect(resultValue).toBeTrue(); + done(); + }); + }); + it('should return false if both left and right type of the relationship type are the same and do not match the provided itemtype', (done) => { + const relationshipType = Object.assign(new RelationshipType(), { + leftType: createSuccessfulRemoteDataObject$({id: 'sameType'}), + rightType: createSuccessfulRemoteDataObject$({id: 'sameType'}), + }); + const itemType = Object.assign(new ItemType(), {id: 'something-else'} ); + + const result = service.relationshipMatchesBothSameTypes(relationshipType, itemType); + result.subscribe((resultValue) => { + expect(resultValue).toBeFalse(); + done(); + }); + }); + it('should return false if both left and right type of the relationship type are different', (done) => { + const relationshipType = Object.assign(new RelationshipType(), { + leftType: createSuccessfulRemoteDataObject$({id: 'leftType'}), + rightType: createSuccessfulRemoteDataObject$({id: 'rightType'}), + }); + const itemType = Object.assign(new ItemType(), {id: 'leftType'} ); + + const result = service.relationshipMatchesBothSameTypes(relationshipType, itemType); + result.subscribe((resultValue) => { + expect(resultValue).toBeFalse(); + done(); + }); + }); + }); + describe('displayNotifications', () => { it('should show one success notification when multiple requests succeeded', () => { service.displayNotifications([ diff --git a/src/app/item-page/edit-item-page/item-relationships/edit-item-relationships.service.ts b/src/app/item-page/edit-item-page/item-relationships/edit-item-relationships.service.ts index 2cecd878b7..f14e95f66f 100644 --- a/src/app/item-page/edit-item-page/item-relationships/edit-item-relationships.service.ts +++ b/src/app/item-page/edit-item-page/item-relationships/edit-item-relationships.service.ts @@ -10,7 +10,7 @@ import { } from '../../../core/data/object-updates/object-updates.reducer'; import { RemoteData } from '../../../core/data/remote-data'; import { Relationship } from '../../../core/shared/item-relationships/relationship.model'; -import { EMPTY, Observable, BehaviorSubject, Subscription } from 'rxjs'; +import { EMPTY, Observable, BehaviorSubject, Subscription, combineLatest as observableCombineLatest } from 'rxjs'; import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service'; import { ItemDataService } from '../../../core/data/item-data.service'; import { Item } from '../../../core/shared/item.model'; @@ -20,6 +20,9 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { RelationshipDataService } from '../../../core/data/relationship-data.service'; import { EntityTypeDataService } from '../../../core/data/entity-type-data.service'; import { TranslateService } from '@ngx-translate/core'; +import { ItemType } from '../../../core/shared/item-relationships/item-type.model'; +import { getFirstSucceededRemoteData, getRemoteDataPayload } from '../../../core/shared/operators'; +import { RelationshipType } from '../../../core/shared/item-relationships/relationship-type.model'; @Injectable({ providedIn: 'root' @@ -169,6 +172,49 @@ export class EditItemRelationshipsService { } } + isProvidedItemTypeLeftType(relationshipType: RelationshipType, itemType: ItemType, item: Item): Observable { + return this.getRelationshipLeftAndRightType(relationshipType).pipe( + map(([leftType, rightType]: [ItemType, ItemType]) => { + if (leftType.id === itemType.id) { + return true; + } + + if (rightType.id === itemType.id) { + return false; + } + + // should never happen... + console.warn(`The item ${item.uuid} is not on the right or the left side of relationship type ${relationshipType.uuid}`); + return undefined; + }) + ); + } + + relationshipMatchesBothSameTypes(relationshipType: RelationshipType, itemType: ItemType): Observable { + return this.getRelationshipLeftAndRightType(relationshipType).pipe( + map(([leftType, rightType]: [ItemType, ItemType]) => { + return leftType.id === itemType.id && rightType.id === itemType.id; + }) + ); + } + + protected getRelationshipLeftAndRightType(relationshipType: RelationshipType): Observable<[ItemType, ItemType]> { + const leftType$: Observable = relationshipType.leftType.pipe( + getFirstSucceededRemoteData(), + getRemoteDataPayload(), + ); + + const rightType$: Observable = relationshipType.rightType.pipe( + getFirstSucceededRemoteData(), + getRemoteDataPayload(), + ); + + return observableCombineLatest([ + leftType$, + rightType$, + ]); + } + /** @@ -185,6 +231,5 @@ export class EditItemRelationshipsService { */ getNotificationContent(key: string): string { return this.translateService.instant(this.notificationsPrefix + key + '.content'); - } } diff --git a/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list-wrapper/edit-relationship-list-wrapper.component.html b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list-wrapper/edit-relationship-list-wrapper.component.html new file mode 100644 index 0000000000..e6c1aa25f7 --- /dev/null +++ b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list-wrapper/edit-relationship-list-wrapper.component.html @@ -0,0 +1,30 @@ + + + + + + + + + diff --git a/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list-wrapper/edit-relationship-list-wrapper.component.scss b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list-wrapper/edit-relationship-list-wrapper.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list-wrapper/edit-relationship-list-wrapper.component.spec.ts b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list-wrapper/edit-relationship-list-wrapper.component.spec.ts new file mode 100644 index 0000000000..6c1aa9dafe --- /dev/null +++ b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list-wrapper/edit-relationship-list-wrapper.component.spec.ts @@ -0,0 +1,108 @@ +import { ChangeDetectorRef, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { of as observableOf } from 'rxjs'; +import { EditRelationshipListWrapperComponent } from './edit-relationship-list-wrapper.component'; +import { EditItemRelationshipsService } from '../edit-item-relationships.service'; +import { By } from '@angular/platform-browser'; +import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model'; +import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils'; +import { ItemType } from '../../../../core/shared/item-relationships/item-type.model'; +import { Item } from '../../../../core/shared/item.model'; + +describe('EditRelationshipListWrapperComponent', () => { + let editItemRelationshipsService: EditItemRelationshipsService; + let comp: EditRelationshipListWrapperComponent; + let fixture: ComponentFixture; + + const leftType = Object.assign(new ItemType(), {id: 'leftType', label: 'leftTypeString'}); + const rightType = Object.assign(new ItemType(), {id: 'rightType', label: 'rightTypeString'}); + + const relationshipType = Object.assign(new RelationshipType(), { + id: '1', + leftMaxCardinality: null, + leftMinCardinality: 0, + leftType: createSuccessfulRemoteDataObject$(leftType), + leftwardType: 'isOrgUnitOfOrgUnit', + rightMaxCardinality: null, + rightMinCardinality: 0, + rightType: createSuccessfulRemoteDataObject$(rightType), + rightwardType: 'isOrgUnitOfOrgUnit', + uuid: 'relationshiptype-1', + }); + + const item = Object.assign(new Item(), {uuid: 'item-uuid'}); + + beforeEach(waitForAsync(() => { + + editItemRelationshipsService = jasmine.createSpyObj('editItemRelationshipsService', { + isProvidedItemTypeLeftType: observableOf(true), + relationshipMatchesBothSameTypes: observableOf(false) + }); + + + TestBed.configureTestingModule({ + // imports: [NoopAnimationsModule, SharedModule, TranslateModule.forRoot()], + declarations: [EditRelationshipListWrapperComponent], + providers: [ + {provide: EditItemRelationshipsService, useValue: editItemRelationshipsService}, + ChangeDetectorRef + ], schemas: [ + CUSTOM_ELEMENTS_SCHEMA + ] + }).compileComponents(); + + })); + + beforeEach(() => { + fixture = TestBed.createComponent(EditRelationshipListWrapperComponent); + comp = fixture.componentInstance; + comp.relationshipType = relationshipType; + comp.itemType = leftType; + comp.item = item; + + fixture.detectChanges(); + }); + + describe('onInit', () => { + it('should render the component', () => { + expect(comp).toBeTruthy(); + }); + it('should set currentItemIsLeftItem$ and bothItemsMatchType$ based on the provided relationshipType, itemType and item', () => { + expect(editItemRelationshipsService.isProvidedItemTypeLeftType).toHaveBeenCalledWith(relationshipType, leftType, item); + expect(editItemRelationshipsService.relationshipMatchesBothSameTypes).toHaveBeenCalledWith(relationshipType, leftType); + + expect(comp.currentItemIsLeftItem$.getValue()).toEqual(true); + expect(comp.bothItemsMatchType$.getValue()).toEqual(false); + }); + }); + + describe('when the current item is left', () => { + it('should render one relationship list section', () => { + const relationshipLists = fixture.debugElement.queryAll(By.css('ds-edit-relationship-list')); + expect(relationshipLists.length).toEqual(1); + }); + }); + + describe('when the current item is right', () => { + it('should render one relationship list section', () => { + (editItemRelationshipsService.isProvidedItemTypeLeftType as jasmine.Spy).and.returnValue(observableOf(false)); + comp.ngOnInit(); + fixture.detectChanges(); + + const relationshipLists = fixture.debugElement.queryAll(By.css('ds-edit-relationship-list')); + expect(relationshipLists.length).toEqual(1); + }); + }); + + describe('when the current item is both left and right', () => { + it('should render two relationship list sections', () => { + (editItemRelationshipsService.relationshipMatchesBothSameTypes as jasmine.Spy).and.returnValue(observableOf(true)); + comp.ngOnInit(); + fixture.detectChanges(); + + const relationshipLists = fixture.debugElement.queryAll(By.css('ds-edit-relationship-list')); + expect(relationshipLists.length).toEqual(2); + }); + }); + +}); diff --git a/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list-wrapper/edit-relationship-list-wrapper.component.ts b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list-wrapper/edit-relationship-list-wrapper.component.ts new file mode 100644 index 0000000000..8231eaa542 --- /dev/null +++ b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list-wrapper/edit-relationship-list-wrapper.component.ts @@ -0,0 +1,91 @@ +import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; +import { BehaviorSubject, Observable, Subscription } from 'rxjs'; +import { Item } from '../../../../core/shared/item.model'; +import { hasValue } from '../../../../shared/empty.util'; +import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model'; +import { ItemType } from '../../../../core/shared/item-relationships/item-type.model'; +import { EditItemRelationshipsService } from '../edit-item-relationships.service'; + +@Component({ + selector: 'ds-edit-relationship-list-wrapper', + styleUrls: ['./edit-relationship-list-wrapper.component.scss'], + templateUrl: './edit-relationship-list-wrapper.component.html', +}) +/** + * A component creating a list of editable relationships of a certain type + * The relationships are rendered as a list of related items + */ +export class EditRelationshipListWrapperComponent implements OnInit, OnDestroy { + + /** + * The item to display related items for + */ + @Input() item: Item; + + @Input() itemType: ItemType; + + /** + * The URL to the current page + * Used to fetch updates for the current item from the store + */ + @Input() url: string; + + /** + * The label of the relationship-type we're rendering a list for + */ + @Input() relationshipType: RelationshipType; + + /** + * If updated information has changed + */ + @Input() hasChanges!: Observable; + + /** + * The event emmiter to submit the new information + */ + @Output() submitModal: EventEmitter = new EventEmitter(); + + /** + * Observable that emits true if {@link itemType} is on the left-hand side of {@link relationshipType}, + * false if it is on the right-hand side and undefined in the rare case that it is on neither side. + */ + currentItemIsLeftItem$: BehaviorSubject = new BehaviorSubject(undefined); + + + isLeftItem$ = new BehaviorSubject(true); + + isRightItem$ = new BehaviorSubject(false); + + bothItemsMatchType$: BehaviorSubject = new BehaviorSubject(undefined); + + /** + * Array to track all subscriptions and unsubscribe them onDestroy + * @type {Array} + */ + private subs: Subscription[] = []; + + constructor( + protected editItemRelationshipsService: EditItemRelationshipsService, + ) { + } + + + ngOnInit(): void { + this.subs.push(this.editItemRelationshipsService.isProvidedItemTypeLeftType(this.relationshipType, this.itemType, this.item) + .subscribe((nextValue: boolean) => { + this.currentItemIsLeftItem$.next(nextValue); + })); + + this.subs.push(this.editItemRelationshipsService.relationshipMatchesBothSameTypes(this.relationshipType, this.itemType) + .subscribe((nextValue: boolean) => { + this.bothItemsMatchType$.next(nextValue); + })); + } + + + ngOnDestroy(): void { + this.subs + .filter((subscription) => hasValue(subscription)) + .forEach((subscription) => subscription.unsubscribe()); + } +} diff --git a/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts index 3a627232a4..9504ebb078 100644 --- a/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts +++ b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts @@ -2,7 +2,7 @@ import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core'; import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { TranslateModule } from '@ngx-translate/core'; -import { of as observableOf } from 'rxjs'; +import { BehaviorSubject, of as observableOf } from 'rxjs'; import { LinkService } from '../../../../core/cache/builders/link.service'; import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service'; import { RelationshipDataService } from '../../../../core/data/relationship-data.service'; @@ -63,6 +63,7 @@ describe('EditRelationshipListComponent', () => { let relationships: Relationship[]; let relationshipType: RelationshipType; let paginationOptions: PaginationComponentOptions; + let currentItemIsLeftItem$ = new BehaviorSubject(true); const resetComponent = () => { fixture = TestBed.createComponent(EditRelationshipListComponent); @@ -73,6 +74,7 @@ describe('EditRelationshipListComponent', () => { comp.url = url; comp.relationshipType = relationshipType; comp.hasChanges = observableOf(false); + comp.currentItemIsLeftItem$ = currentItemIsLeftItem$; fixture.detectChanges(); }; @@ -293,6 +295,7 @@ describe('EditRelationshipListComponent', () => { leftwardType: 'isAuthorOfPublication', rightwardType: 'isPublicationOfAuthor', }); + currentItemIsLeftItem$ = new BehaviorSubject(true); relationshipService.getItemRelationshipsByLabel.calls.reset(); resetComponent(); }); @@ -317,6 +320,7 @@ describe('EditRelationshipListComponent', () => { leftwardType: 'isPublicationOfAuthor', rightwardType: 'isAuthorOfPublication', }); + currentItemIsLeftItem$ = new BehaviorSubject(false); relationshipService.getItemRelationshipsByLabel.calls.reset(); resetComponent(); }); diff --git a/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.ts b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.ts index c4b2bba36f..c105f0ef93 100644 --- a/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.ts +++ b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.ts @@ -113,7 +113,7 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy { * Observable that emits true if {@link itemType} is on the left-hand side of {@link relationshipType}, * false if it is on the right-hand side and undefined in the rare case that it is on neither side. */ - private currentItemIsLeftItem$: BehaviorSubject = new BehaviorSubject(undefined); + @Input() currentItemIsLeftItem$: BehaviorSubject = new BehaviorSubject(undefined); relatedEntityType$: Observable; @@ -213,18 +213,15 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy { * Get the relevant label for this relationship type */ private getLabel(): Observable { - return observableCombineLatest([ - this.relationshipType.leftType, - this.relationshipType.rightType, - ].map((itemTypeRD) => itemTypeRD.pipe( - getFirstSucceededRemoteData(), - getRemoteDataPayload(), - ))).pipe( - map((itemTypes: ItemType[]) => [ - this.relationshipType.leftwardType, - this.relationshipType.rightwardType, - ][itemTypes.findIndex((itemType) => itemType.id === this.itemType.id)]), - ); + return this.currentItemIsLeftItem$.pipe( + map((currentItemIsLeftItem) => { + if (currentItemIsLeftItem) { + return this.relationshipType.leftwardType; + } else { + return this.relationshipType.rightwardType; + } + }) + ); } /** @@ -251,6 +248,7 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy { modalComp.toAdd = []; modalComp.toRemove = []; modalComp.isPending = false; + modalComp.hiddenQuery = '-search.resourceid:' + this.item.uuid; this.item.owningCollection.pipe( getFirstSucceededRemoteDataPayload() @@ -424,24 +422,6 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy { this.relationshipMessageKey$ = this.getRelationshipMessageKey(); - this.subs.push(this.relationshipLeftAndRightType$.pipe( - map(([leftType, rightType]: [ItemType, ItemType]) => { - if (leftType.id === this.itemType.id) { - return true; - } - - if (rightType.id === this.itemType.id) { - return false; - } - - // should never happen... - console.warn(`The item ${this.item.uuid} is not on the right or the left side of relationship type ${this.relationshipType.uuid}`); - return undefined; - }) - ).subscribe((nextValue: boolean) => { - this.currentItemIsLeftItem$.next(nextValue); - })); - // initialize the pagination options this.paginationConfig = new PaginationComponentOptions(); diff --git a/src/app/item-page/edit-item-page/item-relationships/item-relationships.component.html b/src/app/item-page/edit-item-page/item-relationships/item-relationships.component.html index 755731f576..85f41a25d9 100644 --- a/src/app/item-page/edit-item-page/item-relationships/item-relationships.component.html +++ b/src/app/item-page/edit-item-page/item-relationships/item-relationships.component.html @@ -5,13 +5,13 @@
- + >
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.html index 71cc8de0ac..703fb8c812 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.html @@ -19,6 +19,7 @@ [repeatable]="repeatable" [context]="context" [query]="query" + [hiddenQuery]="hiddenQuery" [relationshipType]="relationshipType" [isLeft]="isLeft" [item]="item" diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts index ac436639bc..1881cc1efa 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts @@ -97,6 +97,11 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy query: string; + /** + * A hidden query that will be used but not displayed in the url/searchbar + */ + hiddenQuery: string; + /** * A map of subscriptions within this component */ diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/search-tab/dynamic-lookup-relation-search-tab.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/search-tab/dynamic-lookup-relation-search-tab.component.html index 00bfa7981a..caf3116a53 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/search-tab/dynamic-lookup-relation-search-tab.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/search-tab/dynamic-lookup-relation-search-tab.component.html @@ -2,6 +2,7 @@ [configuration]="this.relationship.searchConfiguration" [context]="context" [fixedFilterQuery]="this.relationship.filter" + [hiddenQuery]="hiddenQuery" [inPlaceSearch]="true" [linkType]="linkTypes.ExternalLink" [searchFormPlaceholder]="'submission.sections.describe.relationship-lookup.search-tab.search-form.placeholder'" diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/search-tab/dynamic-lookup-relation-search-tab.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/search-tab/dynamic-lookup-relation-search-tab.component.ts index 3e874d8c50..24b5ae3595 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/search-tab/dynamic-lookup-relation-search-tab.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/search-tab/dynamic-lookup-relation-search-tab.component.ts @@ -93,6 +93,11 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest */ @Input() isEditRelationship: boolean; + /** + * A hidden query that will be used but not displayed in the url/searchbar + */ + @Input() hiddenQuery: string; + /** * Send an event to deselect an object from the list */ diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/search-tab/themed-dynamic-lookup-relation-search-tab.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/search-tab/themed-dynamic-lookup-relation-search-tab.component.ts index d3ad1e2b78..9770de9745 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/search-tab/themed-dynamic-lookup-relation-search-tab.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/search-tab/themed-dynamic-lookup-relation-search-tab.component.ts @@ -18,7 +18,7 @@ import { DSpaceObject } from '../../../../../../core/shared/dspace-object.model' }) export class ThemedDynamicLookupRelationSearchTabComponent extends ThemedComponent { protected inAndOutputNames: (keyof DsDynamicLookupRelationSearchTabComponent & keyof this)[] = ['relationship', 'listId', - 'query', 'repeatable', 'selection$', 'context', 'relationshipType', 'item', 'isLeft', 'toRemove', 'isEditRelationship', + 'query', 'hiddenQuery', 'repeatable', 'selection$', 'context', 'relationshipType', 'item', 'isLeft', 'toRemove', 'isEditRelationship', 'deselectObject', 'selectObject', 'resultFound']; @Input() relationship: RelationshipOptions; @@ -27,6 +27,8 @@ export class ThemedDynamicLookupRelationSearchTabComponent extends ThemedCompone @Input() query: string; + @Input() hiddenQuery: string; + @Input() repeatable: boolean; @Input() selection$: Observable; diff --git a/src/app/shared/search/search.component.spec.ts b/src/app/shared/search/search.component.spec.ts index d0d9bdda86..876390e3a8 100644 --- a/src/app/shared/search/search.component.spec.ts +++ b/src/app/shared/search/search.component.spec.ts @@ -108,6 +108,7 @@ const searchServiceStub = jasmine.createSpyObj('SearchService', { }) as SearchService; const configurationParam = 'default'; const queryParam = 'test query'; +const hiddenQuery = 'hidden query'; const scopeParam = '7669c72a-3f2a-451f-a3b9-9210e7a4c02f'; const fixedFilter = 'fixed filter'; @@ -250,6 +251,7 @@ describe('SearchComponent', () => { comp = fixture.componentInstance; // SearchComponent test instance comp.inPlaceSearch = false; comp.paginationId = paginationId; + comp.hiddenQuery = hiddenQuery; spyOn((comp as any), 'getSearchOptions').and.returnValue(paginatedSearchOptions$.asObservable()); diff --git a/src/app/shared/search/search.component.ts b/src/app/shared/search/search.component.ts index 61f3a119c8..a5bc4c19cb 100644 --- a/src/app/shared/search/search.component.ts +++ b/src/app/shared/search/search.component.ts @@ -75,6 +75,11 @@ export class SearchComponent implements OnInit { */ @Input() fixedFilterQuery: string; + /** + * A hidden query that will be used but not displayed in the url/searchbar + */ + @Input() hiddenQuery: string; + /** * If this is true, the request will only be sent if there's * no valid cached version. Defaults to true @@ -337,6 +342,7 @@ export class SearchComponent implements OnInit { if (combinedOptions.query === '') { combinedOptions.query = this.query; } + const newSearchOptions = new PaginatedSearchOptions(combinedOptions); // check if search options are changed // if so retrieve new related results otherwise skip it @@ -440,8 +446,18 @@ export class SearchComponent implements OnInit { if (this.configuration === 'supervision') { followLinks.push(followLink('supervisionOrders', { isOptional: true }) as any); } + + const searchOptionsWithHidden = Object.assign (new PaginatedSearchOptions({}), searchOptions); + if (isNotEmpty(this.hiddenQuery)) { + if (isNotEmpty(searchOptionsWithHidden.query)) { + searchOptionsWithHidden.query = searchOptionsWithHidden.query + ' AND ' + this.hiddenQuery; + } else { + searchOptionsWithHidden.query = this.hiddenQuery; + } + } + this.service.search( - searchOptions, + searchOptionsWithHidden, undefined, this.useCachedVersionIfAvailable, true, @@ -450,7 +466,7 @@ export class SearchComponent implements OnInit { .subscribe((results: RemoteData>) => { if (results.hasSucceeded) { if (this.trackStatistics) { - this.service.trackSearch(searchOptions, results.payload); + this.service.trackSearch(searchOptionsWithHidden, results.payload); } if (results.payload?.page?.length > 0) { this.resultFound.emit(results.payload); diff --git a/src/app/shared/search/themed-search.component.ts b/src/app/shared/search/themed-search.component.ts index fe531e4f0f..5ca2dceab1 100644 --- a/src/app/shared/search/themed-search.component.ts +++ b/src/app/shared/search/themed-search.component.ts @@ -19,7 +19,7 @@ import { ListableObject } from '../object-collection/shared/listable-object.mode templateUrl: '../theme-support/themed.component.html', }) export class ThemedSearchComponent extends ThemedComponent { - protected inAndOutputNames: (keyof SearchComponent & keyof this)[] = ['configurationList', 'context', 'configuration', 'fixedFilterQuery', 'useCachedVersionIfAvailable', 'inPlaceSearch', 'linkType', 'paginationId', 'searchEnabled', 'sideBarWidth', 'searchFormPlaceholder', 'selectable', 'selectionConfig', 'showCsvExport', 'showSidebar', 'showThumbnails', 'showViewModes', 'useUniquePageId', 'viewModeList', 'showScopeSelector', 'resultFound', 'deselectObject', 'selectObject', 'trackStatistics', 'query']; + protected inAndOutputNames: (keyof SearchComponent & keyof this)[] = ['configurationList', 'context', 'configuration', 'fixedFilterQuery', 'hiddenQuery', 'useCachedVersionIfAvailable', 'inPlaceSearch', 'linkType', 'paginationId', 'searchEnabled', 'sideBarWidth', 'searchFormPlaceholder', 'selectable', 'selectionConfig', 'showCsvExport', 'showSidebar', 'showThumbnails', 'showViewModes', 'useUniquePageId', 'viewModeList', 'showScopeSelector', 'resultFound', 'deselectObject', 'selectObject', 'trackStatistics', 'query']; @Input() configurationList: SearchConfigurationOption[]; @@ -29,6 +29,8 @@ export class ThemedSearchComponent extends ThemedComponent { @Input() fixedFilterQuery: string; + @Input() hiddenQuery: string; + @Input() useCachedVersionIfAvailable: boolean; @Input() inPlaceSearch: boolean; From f1706e24c29a36547e8f3f780eb10132cc26c240 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Mon, 3 Jun 2024 11:56:59 +0200 Subject: [PATCH 2/5] 115046: Fixed related item not invalidating on relationship deletion & automatically invalidate the isSelected value after updating relationship on item --- .../data/relationship-data.service.spec.ts | 13 ++++++++++- .../core/data/relationship-data.service.ts | 7 +++++- .../edit-item-relationships.service.spec.ts | 1 + .../edit-item-relationships.service.ts | 12 +++++++++- .../edit-relationship-list.component.ts | 23 ++++++++++++++++--- 5 files changed, 50 insertions(+), 6 deletions(-) diff --git a/src/app/core/data/relationship-data.service.spec.ts b/src/app/core/data/relationship-data.service.spec.ts index 4432d5213a..625e0d62f4 100644 --- a/src/app/core/data/relationship-data.service.spec.ts +++ b/src/app/core/data/relationship-data.service.spec.ts @@ -125,7 +125,8 @@ describe('RelationshipDataService', () => { const itemService = jasmine.createSpyObj('itemService', { findById: (uuid) => createSuccessfulRemoteDataObject(relatedItems.find((relatedItem) => relatedItem.id === uuid)), - findByHref: createSuccessfulRemoteDataObject$(relatedItems[0]) + findByHref: createSuccessfulRemoteDataObject$(relatedItems[0]), + getIDHrefObs: (uuid: string) => observableOf(`https://demo.dspace.org/server/api/core/items/${uuid}`), }); function initTestService() { @@ -240,6 +241,16 @@ describe('RelationshipDataService', () => { }); }); + describe('searchByItemsAndType', () => { + it('should call addDependency for each item to invalidate the request when one of the items is update', () => { + spyOn(service as any, 'addDependency'); + + service.searchByItemsAndType(relationshipType.id, item.id, relationshipType.leftwardType, ['item-id-1', 'item-id-2']); + + expect((service as any).addDependency).toHaveBeenCalledTimes(2); + }); + }); + describe('resolveMetadataRepresentation', () => { const parentItem: Item = Object.assign(new Item(), { id: 'parent-item', diff --git a/src/app/core/data/relationship-data.service.ts b/src/app/core/data/relationship-data.service.ts index a7ab4eb31c..e3776d9bca 100644 --- a/src/app/core/data/relationship-data.service.ts +++ b/src/app/core/data/relationship-data.service.ts @@ -555,13 +555,18 @@ export class RelationshipDataService extends IdentifiableDataService>> = this.searchBy( 'byItemsAndType', { searchParams: searchParams }, ) as Observable>>; + arrayOfItemIds.forEach((itemId: string) => { + this.addDependency(searchRD$, this.itemService.getIDHrefObs(encodeURIComponent(itemId))); + }); + + return searchRD$; } /** diff --git a/src/app/item-page/edit-item-page/item-relationships/edit-item-relationships.service.spec.ts b/src/app/item-page/edit-item-page/item-relationships/edit-item-relationships.service.spec.ts index 6ca7f57739..ef66f91691 100644 --- a/src/app/item-page/edit-item-page/item-relationships/edit-item-relationships.service.spec.ts +++ b/src/app/item-page/edit-item-page/item-relationships/edit-item-relationships.service.spec.ts @@ -185,6 +185,7 @@ describe('EditItemRelationshipsService', () => { expect(itemService.invalidateByHref).toHaveBeenCalledWith(currentItem.self); expect(itemService.invalidateByHref).toHaveBeenCalledWith(relationshipItem1.self); + expect(itemService.invalidateByHref).toHaveBeenCalledWith(relationshipItem2.self); expect(notificationsService.success).toHaveBeenCalledTimes(1); }); diff --git a/src/app/item-page/edit-item-page/item-relationships/edit-item-relationships.service.ts b/src/app/item-page/edit-item-page/item-relationships/edit-item-relationships.service.ts index f14e95f66f..a3d45d6c70 100644 --- a/src/app/item-page/edit-item-page/item-relationships/edit-item-relationships.service.ts +++ b/src/app/item-page/edit-item-page/item-relationships/edit-item-relationships.service.ts @@ -61,7 +61,17 @@ export class EditItemRelationshipsService { // process each update one by one, while waiting for the previous to finish concatMap((update: FieldUpdate) => { if (update.changeType === FieldChangeType.REMOVE) { - return this.deleteRelationship(update.field as DeleteRelationship).pipe(take(1)); + return this.deleteRelationship(update.field as DeleteRelationship).pipe( + take(1), + switchMap((deleteRD: RemoteData) => { + if (deleteRD.hasSucceeded) { + return this.itemService.invalidateByHref((update.field as DeleteRelationship).relatedItem._links.self.href).pipe( + map(() => deleteRD), + ); + } + return [deleteRD]; + }), + ); } else if (update.changeType === FieldChangeType.ADD) { return this.addRelationship(update.field as RelationshipIdentifiable).pipe( take(1), diff --git a/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.ts b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.ts index c105f0ef93..f57859220a 100644 --- a/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.ts +++ b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.ts @@ -33,6 +33,7 @@ import { } from '../../../../core/shared/item-relationships/relationship-type.model'; import { getAllSucceededRemoteData, + getFirstCompletedRemoteData, getFirstSucceededRemoteData, getFirstSucceededRemoteDataPayload, getRemoteDataPayload, @@ -334,6 +335,7 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy { type: this.relationshipType, originalIsLeft: isLeft, originalItem: this.item, + relatedItem, relationship, } as RelationshipIdentifiable; return this.objectUpdatesService.saveRemoveFieldUpdate(this.url,update); @@ -483,10 +485,24 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy { this.relationshipService.isLeftItem(relationship, this.item).pipe( // emit an array containing both the relationship and whether it's the left item, // as we'll need both - map((isLeftItem: boolean) => [relationship, isLeftItem]) - ) + switchMap((isLeftItem: boolean) => { + if (isLeftItem) { + return relationship.rightItem.pipe( + getFirstCompletedRemoteData(), + getRemoteDataPayload(), + map((relatedItem: Item) => [relationship, isLeftItem, relatedItem]), + ); + } else { + return relationship.leftItem.pipe( + getFirstCompletedRemoteData(), + getRemoteDataPayload(), + map((relatedItem: Item) => [relationship, isLeftItem, relatedItem]), + ); + } + }), + ), ), - map(([relationship, isLeftItem]: [Relationship, boolean]) => { + map(([relationship, isLeftItem, relatedItem]: [Relationship, boolean, Item]) => { // turn it into a RelationshipIdentifiable, an const nameVariant = isLeftItem ? relationship.rightwardValue : relationship.leftwardValue; @@ -496,6 +512,7 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy { relationship, originalIsLeft: isLeftItem, originalItem: this.item, + relatedItem: relatedItem, nameVariant, } as RelationshipIdentifiable; }), From 8ee375016f1e7dbd5db5f6d0621848d7aa70a373 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Mon, 3 Jun 2024 12:46:04 +0200 Subject: [PATCH 3/5] 115046: Fixed loading animation not resetting hen closing modal --- .../edit-relationship-list.component.ts | 10 ++++++++-- .../dynamic-lookup-relation-modal.component.spec.ts | 2 -- .../dynamic-lookup-relation-modal.component.ts | 13 +++++++++---- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.ts b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.ts index f57859220a..6cca52ba96 100644 --- a/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.ts +++ b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.ts @@ -26,7 +26,7 @@ import { toArray, concatMap } from 'rxjs/operators'; -import { hasNoValue, hasValue, hasValueOperator } from '../../../../shared/empty.util'; +import { hasNoValue, hasValue, hasValueOperator, isNotEmpty } from '../../../../shared/empty.util'; import { Relationship } from '../../../../core/shared/item-relationships/relationship.model'; import { RelationshipType @@ -278,7 +278,7 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy { } } - this.loading$.next(true); + this.loading$.next(isNotEmpty(modalComp.toAdd) || isNotEmpty(modalComp.toRemove)); // emit the last page again to trigger a fieldupdates refresh this.relationshipsRd$.next(this.relationshipsRd$.getValue()); }); @@ -296,6 +296,7 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy { } else { modalComp.toRemove.push(searchResult); } + this.loading$.next(isNotEmpty(modalComp.toAdd) || isNotEmpty(modalComp.toRemove)); }); }; @@ -369,6 +370,11 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy { modalComp.toAdd = []; modalComp.toRemove = []; + this.loading$.next(false); + }; + + modalComp.closeEv = () => { + this.loading$.next(false); }; this.relatedEntityType$ diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.spec.ts index 9d57296f82..10adffb12a 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.spec.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.spec.ts @@ -7,7 +7,6 @@ import { DsDynamicLookupRelationModalComponent } from './dynamic-lookup-relation import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { SelectableListService } from '../../../../object-list/selectable-list/selectable-list.service'; import { RelationshipDataService } from '../../../../../core/data/relationship-data.service'; -import { RelationshipTypeDataService } from '../../../../../core/data/relationship-type-data.service'; import { Store } from '@ngrx/store'; import { Item } from '../../../../../core/shared/item.model'; import { ItemSearchResult } from '../../../../object-collection/shared/item-search-result.model'; @@ -119,7 +118,6 @@ describe('DsDynamicLookupRelationModalComponent', () => { { provide: RelationshipDataService, useValue: { getNameVariant: () => observableOf(nameVariant) } }, - { provide: RelationshipTypeDataService, useValue: {} }, { provide: RemoteDataBuildService, useValue: rdbService }, { provide: Store, useValue: { diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts index 1881cc1efa..a61432170a 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts @@ -17,7 +17,6 @@ import { UpdateRelationshipNameVariantAction, } from './relationship.actions'; import { RelationshipDataService } from '../../../../../core/data/relationship-data.service'; -import { RelationshipTypeDataService } from '../../../../../core/data/relationship-type-data.service'; import { Store } from '@ngrx/store'; import { AppState } from '../../../../../app.reducer'; import { Context } from '../../../../../core/shared/context.model'; @@ -164,7 +163,6 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy public modal: NgbActiveModal, private selectableListService: SelectableListService, private relationshipService: RelationshipDataService, - private relationshipTypeService: RelationshipTypeDataService, private externalSourceService: ExternalSourceDataService, private lookupRelationService: LookupRelationService, private searchConfigService: SearchConfigurationService, @@ -217,6 +215,7 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy this.toAdd = []; this.toRemove = []; this.modal.close(); + this.closeEv(); } /** @@ -311,13 +310,19 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy /* eslint-disable no-empty,@typescript-eslint/no-empty-function */ /** - * Called when discard button is clicked, emit discard event to parent to conclude functionality + * Called when close button is clicked + */ + closeEv(): void { + } + + /** + * Called when discard button is clicked */ discardEv(): void { } /** - * Called when submit button is clicked, emit submit event to parent to conclude functionality + * Called when submit button is clicked */ submitEv(): void { } From 6d1e6f90fcad329a8963fef73c2a6ad57b2308bc Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Mon, 3 Jun 2024 13:50:42 +0200 Subject: [PATCH 4/5] 115046: Fixed issue where relationsToItems would never emit when an empty array was passed to it --- .../shared/item-relationships-utils.ts | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/app/item-page/simple/item-types/shared/item-relationships-utils.ts b/src/app/item-page/simple/item-types/shared/item-relationships-utils.ts index b4c3da2cdc..a3531bda05 100644 --- a/src/app/item-page/simple/item-types/shared/item-relationships-utils.ts +++ b/src/app/item-page/simple/item-types/shared/item-relationships-utils.ts @@ -1,4 +1,4 @@ -import { combineLatest as observableCombineLatest, Observable, zip as observableZip } from 'rxjs'; +import { combineLatest as observableCombineLatest, Observable, of as observableOf, zip as observableZip } from 'rxjs'; import { distinctUntilChanged, map, mergeMap, switchMap } from 'rxjs/operators'; import { PaginatedList } from '../../../../core/data/paginated-list.model'; import { RemoteData } from '../../../../core/data/remote-data'; @@ -46,17 +46,19 @@ export const compareArraysUsingIds = () => /** * Operator for turning a list of relationships into a list of the relevant items * @param {string} thisId The item's id of which the relations belong to - * @returns {(source: Observable) => Observable} */ -export const relationsToItems = (thisId: string) => +export const relationsToItems = (thisId: string): (source: Observable) => Observable => (source: Observable): Observable => source.pipe( - mergeMap((rels: Relationship[]) => - observableZip( - ...rels.map((rel: Relationship) => observableCombineLatest(rel.leftItem, rel.rightItem)) - ) - ), - map((arr) => + mergeMap((relationships: Relationship[]) => { + if (relationships.length === 0) { + return observableOf([]); + } + return observableZip( + ...relationships.map((rel: Relationship) => observableCombineLatest([rel.leftItem, rel.rightItem])), + ); + }), + map((arr: [RemoteData, RemoteData][]) => arr .filter(([leftItem, rightItem]) => leftItem.hasSucceeded && rightItem.hasSucceeded) .map(([leftItem, rightItem]) => { @@ -75,9 +77,8 @@ export const relationsToItems = (thisId: string) => * Operator for turning a paginated list of relationships into a paginated list of the relevant items * The result is wrapped in the original RemoteData and PaginatedList * @param {string} thisId The item's id of which the relations belong to - * @returns {(source: Observable) => Observable} */ -export const paginatedRelationsToItems = (thisId: string) => +export const paginatedRelationsToItems = (thisId: string): (source: Observable>>) => Observable>> => (source: Observable>>): Observable>> => source.pipe( getFirstSucceededRemoteData(), From 813db7cc9facd2114a62b9d84ceeb9d97940c427 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 7 Jun 2024 13:02:14 +0200 Subject: [PATCH 5/5] 115046: Fixed same type entity relationships with same leftward/rightward type displaying twice --- .../edit-item-relationships.service.spec.ts | 27 ++++++++++++++++--- .../edit-item-relationships.service.ts | 12 ++++++--- ...t-relationship-list-wrapper.component.html | 5 ++-- ...elationship-list-wrapper.component.spec.ts | 9 ++++--- ...dit-relationship-list-wrapper.component.ts | 7 ++--- .../edit-relationship.component.ts | 8 +++--- 6 files changed, 46 insertions(+), 22 deletions(-) diff --git a/src/app/item-page/edit-item-page/item-relationships/edit-item-relationships.service.spec.ts b/src/app/item-page/edit-item-page/item-relationships/edit-item-relationships.service.spec.ts index ef66f91691..ebc2a82d84 100644 --- a/src/app/item-page/edit-item-page/item-relationships/edit-item-relationships.service.spec.ts +++ b/src/app/item-page/edit-item-page/item-relationships/edit-item-relationships.service.spec.ts @@ -318,23 +318,42 @@ describe('EditItemRelationshipsService', () => { const relationshipType = Object.assign(new RelationshipType(), { leftType: createSuccessfulRemoteDataObject$({id: 'sameType'}), rightType: createSuccessfulRemoteDataObject$({id:'sameType'}), + leftwardType: 'isDepartmentOfDivision', + rightwardType: 'isDivisionOfDepartment', }); const itemType = Object.assign(new ItemType(), {id: 'sameType'} ); - const result = service.relationshipMatchesBothSameTypes(relationshipType, itemType); + const result = service.shouldDisplayBothRelationshipSides(relationshipType, itemType); result.subscribe((resultValue) => { expect(resultValue).toBeTrue(); done(); }); }); + it('should return false if both left and right type of the relationship type are the same and match the provided itemtype but the leftwardType & rightwardType is identical', (done) => { + const relationshipType = Object.assign(new RelationshipType(), { + leftType: createSuccessfulRemoteDataObject$({ id: 'sameType' }), + rightType: createSuccessfulRemoteDataObject$({ id: 'sameType' }), + leftwardType: 'isOrgUnitOfOrgUnit', + rightwardType: 'isOrgUnitOfOrgUnit', + }); + const itemType = Object.assign(new ItemType(), { id: 'sameType' }); + + const result = service.shouldDisplayBothRelationshipSides(relationshipType, itemType); + result.subscribe((resultValue) => { + expect(resultValue).toBeFalse(); + done(); + }); + }); it('should return false if both left and right type of the relationship type are the same and do not match the provided itemtype', (done) => { const relationshipType = Object.assign(new RelationshipType(), { leftType: createSuccessfulRemoteDataObject$({id: 'sameType'}), rightType: createSuccessfulRemoteDataObject$({id: 'sameType'}), + leftwardType: 'isDepartmentOfDivision', + rightwardType: 'isDivisionOfDepartment', }); const itemType = Object.assign(new ItemType(), {id: 'something-else'} ); - const result = service.relationshipMatchesBothSameTypes(relationshipType, itemType); + const result = service.shouldDisplayBothRelationshipSides(relationshipType, itemType); result.subscribe((resultValue) => { expect(resultValue).toBeFalse(); done(); @@ -344,10 +363,12 @@ describe('EditItemRelationshipsService', () => { const relationshipType = Object.assign(new RelationshipType(), { leftType: createSuccessfulRemoteDataObject$({id: 'leftType'}), rightType: createSuccessfulRemoteDataObject$({id: 'rightType'}), + leftwardType: 'isAuthorOfPublication', + rightwardType: 'isPublicationOfAuthor', }); const itemType = Object.assign(new ItemType(), {id: 'leftType'} ); - const result = service.relationshipMatchesBothSameTypes(relationshipType, itemType); + const result = service.shouldDisplayBothRelationshipSides(relationshipType, itemType); result.subscribe((resultValue) => { expect(resultValue).toBeFalse(); done(); diff --git a/src/app/item-page/edit-item-page/item-relationships/edit-item-relationships.service.ts b/src/app/item-page/edit-item-page/item-relationships/edit-item-relationships.service.ts index a3d45d6c70..b9f3738e7b 100644 --- a/src/app/item-page/edit-item-page/item-relationships/edit-item-relationships.service.ts +++ b/src/app/item-page/edit-item-page/item-relationships/edit-item-relationships.service.ts @@ -200,11 +200,17 @@ export class EditItemRelationshipsService { ); } - relationshipMatchesBothSameTypes(relationshipType: RelationshipType, itemType: ItemType): Observable { + /** + * Whether both side of the relationship need to be displayed on the edit relationship page or not. + * + * @param relationshipType The relationship type + * @param itemType The item type + */ + shouldDisplayBothRelationshipSides(relationshipType: RelationshipType, itemType: ItemType): Observable { return this.getRelationshipLeftAndRightType(relationshipType).pipe( map(([leftType, rightType]: [ItemType, ItemType]) => { - return leftType.id === itemType.id && rightType.id === itemType.id; - }) + return leftType.id === itemType.id && rightType.id === itemType.id && relationshipType.leftwardType !== relationshipType.rightwardType; + }), ); } diff --git a/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list-wrapper/edit-relationship-list-wrapper.component.html b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list-wrapper/edit-relationship-list-wrapper.component.html index e6c1aa25f7..ed7fb190f2 100644 --- a/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list-wrapper/edit-relationship-list-wrapper.component.html +++ b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list-wrapper/edit-relationship-list-wrapper.component.html @@ -1,4 +1,4 @@ - + - + { let editItemRelationshipsService: EditItemRelationshipsService; @@ -36,7 +37,7 @@ describe('EditRelationshipListWrapperComponent', () => { editItemRelationshipsService = jasmine.createSpyObj('editItemRelationshipsService', { isProvidedItemTypeLeftType: observableOf(true), - relationshipMatchesBothSameTypes: observableOf(false) + shouldDisplayBothRelationshipSides: observableOf(false), }); @@ -69,10 +70,10 @@ describe('EditRelationshipListWrapperComponent', () => { }); it('should set currentItemIsLeftItem$ and bothItemsMatchType$ based on the provided relationshipType, itemType and item', () => { expect(editItemRelationshipsService.isProvidedItemTypeLeftType).toHaveBeenCalledWith(relationshipType, leftType, item); - expect(editItemRelationshipsService.relationshipMatchesBothSameTypes).toHaveBeenCalledWith(relationshipType, leftType); + expect(editItemRelationshipsService.shouldDisplayBothRelationshipSides).toHaveBeenCalledWith(relationshipType, leftType); expect(comp.currentItemIsLeftItem$.getValue()).toEqual(true); - expect(comp.bothItemsMatchType$.getValue()).toEqual(false); + expect(comp.shouldDisplayBothRelationshipSides$).toBeObservable(cold('(a|)', { a: false })); }); }); @@ -96,7 +97,7 @@ describe('EditRelationshipListWrapperComponent', () => { describe('when the current item is both left and right', () => { it('should render two relationship list sections', () => { - (editItemRelationshipsService.relationshipMatchesBothSameTypes as jasmine.Spy).and.returnValue(observableOf(true)); + (editItemRelationshipsService.shouldDisplayBothRelationshipSides as jasmine.Spy).and.returnValue(observableOf(true)); comp.ngOnInit(); fixture.detectChanges(); diff --git a/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list-wrapper/edit-relationship-list-wrapper.component.ts b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list-wrapper/edit-relationship-list-wrapper.component.ts index 8231eaa542..8ff408cb79 100644 --- a/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list-wrapper/edit-relationship-list-wrapper.component.ts +++ b/src/app/item-page/edit-item-page/item-relationships/edit-relationship-list-wrapper/edit-relationship-list-wrapper.component.ts @@ -56,7 +56,7 @@ export class EditRelationshipListWrapperComponent implements OnInit, OnDestroy { isRightItem$ = new BehaviorSubject(false); - bothItemsMatchType$: BehaviorSubject = new BehaviorSubject(undefined); + shouldDisplayBothRelationshipSides$: Observable; /** * Array to track all subscriptions and unsubscribe them onDestroy @@ -76,10 +76,7 @@ export class EditRelationshipListWrapperComponent implements OnInit, OnDestroy { this.currentItemIsLeftItem$.next(nextValue); })); - this.subs.push(this.editItemRelationshipsService.relationshipMatchesBothSameTypes(this.relationshipType, this.itemType) - .subscribe((nextValue: boolean) => { - this.bothItemsMatchType$.next(nextValue); - })); + this.shouldDisplayBothRelationshipSides$ = this.editItemRelationshipsService.shouldDisplayBothRelationshipSides(this.relationshipType, this.itemType); } diff --git a/src/app/item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.ts b/src/app/item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.ts index cf0c610f8f..b56fc24ebb 100644 --- a/src/app/item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.ts +++ b/src/app/item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.ts @@ -90,13 +90,11 @@ export class EditRelationshipComponent implements OnChanges { getRemoteDataPayload(), filter((item: Item) => hasValue(item) && isNotEmpty(item.uuid)) ); - this.relatedItem$ = observableCombineLatest( + this.relatedItem$ = observableCombineLatest([ this.leftItem$, this.rightItem$, - ).pipe( - map((items: Item[]) => - items.find((item) => item.uuid !== this.editItem.uuid) - ) + ]).pipe( + map(([leftItem, rightItem]: [Item, Item]) => leftItem.uuid === this.editItem.uuid ? rightItem : leftItem), ); } else { this.relatedItem$ = of(this.update.relatedItem);