mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 18:14:17 +00:00
fix issue where a submit emitted from the edit relationship modal wouldn't arrive in the edit relationships component
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { distinctUntilChanged, take, withLatestFrom } from 'rxjs/operators';
|
import { distinctUntilChanged, take, withLatestFrom, delay } from 'rxjs/operators';
|
||||||
import { DOCUMENT, isPlatformBrowser } from '@angular/common';
|
import { DOCUMENT, isPlatformBrowser } from '@angular/common';
|
||||||
import {
|
import {
|
||||||
AfterViewInit,
|
AfterViewInit,
|
||||||
@@ -114,7 +114,10 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit() {
|
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) {
|
if (event instanceof NavigationStart) {
|
||||||
distinctNext(this.isRouteLoading$, true);
|
distinctNext(this.isRouteLoading$, true);
|
||||||
} else if (
|
} else if (
|
||||||
|
@@ -58,6 +58,8 @@ export interface VirtualMetadataSource {
|
|||||||
|
|
||||||
export interface RelationshipIdentifiable extends Identifiable {
|
export interface RelationshipIdentifiable extends Identifiable {
|
||||||
nameVariant?: string;
|
nameVariant?: string;
|
||||||
|
originalItem: Item;
|
||||||
|
originalIsLeft: boolean
|
||||||
relatedItem: Item;
|
relatedItem: Item;
|
||||||
relationship: Relationship;
|
relationship: Relationship;
|
||||||
type: RelationshipType;
|
type: RelationshipType;
|
||||||
|
@@ -309,7 +309,13 @@ export class RelationshipDataService extends IdentifiableDataService<Relationshi
|
|||||||
} else {
|
} else {
|
||||||
findListOptions.searchParams = searchParams;
|
findListOptions.searchParams = searchParams;
|
||||||
}
|
}
|
||||||
return this.searchBy('byLabel', findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
const result$ = this.searchBy('byLabel', findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||||
|
|
||||||
|
// add this result as a dependency of the item, meaning that if the item is invalided, this
|
||||||
|
// result will be as well
|
||||||
|
this.addDependency(result$, item.self);
|
||||||
|
|
||||||
|
return result$;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { EditItemRelationshipsService } from './edit-item-relationships.service';
|
||||||
|
|
||||||
|
describe('EditItemRelationshipsService', () => {
|
||||||
|
let service: EditItemRelationshipsService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(EditItemRelationshipsService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,186 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { take, map, switchMap, concatMap, toArray } from 'rxjs/operators';
|
||||||
|
import { FieldUpdates } from '../../../core/data/object-updates/field-updates.model';
|
||||||
|
import { FieldUpdate } from '../../../core/data/object-updates/field-update.model';
|
||||||
|
import { hasValue } from '../../../shared/empty.util';
|
||||||
|
import { FieldChangeType } from '../../../core/data/object-updates/field-change-type.model';
|
||||||
|
import {
|
||||||
|
DeleteRelationship,
|
||||||
|
RelationshipIdentifiable
|
||||||
|
} 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 } 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';
|
||||||
|
import { NoContent } from '../../../core/shared/NoContent.model';
|
||||||
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
|
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';
|
||||||
|
|
||||||
|
@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<Relationship>) => {
|
||||||
|
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<RemoteData<NoContent>> {
|
||||||
|
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<RemoteData<Relationship>> {
|
||||||
|
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<NoContent>[]) {
|
||||||
|
const failedResponses = responses.filter((response: RemoteData<NoContent>) => response.hasFailed);
|
||||||
|
const successfulResponses = responses.filter((response: RemoteData<NoContent>) => response.hasSucceeded);
|
||||||
|
|
||||||
|
failedResponses.forEach((response: RemoteData<NoContent>) => {
|
||||||
|
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');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@@ -5,6 +5,7 @@ import { ObjectUpdatesService } from '../../../../core/data/object-updates/objec
|
|||||||
import {
|
import {
|
||||||
BehaviorSubject,
|
BehaviorSubject,
|
||||||
combineLatest as observableCombineLatest,
|
combineLatest as observableCombineLatest,
|
||||||
|
EMPTY,
|
||||||
from as observableFrom,
|
from as observableFrom,
|
||||||
Observable,
|
Observable,
|
||||||
Subscription
|
Subscription
|
||||||
@@ -14,10 +15,22 @@ import {
|
|||||||
} from '../../../../core/data/object-updates/object-updates.reducer';
|
} from '../../../../core/data/object-updates/object-updates.reducer';
|
||||||
import { RelationshipDataService } from '../../../../core/data/relationship-data.service';
|
import { RelationshipDataService } from '../../../../core/data/relationship-data.service';
|
||||||
import { Item } from '../../../../core/shared/item.model';
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
import { defaultIfEmpty, map, mergeMap, startWith, switchMap, take, tap, toArray } from 'rxjs/operators';
|
import {
|
||||||
|
defaultIfEmpty,
|
||||||
|
map,
|
||||||
|
mergeMap,
|
||||||
|
startWith,
|
||||||
|
switchMap,
|
||||||
|
take,
|
||||||
|
tap,
|
||||||
|
toArray,
|
||||||
|
concatMap
|
||||||
|
} from 'rxjs/operators';
|
||||||
import { hasNoValue, hasValue, hasValueOperator } from '../../../../shared/empty.util';
|
import { hasNoValue, hasValue, hasValueOperator } from '../../../../shared/empty.util';
|
||||||
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
|
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
|
||||||
import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
|
import {
|
||||||
|
RelationshipType
|
||||||
|
} from '../../../../core/shared/item-relationships/relationship-type.model';
|
||||||
import {
|
import {
|
||||||
getAllSucceededRemoteData,
|
getAllSucceededRemoteData,
|
||||||
getFirstSucceededRemoteData,
|
getFirstSucceededRemoteData,
|
||||||
@@ -25,15 +38,23 @@ import {
|
|||||||
getRemoteDataPayload,
|
getRemoteDataPayload,
|
||||||
} from '../../../../core/shared/operators';
|
} from '../../../../core/shared/operators';
|
||||||
import { ItemType } from '../../../../core/shared/item-relationships/item-type.model';
|
import { ItemType } from '../../../../core/shared/item-relationships/item-type.model';
|
||||||
import { DsDynamicLookupRelationModalComponent } from '../../../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component';
|
import {
|
||||||
import { RelationshipOptions } from '../../../../shared/form/builder/models/relationship-options.model';
|
DsDynamicLookupRelationModalComponent
|
||||||
import { SelectableListService } from '../../../../shared/object-list/selectable-list/selectable-list.service';
|
} from '../../../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component';
|
||||||
|
import {
|
||||||
|
RelationshipOptions
|
||||||
|
} from '../../../../shared/form/builder/models/relationship-options.model';
|
||||||
|
import {
|
||||||
|
SelectableListService
|
||||||
|
} from '../../../../shared/object-list/selectable-list/selectable-list.service';
|
||||||
import { SearchResult } from '../../../../shared/search/models/search-result.model';
|
import { SearchResult } from '../../../../shared/search/models/search-result.model';
|
||||||
import { FollowLinkConfig } from '../../../../shared/utils/follow-link-config.model';
|
import { FollowLinkConfig } from '../../../../shared/utils/follow-link-config.model';
|
||||||
import { PaginatedList } from '../../../../core/data/paginated-list.model';
|
import { PaginatedList } from '../../../../core/data/paginated-list.model';
|
||||||
import { RemoteData } from '../../../../core/data/remote-data';
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
import { Collection } from '../../../../core/shared/collection.model';
|
import { Collection } from '../../../../core/shared/collection.model';
|
||||||
import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model';
|
import {
|
||||||
|
PaginationComponentOptions
|
||||||
|
} from '../../../../shared/pagination/pagination-component-options.model';
|
||||||
import { PaginationService } from '../../../../core/pagination/pagination.service';
|
import { PaginationService } from '../../../../core/pagination/pagination.service';
|
||||||
import { RelationshipTypeDataService } from '../../../../core/data/relationship-type-data.service';
|
import { RelationshipTypeDataService } from '../../../../core/data/relationship-type-data.service';
|
||||||
import { FieldUpdate } from '../../../../core/data/object-updates/field-update.model';
|
import { FieldUpdate } from '../../../../core/data/object-updates/field-update.model';
|
||||||
@@ -41,6 +62,7 @@ import { FieldUpdates } from '../../../../core/data/object-updates/field-updates
|
|||||||
import { FieldChangeType } from '../../../../core/data/object-updates/field-change-type.model';
|
import { FieldChangeType } from '../../../../core/data/object-updates/field-change-type.model';
|
||||||
import { APP_CONFIG, AppConfig } from '../../../../../config/app-config.interface';
|
import { APP_CONFIG, AppConfig } from '../../../../../config/app-config.interface';
|
||||||
import { itemLinksToFollow } from '../../../../shared/utils/relation-query.utils';
|
import { itemLinksToFollow } from '../../../../shared/utils/relation-query.utils';
|
||||||
|
import { EditItemRelationshipsService } from '../edit-item-relationships.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-edit-relationship-list',
|
selector: 'ds-edit-relationship-list',
|
||||||
@@ -90,7 +112,7 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy {
|
|||||||
* Observable that emits true if {@link itemType} is on the left-hand side of {@link relationshipType},
|
* 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.
|
* false if it is on the right-hand side and undefined in the rare case that it is on neither side.
|
||||||
*/
|
*/
|
||||||
private currentItemIsLeftItem$: Observable<boolean>;
|
private currentItemIsLeftItem$: BehaviorSubject<boolean> = new BehaviorSubject(undefined);
|
||||||
|
|
||||||
private relatedEntityType$: Observable<ItemType>;
|
private relatedEntityType$: Observable<ItemType>;
|
||||||
|
|
||||||
@@ -153,6 +175,7 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy {
|
|||||||
protected modalService: NgbModal,
|
protected modalService: NgbModal,
|
||||||
protected paginationService: PaginationService,
|
protected paginationService: PaginationService,
|
||||||
protected selectableListService: SelectableListService,
|
protected selectableListService: SelectableListService,
|
||||||
|
protected editItemRelationshipsService: EditItemRelationshipsService,
|
||||||
@Inject(APP_CONFIG) protected appConfig: AppConfig
|
@Inject(APP_CONFIG) protected appConfig: AppConfig
|
||||||
) {
|
) {
|
||||||
this.fetchThumbnail = this.appConfig.browseBy.showThumbnails;
|
this.fetchThumbnail = this.appConfig.browseBy.showThumbnails;
|
||||||
@@ -211,7 +234,6 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy {
|
|||||||
* Open the dynamic lookup modal to search for items to add as relationships
|
* Open the dynamic lookup modal to search for items to add as relationships
|
||||||
*/
|
*/
|
||||||
openLookup() {
|
openLookup() {
|
||||||
|
|
||||||
this.modalRef = this.modalService.open(DsDynamicLookupRelationModalComponent, {
|
this.modalRef = this.modalService.open(DsDynamicLookupRelationModalComponent, {
|
||||||
size: 'lg'
|
size: 'lg'
|
||||||
});
|
});
|
||||||
@@ -277,27 +299,31 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
|
|
||||||
modalComp.submitEv = () => {
|
modalComp.submitEv = () => {
|
||||||
|
modalComp.isPending = true;
|
||||||
const subscriptions = [];
|
const isLeft = this.currentItemIsLeftItem$.getValue();
|
||||||
|
const addOperations = modalComp.toAdd.map((searchResult: any) => ({ type: 'add', searchResult }));
|
||||||
modalComp.toAdd.forEach((searchResult: SearchResult<Item>) => {
|
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;
|
const relatedItem = searchResult.indexableObject;
|
||||||
subscriptions.push(this.relationshipService.getNameVariant(this.listId, relatedItem.uuid).pipe(
|
return this.relationshipService.getNameVariant(this.listId, relatedItem.uuid).pipe(
|
||||||
map((nameVariant) => {
|
map((nameVariant) => {
|
||||||
const update = {
|
const update = {
|
||||||
uuid: this.relationshipType.id + '-' + searchResult.indexableObject.uuid,
|
uuid: this.relationshipType.id + '-' + searchResult.indexableObject.uuid,
|
||||||
nameVariant,
|
nameVariant,
|
||||||
type: this.relationshipType,
|
type: this.relationshipType,
|
||||||
|
originalIsLeft: isLeft,
|
||||||
|
originalItem: this.item,
|
||||||
relatedItem,
|
relatedItem,
|
||||||
} as RelationshipIdentifiable;
|
} as RelationshipIdentifiable;
|
||||||
this.objectUpdatesService.saveAddFieldUpdate(this.url, update);
|
this.objectUpdatesService.saveAddFieldUpdate(this.url, update);
|
||||||
return update;
|
return update;
|
||||||
})
|
}),
|
||||||
));
|
take(1)
|
||||||
});
|
);
|
||||||
|
} else if (type === 'remove') {
|
||||||
modalComp.toRemove.forEach( (searchResult) => {
|
return this.relationshipService.getNameVariant(this.listId, searchResult.indexableObjectuuid).pipe(
|
||||||
subscriptions.push(this.relationshipService.getNameVariant(this.listId, searchResult.indexableObjectuuid).pipe(
|
|
||||||
switchMap((nameVariant) => {
|
switchMap((nameVariant) => {
|
||||||
return this.getRelationFromId(searchResult.indexableObject).pipe(
|
return this.getRelationFromId(searchResult.indexableObject).pipe(
|
||||||
map( (relationship: Relationship) => {
|
map( (relationship: Relationship) => {
|
||||||
@@ -305,23 +331,27 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy {
|
|||||||
uuid: relationship.id,
|
uuid: relationship.id,
|
||||||
nameVariant,
|
nameVariant,
|
||||||
type: this.relationshipType,
|
type: this.relationshipType,
|
||||||
|
originalIsLeft: isLeft,
|
||||||
|
originalItem: this.item,
|
||||||
relationship,
|
relationship,
|
||||||
} as RelationshipIdentifiable;
|
} as RelationshipIdentifiable;
|
||||||
this.objectUpdatesService.saveRemoveFieldUpdate(this.url,update);
|
this.objectUpdatesService.saveRemoveFieldUpdate(this.url,update);
|
||||||
return update;
|
return update;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
})
|
}),
|
||||||
));
|
take(1)
|
||||||
});
|
)
|
||||||
|
} else {
|
||||||
observableCombineLatest(subscriptions).subscribe( (res) => {
|
return EMPTY;
|
||||||
// Wait until the states changes since there are multiple items
|
}
|
||||||
setTimeout( () => {
|
}),
|
||||||
|
toArray(),
|
||||||
|
).subscribe({
|
||||||
|
complete: () => {
|
||||||
|
this.editItemRelationshipsService.submit(this.item, this.url)
|
||||||
this.submit.emit();
|
this.submit.emit();
|
||||||
},1000);
|
}
|
||||||
|
|
||||||
modalComp.isPending = true;
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -355,28 +385,13 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getRelationFromId(relatedItem) {
|
getRelationFromId(relatedItem) {
|
||||||
return this.currentItemIsLeftItem$.pipe(
|
const relationshipLabel = this.currentItemIsLeftItem$.getValue() ? this.relationshipType.leftwardType : this.relationshipType.rightwardType;
|
||||||
take(1),
|
return this.relationshipService.searchByItemsAndType( this.relationshipType.id, this.item.uuid, relationshipLabel ,[relatedItem.id] ).pipe(
|
||||||
switchMap( isLeft => {
|
|
||||||
let apiCall;
|
|
||||||
if (isLeft) {
|
|
||||||
apiCall = this.relationshipService.searchByItemsAndType( this.relationshipType.id, this.item.uuid, this.relationshipType.leftwardType ,[relatedItem.id] ).pipe(
|
|
||||||
getFirstSucceededRemoteData(),
|
getFirstSucceededRemoteData(),
|
||||||
getRemoteDataPayload(),
|
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<Relationship>) => res.page[0])
|
map( (res: PaginatedList<Relationship>) => res.page[0])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -440,7 +455,6 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
|
||||||
// store the left and right type of the relationship in a single observable
|
// store the left and right type of the relationship in a single observable
|
||||||
this.relationshipLeftAndRightType$ = observableCombineLatest([
|
this.relationshipLeftAndRightType$ = observableCombineLatest([
|
||||||
this.relationshipType.leftType,
|
this.relationshipType.leftType,
|
||||||
@@ -461,7 +475,7 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy {
|
|||||||
(relatedEntityType) => this.listId = `edit-relationship-${this.itemType.id}-${relatedEntityType.id}`
|
(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]) => {
|
map(([leftType, rightType]: [ItemType, ItemType]) => {
|
||||||
if (leftType.id === this.itemType.id) {
|
if (leftType.id === this.itemType.id) {
|
||||||
return true;
|
return true;
|
||||||
@@ -475,7 +489,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}`);
|
console.warn(`The item ${this.item.uuid} is not on the right or the left side of relationship type ${this.relationshipType.uuid}`);
|
||||||
return undefined;
|
return undefined;
|
||||||
})
|
})
|
||||||
);
|
).subscribe((nextValue: boolean) => {
|
||||||
|
this.currentItemIsLeftItem$.next(nextValue);
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
// initialize the pagination options
|
// initialize the pagination options
|
||||||
@@ -500,19 +516,20 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy {
|
|||||||
currentPagination$,
|
currentPagination$,
|
||||||
this.currentItemIsLeftItem$,
|
this.currentItemIsLeftItem$,
|
||||||
]).pipe(
|
]).pipe(
|
||||||
switchMap(([currentPagination, currentItemIsLeftItem]: [PaginationComponentOptions, boolean]) =>
|
switchMap(([currentPagination, currentItemIsLeftItem]: [PaginationComponentOptions, boolean]) => {
|
||||||
// get the relationships for the current item, relationshiptype and page
|
// get the relationships for the current item, relationshiptype and page
|
||||||
this.relationshipService.getItemRelationshipsByLabel(
|
return this.relationshipService.getItemRelationshipsByLabel(
|
||||||
this.item,
|
this.item,
|
||||||
currentItemIsLeftItem ? this.relationshipType.leftwardType : this.relationshipType.rightwardType,
|
currentItemIsLeftItem ? this.relationshipType.leftwardType : this.relationshipType.rightwardType,
|
||||||
{
|
{
|
||||||
elementsPerPage: currentPagination.pageSize,
|
elementsPerPage: currentPagination.pageSize,
|
||||||
currentPage: currentPagination.currentPage
|
currentPage: currentPagination.currentPage
|
||||||
},
|
},
|
||||||
false,
|
true,
|
||||||
true,
|
true,
|
||||||
...linksToFollow
|
...linksToFollow
|
||||||
)),
|
);
|
||||||
|
}),
|
||||||
).subscribe((rd: RemoteData<PaginatedList<Relationship>>) => {
|
).subscribe((rd: RemoteData<PaginatedList<Relationship>>) => {
|
||||||
this.relationshipsRd$.next(rd);
|
this.relationshipsRd$.next(rd);
|
||||||
})
|
})
|
||||||
@@ -548,6 +565,8 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy {
|
|||||||
uuid: relationship.id,
|
uuid: relationship.id,
|
||||||
type: this.relationshipType,
|
type: this.relationshipType,
|
||||||
relationship,
|
relationship,
|
||||||
|
originalIsLeft: isLeftItem,
|
||||||
|
originalItem: this.item,
|
||||||
nameVariant,
|
nameVariant,
|
||||||
} as RelationshipIdentifiable;
|
} as RelationshipIdentifiable;
|
||||||
}),
|
}),
|
||||||
|
@@ -108,10 +108,10 @@ export class EditRelationshipComponent implements OnChanges {
|
|||||||
*/
|
*/
|
||||||
remove(): void {
|
remove(): void {
|
||||||
this.closeVirtualMetadataModal();
|
this.closeVirtualMetadataModal();
|
||||||
observableCombineLatest(
|
observableCombineLatest([
|
||||||
this.leftItem$,
|
this.leftItem$,
|
||||||
this.rightItem$,
|
this.rightItem$,
|
||||||
).pipe(
|
]).pipe(
|
||||||
map((items: Item[]) =>
|
map((items: Item[]) =>
|
||||||
items.map((item) => this.objectUpdatesService
|
items.map((item) => this.objectUpdatesService
|
||||||
.isSelectedVirtualMetadata(this.url, this.relationship.id, item.uuid))
|
.isSelectedVirtualMetadata(this.url, this.relationship.id, item.uuid))
|
||||||
@@ -127,9 +127,9 @@ export class EditRelationshipComponent implements OnChanges {
|
|||||||
) as DeleteRelationship;
|
) as DeleteRelationship;
|
||||||
}),
|
}),
|
||||||
take(1),
|
take(1),
|
||||||
).subscribe((deleteRelationship: DeleteRelationship) =>
|
).subscribe((deleteRelationship: DeleteRelationship) => {
|
||||||
this.objectUpdatesService.saveRemoveFieldUpdate(this.url, deleteRelationship)
|
this.objectUpdatesService.saveRemoveFieldUpdate(this.url, deleteRelationship);
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
openVirtualMetadataModal(content: any) {
|
openVirtualMetadataModal(content: any) {
|
||||||
|
@@ -27,8 +27,7 @@
|
|||||||
[item]="item"
|
[item]="item"
|
||||||
[itemType]="entityType$ | async"
|
[itemType]="entityType$ | async"
|
||||||
[relationshipType]="relationshipType"
|
[relationshipType]="relationshipType"
|
||||||
[hasChanges] = hasChanges()
|
[hasChanges]="hasChanges()"
|
||||||
(submit) = submit()
|
|
||||||
></ds-edit-relationship-list>
|
></ds-edit-relationship-list>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@@ -4,7 +4,7 @@ import {
|
|||||||
DeleteRelationship,
|
DeleteRelationship,
|
||||||
RelationshipIdentifiable,
|
RelationshipIdentifiable,
|
||||||
} from '../../../core/data/object-updates/object-updates.reducer';
|
} from '../../../core/data/object-updates/object-updates.reducer';
|
||||||
import { map, switchMap, take, concatMap, toArray } from 'rxjs/operators';
|
import { map, switchMap, take, concatMap, toArray, tap } from 'rxjs/operators';
|
||||||
import {
|
import {
|
||||||
combineLatest as observableCombineLatest,
|
combineLatest as observableCombineLatest,
|
||||||
Observable,
|
Observable,
|
||||||
@@ -34,6 +34,7 @@ import { FieldChangeType } from '../../../core/data/object-updates/field-change-
|
|||||||
import { RelationshipTypeDataService } from '../../../core/data/relationship-type-data.service';
|
import { RelationshipTypeDataService } from '../../../core/data/relationship-type-data.service';
|
||||||
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { EditItemRelationshipsService } from './edit-item-relationships.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-item-relationships',
|
selector: 'ds-item-relationships',
|
||||||
@@ -70,6 +71,7 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent {
|
|||||||
protected relationshipTypeService: RelationshipTypeDataService,
|
protected relationshipTypeService: RelationshipTypeDataService,
|
||||||
public cdr: ChangeDetectorRef,
|
public cdr: ChangeDetectorRef,
|
||||||
protected modalService: NgbModal,
|
protected modalService: NgbModal,
|
||||||
|
protected editItemRelationshipsService: EditItemRelationshipsService,
|
||||||
) {
|
) {
|
||||||
super(itemService, objectUpdatesService, router, notificationsService, translateService, route);
|
super(itemService, objectUpdatesService, router, notificationsService, translateService, route);
|
||||||
}
|
}
|
||||||
@@ -108,152 +110,14 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent {
|
|||||||
* Make sure the lists are refreshed afterwards and notifications are sent for success and errors
|
* Make sure the lists are refreshed afterwards and notifications are sent for success and errors
|
||||||
*/
|
*/
|
||||||
public submit(): void {
|
public submit(): void {
|
||||||
|
this.editItemRelationshipsService.submit(this.item, this.url);
|
||||||
// Get all the relationships that should be removed
|
|
||||||
const removeUpdates$: Observable<FieldUpdate[]> = 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<FieldUpdates>;
|
|
||||||
}),
|
|
||||||
map((fieldUpdates: FieldUpdates) =>
|
|
||||||
Object.values(fieldUpdates)
|
|
||||||
.filter((fieldUpdate: FieldUpdate) => fieldUpdate.changeType === FieldChangeType.REMOVE)
|
|
||||||
),
|
|
||||||
take(1)
|
|
||||||
);
|
|
||||||
|
|
||||||
const addUpdates$: Observable<FieldUpdate[]> = 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<Relationship>) => {
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteRelationship(deleteRelationship: DeleteRelationship): Observable<RemoteData<NoContent>> {
|
|
||||||
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<RemoteData<Relationship>> {
|
|
||||||
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<NoContent>[]) {
|
|
||||||
const failedResponses = responses.filter((response: RemoteData<NoContent>) => response.hasFailed);
|
|
||||||
const successfulResponses = responses.filter((response: RemoteData<NoContent>) => response.hasSucceeded);
|
|
||||||
|
|
||||||
failedResponses.forEach((response: RemoteData<NoContent>) => {
|
|
||||||
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
|
* Sends all initial values of this item to the object updates service
|
||||||
*/
|
*/
|
||||||
public initializeOriginalFields() {
|
public initializeOriginalFields() {
|
||||||
return this.relationshipService.getRelatedItems(this.item).pipe(
|
return this.editItemRelationshipsService.initializeOriginalFields(this.item, this.url);
|
||||||
take(1),
|
|
||||||
).subscribe((items: Item[]) => {
|
|
||||||
this.objectUpdatesService.initialize(this.url, items, this.item.lastModified);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user