diff --git a/src/app/app.component.ts b/src/app/app.component.ts index cdf45f50a8..b87073c034 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -32,6 +32,7 @@ import { Observable, } from 'rxjs'; import { + delay, distinctUntilChanged, take, withLatestFrom, @@ -136,7 +137,10 @@ export class AppComponent implements OnInit, AfterViewInit { } ngAfterViewInit() { - this.router.events.subscribe((event) => { + this.router.events.pipe( + // delay(0) to prevent "Expression has changed after it was checked" errors + delay(0), + ).subscribe((event) => { if (event instanceof NavigationStart) { distinctNext(this.isRouteLoading$, true); } else if ( diff --git a/src/app/core/data/object-updates/object-updates.reducer.ts b/src/app/core/data/object-updates/object-updates.reducer.ts index e014889850..cadae9ae83 100644 --- a/src/app/core/data/object-updates/object-updates.reducer.ts +++ b/src/app/core/data/object-updates/object-updates.reducer.ts @@ -61,6 +61,8 @@ export interface VirtualMetadataSource { export interface RelationshipIdentifiable extends Identifiable { nameVariant?: string; + originalItem: Item; + originalIsLeft: boolean relatedItem: Item; relationship: Relationship; type: RelationshipType; diff --git a/src/app/core/data/relationship-data.service.ts b/src/app/core/data/relationship-data.service.ts index f0164b839d..43c56f1a32 100644 --- a/src/app/core/data/relationship-data.service.ts +++ b/src/app/core/data/relationship-data.service.ts @@ -350,7 +350,13 @@ export class RelationshipDataService extends IdentifiableDataService { + let service: EditItemRelationshipsService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(EditItemRelationshipsService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); 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 new file mode 100644 index 0000000000..c2dab99756 --- /dev/null +++ b/src/app/item-page/edit-item-page/item-relationships/edit-item-relationships.service.ts @@ -0,0 +1,196 @@ +import { Injectable } from '@angular/core'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { TranslateService } from '@ngx-translate/core'; +import { + EMPTY, + Observable, +} from 'rxjs'; +import { + concatMap, + map, + switchMap, + take, + toArray, +} from 'rxjs/operators'; + +import { EntityTypeDataService } from '../../../core/data/entity-type-data.service'; +import { ItemDataService } from '../../../core/data/item-data.service'; +import { FieldChangeType } from '../../../core/data/object-updates/field-change-type.model'; +import { FieldUpdate } from '../../../core/data/object-updates/field-update.model'; +import { FieldUpdates } from '../../../core/data/object-updates/field-updates.model'; +import { + DeleteRelationship, + RelationshipIdentifiable, +} from '../../../core/data/object-updates/object-updates.reducer'; +import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service'; +import { RelationshipDataService } from '../../../core/data/relationship-data.service'; +import { RemoteData } from '../../../core/data/remote-data'; +import { Item } from '../../../core/shared/item.model'; +import { Relationship } from '../../../core/shared/item-relationships/relationship.model'; +import { NoContent } from '../../../core/shared/NoContent.model'; +import { hasValue } from '../../../shared/empty.util'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; + +@Injectable({ + providedIn: 'root', +}) +export class EditItemRelationshipsService { + public notificationsPrefix = 'static-pages.form.notification'; + + constructor( + public itemService: ItemDataService, + public objectUpdatesService: ObjectUpdatesService, + public notificationsService: NotificationsService, + protected modalService: NgbModal, + public relationshipService: RelationshipDataService, + public entityTypeService: EntityTypeDataService, + public translateService: TranslateService, + ) { } + + + /** + * Resolve the currently selected related items back to relationships and send a delete request for each of the relationships found + * Make sure the lists are refreshed afterwards and notifications are sent for success and errors + */ + public submit(item: Item, url: string): void { + this.objectUpdatesService.getFieldUpdates(url, [], true).pipe( + map((fieldUpdates: FieldUpdates) => + Object.values(fieldUpdates) + .filter((fieldUpdate: FieldUpdate) => hasValue(fieldUpdate)) + .filter((fieldUpdate: FieldUpdate) => fieldUpdate.changeType === FieldChangeType.ADD || fieldUpdate.changeType === FieldChangeType.REMOVE), + ), + take(1), + // emit each update in the array separately + switchMap((updates) => updates), + // 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)); + } 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 === 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(item.self).pipe( + map(() => responses), + ); + }), + ).subscribe((responses) => { + if (responses.length > 0) { + this.initializeOriginalFields(item, url); + this.displayNotifications(responses); + this.modalService.dismissAll(); + } + }); + } + + /** + * Sends all initial values of this item to the object updates service + */ + public initializeOriginalFields(item: Item, url: string) { + return this.relationshipService.getRelatedItems(item).pipe( + take(1), + ).subscribe((items: Item[]) => { + this.objectUpdatesService.initialize(url, items, item.lastModified); + }); + } + + 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); + } + + addRelationship(addRelationship: RelationshipIdentifiable): Observable> { + let leftItem: Item; + let rightItem: Item; + let leftwardValue: string; + let rightwardValue: string; + if (addRelationship.originalIsLeft) { + leftItem = addRelationship.originalItem; + rightItem = addRelationship.relatedItem; + leftwardValue = null; + rightwardValue = addRelationship.nameVariant; + } else { + leftItem = addRelationship.relatedItem; + rightItem = addRelationship.originalItem; + leftwardValue = addRelationship.nameVariant; + rightwardValue = null; + } + return this.relationshipService.addRelationship(addRelationship.type.id, leftItem, rightItem, leftwardValue, rightwardValue, false); + } + + /** + * Display notifications + * - Error notification for each failed response with their message + * - Success notification in case there's at least one successful response + * @param responses + */ + displayNotifications(responses: RemoteData[]) { + const failedResponses = responses.filter((response: RemoteData) => response.hasFailed); + const successfulResponses = responses.filter((response: RemoteData) => response.hasSucceeded); + + failedResponses.forEach((response: RemoteData) => { + this.notificationsService.error(this.getNotificationTitle('failed'), response.errorMessage); + }); + if (successfulResponses.length > 0) { + this.notificationsService.success(this.getNotificationTitle('saved'), this.getNotificationContent('saved')); + } + } + + + + /** + * Get translated notification title + * @param key + */ + getNotificationTitle(key: string) { + return this.translateService.instant(this.notificationsPrefix + key + '.title'); + } + + /** + * Get translated notification content + * @param key + */ + getNotificationContent(key: string) { + return this.translateService.instant(this.notificationsPrefix + key + '.content'); + + } +} 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 d9029a7af4..740d95e473 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 @@ -21,11 +21,13 @@ import { TranslateModule } from '@ngx-translate/core'; import { BehaviorSubject, combineLatest as observableCombineLatest, + EMPTY, from as observableFrom, Observable, Subscription, } from 'rxjs'; import { + concatMap, defaultIfEmpty, map, mergeMap, @@ -78,6 +80,7 @@ import { FollowLinkConfig } from '../../../../shared/utils/follow-link-config.mo import { ObjectValuesPipe } from '../../../../shared/utils/object-values-pipe'; import { itemLinksToFollow } from '../../../../shared/utils/relation-query.utils'; import { VarDirective } from '../../../../shared/utils/var.directive'; +import { EditItemRelationshipsService } from '../edit-item-relationships.service'; import { EditRelationshipComponent } from '../edit-relationship/edit-relationship.component'; @Component({ @@ -141,7 +144,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$: Observable; + private currentItemIsLeftItem$: BehaviorSubject = new BehaviorSubject(undefined); private relatedEntityType$: Observable; @@ -206,6 +209,7 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy { protected modalService: NgbModal, protected paginationService: PaginationService, protected selectableListService: SelectableListService, + protected editItemRelationshipsService: EditItemRelationshipsService, @Inject(APP_CONFIG) protected appConfig: AppConfig, ) { this.fetchThumbnail = this.appConfig.browseBy.showThumbnails; @@ -240,7 +244,6 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy { * Open the dynamic lookup modal to search for items to add as relationships */ openLookup() { - this.modalRef = this.modalService.open(DsDynamicLookupRelationModalComponent, { size: 'lg', }); @@ -306,51 +309,59 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy { modalComp.submitEv = () => { - - const subscriptions = []; - - modalComp.toAdd.forEach((searchResult: SearchResult) => { - const relatedItem = searchResult.indexableObject; - subscriptions.push(this.relationshipService.getNameVariant(this.listId, relatedItem.uuid).pipe( - map((nameVariant) => { - const update = { - uuid: this.relationshipType.id + '-' + searchResult.indexableObject.uuid, - nameVariant, - type: this.relationshipType, - relatedItem, - } as RelationshipIdentifiable; - this.objectUpdatesService.saveAddFieldUpdate(this.url, update); - return update; - }), - )); - }); - - modalComp.toRemove.forEach( (searchResult) => { - subscriptions.push(this.relationshipService.getNameVariant(this.listId, searchResult.indexableObjectuuid).pipe( - switchMap((nameVariant) => { - return this.getRelationFromId(searchResult.indexableObject).pipe( - map( (relationship: Relationship) => { + modalComp.isPending = true; + const isLeft = this.currentItemIsLeftItem$.getValue(); + const addOperations = modalComp.toAdd.map((searchResult: any) => ({ type: 'add', searchResult })); + const removeOperations = modalComp.toRemove.map((searchResult: any) => ({ type: 'remove', searchResult })); + observableFrom([...addOperations, ...removeOperations]).pipe( + concatMap(({ type, searchResult }: { type: string, searchResult: any }) => { + if (type === 'add') { + const relatedItem = searchResult.indexableObject; + return this.relationshipService.getNameVariant(this.listId, relatedItem.uuid).pipe( + map((nameVariant) => { const update = { - uuid: relationship.id, + uuid: this.relationshipType.id + '-' + searchResult.indexableObject.uuid, nameVariant, type: this.relationshipType, - relationship, + originalIsLeft: isLeft, + originalItem: this.item, + relatedItem, } as RelationshipIdentifiable; - this.objectUpdatesService.saveRemoveFieldUpdate(this.url,update); + this.objectUpdatesService.saveAddFieldUpdate(this.url, update); return update; }), + take(1), ); - }), - )); - }); - - observableCombineLatest(subscriptions).subscribe( (res) => { - // Wait until the states changes since there are multiple items - setTimeout( () => { + } else if (type === 'remove') { + return this.relationshipService.getNameVariant(this.listId, searchResult.indexableObjectuuid).pipe( + switchMap((nameVariant) => { + return this.getRelationFromId(searchResult.indexableObject).pipe( + map( (relationship: Relationship) => { + const update = { + uuid: relationship.id, + nameVariant, + type: this.relationshipType, + originalIsLeft: isLeft, + originalItem: this.item, + relationship, + } as RelationshipIdentifiable; + this.objectUpdatesService.saveRemoveFieldUpdate(this.url,update); + return update; + }), + ); + }), + take(1), + ); + } else { + return EMPTY; + } + }), + toArray(), + ).subscribe({ + complete: () => { + this.editItemRelationshipsService.submit(this.item, this.url); this.submit.emit(); - },1000); - - modalComp.isPending = true; + }, }); }; @@ -384,27 +395,12 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy { } getRelationFromId(relatedItem) { - return this.currentItemIsLeftItem$.pipe( - take(1), - switchMap( isLeft => { - let apiCall; - if (isLeft) { - apiCall = this.relationshipService.searchByItemsAndType( this.relationshipType.id, this.item.uuid, this.relationshipType.leftwardType ,[relatedItem.id] ).pipe( - getFirstSucceededRemoteData(), - getRemoteDataPayload(), - ); - } else { - apiCall = this.relationshipService.searchByItemsAndType( this.relationshipType.id, this.item.uuid, this.relationshipType.rightwardType ,[relatedItem.id] ).pipe( - getFirstSucceededRemoteData(), - getRemoteDataPayload(), - ); - } - - return apiCall.pipe( - map( (res: PaginatedList) => res.page[0]), - ); - }, - )); + const relationshipLabel = this.currentItemIsLeftItem$.getValue() ? this.relationshipType.leftwardType : this.relationshipType.rightwardType; + return this.relationshipService.searchByItemsAndType( this.relationshipType.id, this.item.uuid, relationshipLabel ,[relatedItem.id] ).pipe( + getFirstSucceededRemoteData(), + getRemoteDataPayload(), + map( (res: PaginatedList) => res.page[0]), + ); } @@ -469,7 +465,6 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy { } ngOnInit(): void { - // store the left and right type of the relationship in a single observable this.relationshipLeftAndRightType$ = observableCombineLatest([ this.relationshipType.leftType, @@ -490,7 +485,7 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy { (relatedEntityType) => this.listId = `edit-relationship-${this.itemType.id}-${relatedEntityType.id}`, ); - this.currentItemIsLeftItem$ = this.relationshipLeftAndRightType$.pipe( + this.subs.push(this.relationshipLeftAndRightType$.pipe( map(([leftType, rightType]: [ItemType, ItemType]) => { if (leftType.id === this.itemType.id) { return true; @@ -504,7 +499,9 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy { 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); + })); this.getRelationshipMessageKey$ = observableCombineLatest( this.getLabel(), @@ -547,19 +544,20 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy { currentPagination$, this.currentItemIsLeftItem$, ]).pipe( - switchMap(([currentPagination, currentItemIsLeftItem]: [PaginationComponentOptions, boolean]) => + switchMap(([currentPagination, currentItemIsLeftItem]: [PaginationComponentOptions, boolean]) => { // get the relationships for the current item, relationshiptype and page - this.relationshipService.getItemRelationshipsByLabel( + return this.relationshipService.getItemRelationshipsByLabel( this.item, currentItemIsLeftItem ? this.relationshipType.leftwardType : this.relationshipType.rightwardType, { elementsPerPage: currentPagination.pageSize, currentPage: currentPagination.currentPage, }, - false, + true, true, ...linksToFollow, - )), + ); + }), ).subscribe((rd: RemoteData>) => { this.relationshipsRd$.next(rd); }), @@ -595,6 +593,8 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy { uuid: relationship.id, type: this.relationshipType, relationship, + originalIsLeft: isLeftItem, + originalItem: this.item, nameVariant, } as RelationshipIdentifiable; }), 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 5f213327e3..97cfb7a16b 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 @@ -167,9 +167,9 @@ export class EditRelationshipComponent implements OnChanges { ) as DeleteRelationship; }), take(1), - ).subscribe((deleteRelationship: DeleteRelationship) => - this.objectUpdatesService.saveRemoveFieldUpdate(this.url, deleteRelationship), - ); + ).subscribe((deleteRelationship: DeleteRelationship) => { + this.objectUpdatesService.saveRemoveFieldUpdate(this.url, deleteRelationship); + }); } openVirtualMetadataModal(content: any) { 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 c3556962d4..e413b5f8ad 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 @@ -27,8 +27,7 @@ [item]="item" [itemType]="entityType$ | async" [relationshipType]="relationshipType" - [hasChanges] = hasChanges() - (submit) = submit() + [hasChanges]="hasChanges()" > 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 3cf6601f0e..6c013c8d60 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 @@ -18,49 +18,30 @@ import { } from '@ngx-translate/core'; import { BehaviorSubject, - combineLatest as observableCombineLatest, - EMPTY, Observable, } from 'rxjs'; -import { - concatMap, - map, - switchMap, - take, - toArray, -} from 'rxjs/operators'; +import { map } from 'rxjs/operators'; import { ObjectCacheService } from '../../../core/cache/object-cache.service'; import { EntityTypeDataService } from '../../../core/data/entity-type-data.service'; import { ItemDataService } from '../../../core/data/item-data.service'; -import { FieldChangeType } from '../../../core/data/object-updates/field-change-type.model'; -import { FieldUpdate } from '../../../core/data/object-updates/field-update.model'; -import { FieldUpdates } from '../../../core/data/object-updates/field-updates.model'; -import { - DeleteRelationship, - RelationshipIdentifiable, -} from '../../../core/data/object-updates/object-updates.reducer'; import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service'; import { PaginatedList } from '../../../core/data/paginated-list.model'; import { RelationshipDataService } from '../../../core/data/relationship-data.service'; import { RelationshipTypeDataService } from '../../../core/data/relationship-type-data.service'; -import { RemoteData } from '../../../core/data/remote-data'; import { RequestService } from '../../../core/data/request.service'; -import { Item } from '../../../core/shared/item.model'; import { ItemType } from '../../../core/shared/item-relationships/item-type.model'; -import { Relationship } from '../../../core/shared/item-relationships/relationship.model'; import { RelationshipType } from '../../../core/shared/item-relationships/relationship-type.model'; -import { NoContent } from '../../../core/shared/NoContent.model'; import { getFirstSucceededRemoteData, getRemoteDataPayload, } from '../../../core/shared/operators'; -import { hasValue } from '../../../shared/empty.util'; import { ThemedLoadingComponent } from '../../../shared/loading/themed-loading.component'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { followLink } from '../../../shared/utils/follow-link-config.model'; import { VarDirective } from '../../../shared/utils/var.directive'; import { AbstractItemUpdateComponent } from '../abstract-item-update/abstract-item-update.component'; +import { EditItemRelationshipsService } from './edit-item-relationships.service'; import { EditRelationshipListComponent } from './edit-relationship-list/edit-relationship-list.component'; @Component({ @@ -108,6 +89,7 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent { protected relationshipTypeService: RelationshipTypeDataService, public cdr: ChangeDetectorRef, protected modalService: NgbModal, + protected editItemRelationshipsService: EditItemRelationshipsService, ) { super(itemService, objectUpdatesService, router, notificationsService, translateService, route); } @@ -146,152 +128,14 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent { * Make sure the lists are refreshed afterwards and notifications are sent for success and errors */ public submit(): void { - - // Get all the relationships that should be removed - const removeUpdates$: Observable = this.relationshipService.getItemRelationshipsArray(this.item).pipe( - map((relationships: Relationship[]) => relationships.map((relationship) => - Object.assign(new Relationship(), relationship, { uuid: relationship.id }), - )), - switchMap((relationships: Relationship[]) => { - return this.objectUpdatesService.getFieldUpdatesExclusive(this.url, relationships) as Observable; - }), - map((fieldUpdates: FieldUpdates) => - Object.values(fieldUpdates) - .filter((fieldUpdate: FieldUpdate) => fieldUpdate.changeType === FieldChangeType.REMOVE), - ), - take(1), - ); - - 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), - ), - take(1), - ); - - observableCombineLatest([ - removeUpdates$, - addUpdates$, - ]).pipe( - take(1), - 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(); - } - }); + this.editItemRelationshipsService.submit(this.item, this.url); } - 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); - } - - 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); - }), - ); - - } - - /** - * Display notifications - * - Error notification for each failed response with their message - * - Success notification in case there's at least one successful response - * @param responses - */ - displayNotifications(responses: RemoteData[]) { - const failedResponses = responses.filter((response: RemoteData) => response.hasFailed); - const successfulResponses = responses.filter((response: RemoteData) => response.hasSucceeded); - - failedResponses.forEach((response: RemoteData) => { - this.notificationsService.error(this.getNotificationTitle('failed'), response.errorMessage); - }); - if (successfulResponses.length > 0) { - this.notificationsService.success(this.getNotificationTitle('saved'), this.getNotificationContent('saved')); - } - } /** * Sends all initial values of this item to the object updates service */ public initializeOriginalFields() { - return this.relationshipService.getRelatedItems(this.item).pipe( - take(1), - ).subscribe((items: Item[]) => { - this.objectUpdatesService.initialize(this.url, items, this.item.lastModified); - }); + return this.editItemRelationshipsService.initializeOriginalFields(this.item, this.url); }