diff --git a/src/app/core/data/relationship-data.service.ts b/src/app/core/data/relationship-data.service.ts index 17d0fa6634..f0164b839d 100644 --- a/src/app/core/data/relationship-data.service.ts +++ b/src/app/core/data/relationship-data.service.ts @@ -155,8 +155,11 @@ export class RelationshipDataService extends IdentifiableDataService> { + deleteRelationship(id: string, copyVirtualMetadata: string, shouldRefresh = true): Observable> { return this.getRelationshipEndpoint(id).pipe( isNotEmptyOperator(), take(1), @@ -167,7 +170,11 @@ export class RelationshipDataService extends IdentifiableDataService this.rdbService.buildFromRequestUUID(restRequest.uuid)), getFirstCompletedRemoteData(), - tap(() => this.refreshRelationshipItemsInCacheByRelationship(id)), + tap(() => { + if (shouldRefresh) { + this.refreshRelationshipItemsInCacheByRelationship(id); + } + }), ); } @@ -178,8 +185,11 @@ export class RelationshipDataService extends IdentifiableDataService> { + addRelationship(typeId: string, item1: Item, item2: Item, leftwardValue?: string, rightwardValue?: string, shouldRefresh = true): Observable> { const options: HttpOptions = Object.create({}); let headers = new HttpHeaders(); headers = headers.append('Content-Type', 'text/uri-list'); @@ -194,8 +204,12 @@ export class RelationshipDataService extends IdentifiableDataService this.rdbService.buildFromRequestUUID(restRequest.uuid)), getFirstCompletedRemoteData(), - tap(() => this.refreshRelationshipItemsInCache(item1)), - tap(() => this.refreshRelationshipItemsInCache(item2)), + tap(() => { + if (shouldRefresh) { + this.refreshRelationshipItemsInCache(item1); + this.refreshRelationshipItemsInCache(item2); + } + }), ) as Observable>; } diff --git a/src/app/item-page/edit-item-page/item-relationships/item-relationships.component.ts b/src/app/item-page/edit-item-page/item-relationships/item-relationships.component.ts index ea9b571cd6..3cf6601f0e 100644 --- a/src/app/item-page/edit-item-page/item-relationships/item-relationships.component.ts +++ b/src/app/item-page/edit-item-page/item-relationships/item-relationships.component.ts @@ -17,16 +17,17 @@ import { TranslateService, } from '@ngx-translate/core'; import { + BehaviorSubject, combineLatest as observableCombineLatest, + EMPTY, Observable, - of as observableOf, - zip as observableZip, } from 'rxjs'; import { + concatMap, map, - startWith, switchMap, take, + toArray, } from 'rxjs/operators'; import { ObjectCacheService } from '../../../core/cache/object-cache.service'; @@ -91,7 +92,7 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent { /** * The item's entity type as an observable */ - entityType$: Observable; + entityType$: BehaviorSubject = new BehaviorSubject(undefined); constructor( public itemService: ItemDataService, @@ -123,13 +124,13 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent { map((relationshipTypes: PaginatedList) => relationshipTypes.page), ); - this.entityType$ = this.entityTypeService.getEntityTypeByLabel(label).pipe( + this.entityTypeService.getEntityTypeByLabel(label).pipe( getFirstSucceededRemoteData(), getRemoteDataPayload(), - ); + ).subscribe((type) => this.entityType$.next(type)); } else { - this.entityType$ = observableOf(undefined); + this.entityType$.next(undefined); } } @@ -147,8 +148,7 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent { public submit(): void { // Get all the relationships that should be removed - const removedRelationshipIDs$: Observable = this.relationshipService.getItemRelationshipsArray(this.item).pipe( - startWith([]), + const removeUpdates$: Observable = this.relationshipService.getItemRelationshipsArray(this.item).pipe( map((relationships: Relationship[]) => relationships.map((relationship) => Object.assign(new Relationship(), relationship, { uuid: relationship.id }), )), @@ -157,84 +157,113 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent { }), map((fieldUpdates: FieldUpdates) => Object.values(fieldUpdates) - .filter((fieldUpdate: FieldUpdate) => fieldUpdate.changeType === FieldChangeType.REMOVE) - .map((fieldUpdate: FieldUpdate) => fieldUpdate.field as DeleteRelationship), + .filter((fieldUpdate: FieldUpdate) => fieldUpdate.changeType === FieldChangeType.REMOVE), ), + take(1), ); - const addRelatedItems$: Observable = this.objectUpdatesService.getFieldUpdates(this.url, []).pipe( + const addUpdates$: Observable = this.objectUpdatesService.getFieldUpdates(this.url, []).pipe( map((fieldUpdates: FieldUpdates) => Object.values(fieldUpdates) .filter((fieldUpdate: FieldUpdate) => hasValue(fieldUpdate)) - .filter((fieldUpdate: FieldUpdate) => fieldUpdate.changeType === FieldChangeType.ADD) - .map((fieldUpdate: FieldUpdate) => fieldUpdate.field as RelationshipIdentifiable), + .filter((fieldUpdate: FieldUpdate) => fieldUpdate.changeType === FieldChangeType.ADD), ), + take(1), ); - observableCombineLatest( - removedRelationshipIDs$, - addRelatedItems$, - ).pipe( + observableCombineLatest([ + removeUpdates$, + addUpdates$, + ]).pipe( take(1), - ).subscribe(([removeRelationshipIDs, addRelatedItems]) => { - const actions = [ - this.deleteRelationships(removeRelationshipIDs), - this.addRelationships(addRelatedItems), - ]; - actions.forEach((action) => - action.subscribe((response) => { - if (response.length > 0) { - this.initializeOriginalFields(); - this.cdr.detectChanges(); - this.displayNotifications(response); - this.modalService.dismissAll(); - } - }), - ); + switchMap(([removeUpdates, addUpdates]) => [...removeUpdates, ...addUpdates]), + concatMap((update: FieldUpdate) => { + if (update.changeType === FieldChangeType.REMOVE) { + return this.deleteRelationship(update.field as DeleteRelationship).pipe(take(1)); + } else if (update.changeType === FieldChangeType.ADD) { + return this.addRelationship(update.field as RelationshipIdentifiable).pipe( + take(1), + switchMap((relationshipRD: RemoteData) => { + if (relationshipRD.hasSucceeded) { + // Set the newly related item to stale, so its relationships will update to include + // the new one. Only set the current item to stale at the very end so we only do it + // once + const { leftItem, rightItem } = relationshipRD.payload._links; + if (leftItem.href === this.item.self) { + return this.itemService.invalidateByHref(rightItem.href).pipe( + // when it's invalidated, emit the original relationshipRD for use in the pipe below + map(() => relationshipRD), + ); + } else { + return this.itemService.invalidateByHref(leftItem.href).pipe( + // when it's invalidated, emit the original relationshipRD for use in the pipe below + map(() => relationshipRD), + ); + } + } else { + return [relationshipRD]; + } + }), + ); + } else { + return EMPTY; + } + }), + toArray(), + switchMap((responses) => { + // once all relationships are made and all related items have been invalidated, invalidate + // the current item + return this.itemService.invalidateByHref(this.item.self).pipe( + map(() => responses), + ); + }), + ).subscribe((responses) => { + if (responses.length > 0) { + this.initializeOriginalFields(); + this.displayNotifications(responses); + this.modalService.dismissAll(); + } }); } - deleteRelationships(deleteRelationshipIDs: DeleteRelationship[]): Observable[]> { - return observableZip(...deleteRelationshipIDs.map((deleteRelationship) => { - let copyVirtualMetadata: string; - if (deleteRelationship.keepLeftVirtualMetadata && deleteRelationship.keepRightVirtualMetadata) { - copyVirtualMetadata = 'all'; - } else if (deleteRelationship.keepLeftVirtualMetadata) { - copyVirtualMetadata = 'left'; - } else if (deleteRelationship.keepRightVirtualMetadata) { - copyVirtualMetadata = 'right'; - } else { - copyVirtualMetadata = 'none'; - } - return this.relationshipService.deleteRelationship(deleteRelationship.uuid, copyVirtualMetadata); - }, - )); + deleteRelationship(deleteRelationship: DeleteRelationship): Observable> { + let copyVirtualMetadata: string; + if (deleteRelationship.keepLeftVirtualMetadata && deleteRelationship.keepRightVirtualMetadata) { + copyVirtualMetadata = 'all'; + } else if (deleteRelationship.keepLeftVirtualMetadata) { + copyVirtualMetadata = 'left'; + } else if (deleteRelationship.keepRightVirtualMetadata) { + copyVirtualMetadata = 'right'; + } else { + copyVirtualMetadata = 'none'; + } + + return this.relationshipService.deleteRelationship(deleteRelationship.uuid, copyVirtualMetadata, false); } - addRelationships(addRelatedItems: RelationshipIdentifiable[]): Observable[]> { - return observableZip(...addRelatedItems.map((addRelationship) => - this.entityType$.pipe( - switchMap((entityType) => this.entityTypeService.isLeftType(addRelationship.type, entityType)), - switchMap((isLeftType) => { - let leftItem: Item; - let rightItem: Item; - let leftwardValue: string; - let rightwardValue: string; - if (isLeftType) { - leftItem = this.item; - rightItem = addRelationship.relatedItem; - leftwardValue = null; - rightwardValue = addRelationship.nameVariant; - } else { - leftItem = addRelationship.relatedItem; - rightItem = this.item; - leftwardValue = addRelationship.nameVariant; - rightwardValue = null; - } - return this.relationshipService.addRelationship(addRelationship.type.id, leftItem, rightItem, leftwardValue, rightwardValue); - }), - ), - )); + addRelationship(addRelationship: RelationshipIdentifiable): Observable> { + return this.entityType$.pipe( + switchMap((entityType) => this.entityTypeService.isLeftType(addRelationship.type, entityType)), + switchMap((isLeftType) => { + let leftItem: Item; + let rightItem: Item; + let leftwardValue: string; + let rightwardValue: string; + if (isLeftType) { + leftItem = this.item; + rightItem = addRelationship.relatedItem; + leftwardValue = null; + rightwardValue = addRelationship.nameVariant; + } else { + leftItem = addRelationship.relatedItem; + rightItem = this.item; + leftwardValue = addRelationship.nameVariant; + rightwardValue = null; + } + return this.relationshipService.addRelationship(addRelationship.type.id, leftItem, rightItem, leftwardValue, rightwardValue, false); + }), + ); + } /**