diff --git a/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.html b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.html index 8edcb6808e5f2f0697319eb3f2cdd425540bf082..1a7cc2e2df015d5ba23ea82bd9c962f1b25bf586 100644 --- a/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.html +++ b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.html @@ -1,16 +1,15 @@ -<ng-container *ngVar="(updates$ | async) as updates"> - <div *ngIf="updates"> - <h5>{{getRelationshipMessageKey(relationshipLabel) | translate}}</h5> - <ng-container *ngVar="(updates | dsObjectValues) as updateValues"> - <div *ngFor="let updateValue of updateValues; trackBy: trackUpdate" - ds-edit-relationship - class="relationship-row d-block" - [fieldUpdate]="updateValue || {}" - [url]="url" - [editItem]="item" - [ngClass]="{'alert alert-danger': updateValue.changeType === 2}"> - </div> - <ds-loading *ngIf="updateValues.length == 0" message="{{'loading.items' | translate}}"></ds-loading> +<h5>{{getRelationshipMessageKey() | async | translate}}</h5> +<ng-container *ngVar="updates$ | async as updates"> + <ng-container *ngIf="updates"> + <ng-container *ngVar="updates | dsObjectValues as updateValues"> + <ds-edit-relationship *ngFor="let updateValue of updateValues; trackBy: trackUpdate" + class="relationship-row d-block" + [fieldUpdate]="updateValue" + [url]="url" + [editItem]="item" + [ngClass]="{'alert alert-danger': updateValue?.changeType === 2}"> + </ds-edit-relationship> + </ng-container> </ng-container> - </div> + <div *ngIf="!updates">no relationships</div> </ng-container> 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 bc2fcda3a9818e1c6cbb46d329d266a4a2edf173..9b2bb3c9923b924b952a971a10a70e8e5a61d149 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 @@ -1,27 +1,26 @@ -import { EditRelationshipListComponent } from './edit-relationship-list.component'; -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model'; -import { ResourceType } from '../../../../core/shared/resource-type'; -import { Relationship } from '../../../../core/shared/item-relationships/relationship.model'; -import { of as observableOf } from 'rxjs/internal/observable/of'; -import { RemoteData } from '../../../../core/data/remote-data'; -import { Item } from '../../../../core/shared/item.model'; -import { PaginatedList } from '../../../../core/data/paginated-list'; -import { PageInfo } from '../../../../core/shared/page-info.model'; -import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions'; -import { SharedModule } from '../../../../shared/shared.module'; -import { TranslateModule } from '@ngx-translate/core'; -import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service'; -import { RelationshipService } from '../../../../core/data/relationship.service'; -import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core'; -import { By } from '@angular/platform-browser'; +import {EditRelationshipListComponent} from './edit-relationship-list.component'; +import {async, ComponentFixture, TestBed} from '@angular/core/testing'; +import {RelationshipType} from '../../../../core/shared/item-relationships/relationship-type.model'; +import {Relationship} from '../../../../core/shared/item-relationships/relationship.model'; +import {of as observableOf} from 'rxjs/internal/observable/of'; +import {RemoteData} from '../../../../core/data/remote-data'; +import {Item} from '../../../../core/shared/item.model'; +import {PaginatedList} from '../../../../core/data/paginated-list'; +import {PageInfo} from '../../../../core/shared/page-info.model'; +import {FieldChangeType} from '../../../../core/data/object-updates/object-updates.actions'; +import {SharedModule} from '../../../../shared/shared.module'; +import {TranslateModule} from '@ngx-translate/core'; +import {ObjectUpdatesService} from '../../../../core/data/object-updates/object-updates.service'; +import {DebugElement, NO_ERRORS_SCHEMA} from '@angular/core'; +import {By} from '@angular/platform-browser'; +import {ItemType} from '../../../../core/shared/item-relationships/item-type.model'; let comp: EditRelationshipListComponent; let fixture: ComponentFixture<EditRelationshipListComponent>; let de: DebugElement; let objectUpdatesService; -let relationshipService; +let entityTypeService; const url = 'http://test-url.com/test-url'; @@ -30,42 +29,65 @@ let author1; let author2; let fieldUpdate1; let fieldUpdate2; -let relationships; +let relationship1; +let relationship2; let relationshipType; +let entityType; +let relatedEntityType; describe('EditRelationshipListComponent', () => { beforeEach(async(() => { + relationshipType = Object.assign(new RelationshipType(), { id: '1', uuid: '1', leftwardType: 'isAuthorOfPublication', - rightwardType: 'isPublicationOfAuthor' + rightwardType: 'isPublicationOfAuthor', + leftType: observableOf(new RemoteData(false, false, true, undefined, entityType)), + rightType: observableOf(new RemoteData(false, false, true, undefined, relatedEntityType)), + }); + + relationship1 = Object.assign(new Relationship(), { + self: url + '/2', + id: '2', + uuid: '2', + leftId: 'author1', + rightId: 'publication', + leftItem: observableOf(new RemoteData(false, false, true, undefined, item)), + rightItem: observableOf(new RemoteData(false, false, true, undefined, author1)), + relationshipType: observableOf(new RemoteData(false, false, true, undefined, relationshipType)) }); - relationships = [ - Object.assign(new Relationship(), { - self: url + '/2', - id: '2', - uuid: '2', - leftId: 'author1', - rightId: 'publication', - relationshipType: observableOf(new RemoteData(false, false, true, undefined, relationshipType)) - }), - Object.assign(new Relationship(), { - self: url + '/3', - id: '3', - uuid: '3', - leftId: 'author2', - rightId: 'publication', - relationshipType: observableOf(new RemoteData(false, false, true, undefined, relationshipType)) - }) - ]; + relationship2 = Object.assign(new Relationship(), { + self: url + '/3', + id: '3', + uuid: '3', + leftId: 'author2', + rightId: 'publication', + leftItem: observableOf(new RemoteData(false, false, true, undefined, item)), + rightItem: observableOf(new RemoteData(false, false, true, undefined, author2)), + relationshipType: observableOf(new RemoteData(false, false, true, undefined, relationshipType)) + }); item = Object.assign(new Item(), { self: 'fake-item-url/publication', id: 'publication', uuid: 'publication', - relationships: observableOf(new RemoteData(false, false, true, undefined, new PaginatedList(new PageInfo(), relationships))) + relationships: observableOf(new RemoteData( + false, + false, + true, + undefined, + new PaginatedList(new PageInfo(), [relationship1, relationship2]) + )) + }); + + entityType = Object.assign(new ItemType(), { + id: 'entityType', + }); + + relatedEntityType = Object.assign(new ItemType(), { + id: 'relatedEntityType', }); author1 = Object.assign(new Item(), { @@ -88,17 +110,29 @@ describe('EditRelationshipListComponent', () => { objectUpdatesService = jasmine.createSpyObj('objectUpdatesService', { - getFieldUpdatesExclusive: observableOf({ + getFieldUpdates: observableOf({ [author1.uuid]: fieldUpdate1, [author2.uuid]: fieldUpdate2 }) } ); - relationshipService = jasmine.createSpyObj('relationshipService', + entityTypeService = jasmine.createSpyObj('entityTypeService', { - getRelatedItemsByLabel: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), [author1, author2]))), - getItemRelationshipsByLabel: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), relationships))), + getEntityTypeByLabel: observableOf(new RemoteData( + false, + false, + true, + null, + entityType, + )), + getEntityTypeRelationships: observableOf(new RemoteData( + false, + false, + true, + null, + new PaginatedList(new PageInfo(), [relationshipType]), + )), } ); @@ -107,7 +141,6 @@ describe('EditRelationshipListComponent', () => { declarations: [EditRelationshipListComponent], providers: [ { provide: ObjectUpdatesService, useValue: objectUpdatesService }, - { provide: RelationshipService, useValue: relationshipService } ], schemas: [ NO_ERRORS_SCHEMA ] @@ -120,7 +153,7 @@ describe('EditRelationshipListComponent', () => { de = fixture.debugElement; comp.item = item; comp.url = url; - comp.relationshipLabel = relationshipType.leftwardType; + comp.relationshipType = relationshipType; fixture.detectChanges(); }); 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 7645b2b4b74c09fd6dade827188a62ab867056d3..13214e27cf1ea5ba922666296c6940b6bdfe0981 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 @@ -1,12 +1,15 @@ -import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; -import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service'; -import { Observable } from 'rxjs/internal/Observable'; -import { FieldUpdate, FieldUpdates } from '../../../../core/data/object-updates/object-updates.reducer'; -import { RelationshipService } from '../../../../core/data/relationship.service'; -import { Item } from '../../../../core/shared/item.model'; -import { map, switchMap} from 'rxjs/operators'; -import { hasValue } from '../../../../shared/empty.util'; +import {Component, Input, OnInit} from '@angular/core'; +import {ObjectUpdatesService} from '../../../../core/data/object-updates/object-updates.service'; +import {Observable} from 'rxjs/internal/Observable'; +import {FieldUpdate, FieldUpdates} from '../../../../core/data/object-updates/object-updates.reducer'; +import {Item} from '../../../../core/shared/item.model'; +import {map, switchMap} from 'rxjs/operators'; +import {hasValue} from '../../../../shared/empty.util'; import {Relationship} from '../../../../core/shared/item-relationships/relationship.model'; +import {RelationshipType} from '../../../../core/shared/item-relationships/relationship-type.model'; +import {getRemoteDataPayload, getSucceededRemoteData} from '../../../../core/shared/operators'; +import {combineLatest as observableCombineLatest, combineLatest} from 'rxjs'; +import {ItemType} from '../../../../core/shared/item-relationships/item-type.model'; @Component({ selector: 'ds-edit-relationship-list', @@ -17,12 +20,15 @@ import {Relationship} from '../../../../core/shared/item-relationships/relations * A component creating a list of editable relationships of a certain type * The relationships are rendered as a list of related items */ -export class EditRelationshipListComponent implements OnInit, OnChanges { +export class EditRelationshipListComponent implements OnInit { + /** * 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 @@ -32,7 +38,7 @@ export class EditRelationshipListComponent implements OnInit, OnChanges { /** * The label of the relationship-type we're rendering a list for */ - @Input() relationshipLabel: string; + @Input() relationshipType: RelationshipType; /** * The FieldUpdates for the relationships in question @@ -41,48 +47,42 @@ export class EditRelationshipListComponent implements OnInit, OnChanges { constructor( protected objectUpdatesService: ObjectUpdatesService, - protected relationshipService: RelationshipService ) { } - ngOnInit(): void { - this.initUpdates(); - } - - ngOnChanges(changes: SimpleChanges): void { - this.initUpdates(); - } - /** - * Initialize the FieldUpdates using the related items + * Get the i18n message key for this relationship type */ - initUpdates() { - this.updates$ = this.getUpdatesByLabel(this.relationshipLabel); - } + public getRelationshipMessageKey(): Observable<string> { - /** - * Get FieldUpdates for the relationships of a specific type - * @param label The relationship type's label - */ - public getUpdatesByLabel(label: string): Observable<FieldUpdates> { - return this.relationshipService.getItemRelationshipsByLabel(this.item, label).pipe( - map((relationsRD) => relationsRD.payload.page.map((relationship) => - Object.assign(new Relationship(), relationship, {uuid: relationship.id}) - )), - switchMap((initialFields) => this.objectUpdatesService.getFieldUpdatesExclusive(this.url, initialFields)), + return this.getLabel().pipe( + map((label) => { + if (hasValue(label) && label.indexOf('Of') > -1) { + return `relationships.${label.substring(0, label.indexOf('Of') + 2)}` + } else { + return label; + } + }), ); } /** - * Get the i18n message key for a relationship - * @param label The relationship type's label + * Get the relevant label for this relationship type */ - public getRelationshipMessageKey(label: string): string { - if (hasValue(label) && label.indexOf('Of') > -1) { - return `relationships.${label.substring(0, label.indexOf('Of') + 2)}` - } else { - return label; - } + private getLabel(): Observable<string> { + + return combineLatest([ + this.relationshipType.leftType, + this.relationshipType.rightType, + ].map((itemTypeRD) => itemTypeRD.pipe( + getSucceededRemoteData(), + getRemoteDataPayload(), + ))).pipe( + map((itemTypes) => [ + this.relationshipType.leftwardType, + this.relationshipType.rightwardType, + ][itemTypes.findIndex((itemType) => itemType.id === this.itemType.id)]), + ); } /** @@ -91,4 +91,27 @@ export class EditRelationshipListComponent implements OnInit, OnChanges { trackUpdate(index, update: FieldUpdate) { return update && update.field ? update.field.uuid : undefined; } + + ngOnInit(): void { + this.updates$ = this.item.relationships.pipe( + map((relationships) => relationships.payload.page.filter((relationship) => relationship)), + switchMap((itemRelationships) => + observableCombineLatest( + itemRelationships + .map((relationship) => relationship.relationshipType.pipe( + getSucceededRemoteData(), + getRemoteDataPayload(), + )) + ).pipe( + map((relationshipTypes) => itemRelationships.filter( + (relationship, index) => relationshipTypes[index].id === this.relationshipType.id) + ), + map((relationships) => relationships.map((relationship) => + Object.assign(new Relationship(), relationship, {uuid: relationship.id}) + )), + ) + ), + switchMap((initialFields) => this.objectUpdatesService.getFieldUpdates(this.url, initialFields)), + ); + } } diff --git a/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.html b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.html index 245bf04051bb0694c5cf4045ec9e5da2b9ae32ad..7e61e8958f4c0aedab0f58ad7ab2191b4ee58596 100644 --- a/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.html +++ b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.html @@ -19,7 +19,9 @@ </div> <ng-template #virtualMetadataModal> <ds-virtual-metadata - [relationship]="relationship" + [relationshipId]="relationship.id" + [leftItem]="leftItem$ | async" + [rightItem]="rightItem$ | async" [url]="url" (close)="closeVirtualMetadataModal()" (save)="remove()" 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 3e2480199240d4db580bdccdde7234d034a7bc8f..e8ab8b38db2306c70b17d7cb56aecd75375da7ee 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 @@ -13,7 +13,7 @@ import {NgbModal, NgbModalRef} from '@ng-bootstrap/ng-bootstrap'; @Component({ // tslint:disable-next-line:component-selector - selector: '[ds-edit-relationship]', + selector: 'ds-edit-relationship', styleUrls: ['./edit-relationship.component.scss'], templateUrl: './edit-relationship.component.html', }) 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 4bd0b3df2c3d2b6f9d992b272c85489c1e2cf25b..384a469f24f336ab8301e7d23647d1698619537f 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 @@ -17,8 +17,13 @@ <span class="d-none d-sm-inline"> {{"item.edit.metadata.save-button" | translate}}</span> </button> </div> - <div *ngFor="let label of relationLabels$ | async" class="mb-4"> - <ds-edit-relationship-list [item]="item" [url]="url" [relationshipLabel]="label" ></ds-edit-relationship-list> + <div *ngFor="let relationshipType of relationshipTypes$ | async" class="mb-4"> + <ds-edit-relationship-list + [url]="url" + [item]="item" + [itemType]="entityType$ | async" + [relationshipType]="relationshipType" + ></ds-edit-relationship-list> </div> <div class="button-row bottom"> <div class="float-right"> 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 1e678884b105aa5caf18fe1d76a4d31de210d03e..2030b07f50f367d77c1a6961bc816b43a480fc0d 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 @@ -22,10 +22,12 @@ import { ErrorResponse, RestResponse } from '../../../core/cache/response.models import { isNotEmptyOperator } from '../../../shared/empty.util'; import { RemoteData } from '../../../core/data/remote-data'; import { ObjectCacheService } from '../../../core/cache/object-cache.service'; -import { getSucceededRemoteData} from '../../../core/shared/operators'; +import {getRemoteDataPayload, getSucceededRemoteData} from '../../../core/shared/operators'; import { RequestService } from '../../../core/data/request.service'; import { Subscription } from 'rxjs/internal/Subscription'; -import { getRelationsByRelatedItemIds } from '../../simple/item-types/shared/item-relationships-utils'; +import {RelationshipType} from '../../../core/shared/item-relationships/relationship-type.model'; +import {ItemType} from '../../../core/shared/item-relationships/item-type.model'; +import {EntityTypeService} from '../../../core/data/entity-type.service'; @Component({ selector: 'ds-item-relationships', @@ -40,13 +42,14 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent impl /** * The labels of all different relations within this item */ - relationLabels$: Observable<string[]>; + relationshipTypes$: Observable<RelationshipType[]>; /** * A subscription that checks when the item is deleted in cache and reloads the item by sending a new request * This is used to update the item in cache after relationships are deleted */ itemUpdateSubscription: Subscription; + entityType$: Observable<ItemType>; constructor( protected itemService: ItemDataService, @@ -59,7 +62,7 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent impl protected relationshipService: RelationshipService, protected objectCache: ObjectCacheService, protected requestService: RequestService, - protected cdRef: ChangeDetectorRef + protected entityTypeService: EntityTypeService, ) { super(itemService, objectUpdatesService, router, notificationsService, translateService, EnvConfig, route); } @@ -69,21 +72,13 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent impl */ ngOnInit(): void { super.ngOnInit(); - this.relationLabels$ = this.relationshipService.getItemRelationshipLabels(this.item); - this.initializeItemUpdate(); - } - - /** - * Update the item (and view) when it's removed in the request cache - */ - public initializeItemUpdate(): void { this.itemUpdateSubscription = this.requestService.hasByHrefObservable(this.item.self).pipe( filter((exists: boolean) => !exists), switchMap(() => this.itemService.findById(this.item.uuid)), getSucceededRemoteData(), ).subscribe((itemRD: RemoteData<Item>) => { this.item = itemRD.payload; - this.cdRef.detectChanges(); + this.initializeUpdates(); }); } @@ -91,8 +86,22 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent impl * Initialize the values and updates of the current item's relationship fields */ public initializeUpdates(): void { - this.updates$ = this.relationshipService.getRelatedItems(this.item).pipe( - switchMap((items: Item[]) => this.objectUpdatesService.getFieldUpdates(this.url, items)) + + this.entityType$ = this.entityTypeService.getEntityTypeByLabel( + this.item.firstMetadataValue('relationship.type') + ).pipe( + getSucceededRemoteData(), + getRemoteDataPayload(), + ); + + this.relationshipTypes$ = this.entityType$.pipe( + switchMap((entityType) => + this.entityTypeService.getEntityTypeRelationships(entityType.id).pipe( + getSucceededRemoteData(), + getRemoteDataPayload(), + map((relationshipTypes) => relationshipTypes.page), + ) + ), ); } @@ -142,8 +151,9 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent impl )) ), ).subscribe((responses: RestResponse[]) => { - this.displayNotifications(responses); - this.reset(); + this.itemUpdateSubscription.add(() => { + this.displayNotifications(responses); + }); }); } @@ -165,22 +175,12 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent impl } } - /** - * Re-initialize fields and subscriptions - */ - reset() { - this.initializeOriginalFields(); - this.initializeUpdates(); - this.initializeItemUpdate(); - } - /** * Sends all initial values of this item to the object updates service */ public initializeOriginalFields() { - this.relationshipService.getRelatedItems(this.item).pipe(take(1)).subscribe((items: Item[]) => { - this.objectUpdatesService.initialize(this.url, items, this.item.lastModified); - }); + const initialFields = []; + this.objectUpdatesService.initialize(this.url, initialFields, this.item.lastModified); } /** @@ -189,5 +189,4 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent impl ngOnDestroy(): void { this.itemUpdateSubscription.unsubscribe(); } - } diff --git a/src/app/+item-page/edit-item-page/virtual-metadata/virtual-metadata.component.html b/src/app/+item-page/edit-item-page/virtual-metadata/virtual-metadata.component.html index 8a0269d6b7ed9a6f12950f37c084a249dfbc79cc..f5dab718849c08a1c45895b71084d347d80c35e2 100644 --- a/src/app/+item-page/edit-item-page/virtual-metadata/virtual-metadata.component.html +++ b/src/app/+item-page/edit-item-page/virtual-metadata/virtual-metadata.component.html @@ -5,37 +5,33 @@ </button> </div> <div class="modal-body"> - <div *ngFor="let item$ of [leftItem$, rightItem$]"> - <div *ngVar="item$ | async as item"> - <div *ngVar="(isSelectedVirtualMetadataItem(item) | async) as selected" - (click)="setSelectedVirtualMetadataItem(item, !selected)" - class="d-flex flex-row"> - <div class="m-2"> - <label> - <input type="checkbox" [checked]="selected"> - </label> - </div> - <div class="flex-column"> - <ds-listable-object-component-loader - [object]="item$ | async"></ds-listable-object-component-loader> - <div *ngFor="let metadata of getVirtualMetadata(relationship, item$ | async)"> - <div> - <div class="font-weight-bold"> - {{metadata.metadataField}} - </div> - <div> - {{metadata.metadataValue.value}} - </div> - </div> + <ng-container *ngFor="let item of items; trackBy: trackItem"> + <div *ngVar="(isSelectedVirtualMetadataItem(item) | async) as selected" + (click)="setSelectedVirtualMetadataItem(item, !selected)" + class="d-flex flex-row"> + <div class="m-2"> + <label> + <input class="select" type="checkbox"> + </label> + </div> + <div class="flex-column"> + <ds-listable-object-component-loader [object]="item"> + </ds-listable-object-component-loader> + <div *ngFor="let metadata of virtualMetadata.get(item.uuid)"> + <div class="font-weight-bold"> + {{metadata.metadataField}} + </div> + <div> + {{metadata.metadataValue.value}} </div> </div> </div> </div> - </div> + </ng-container> <div class="d-flex flex-row-reverse m-2"> - <button class="btn btn-primary" - (click)="save.emit()"><i - class="fas fa-save"></i> {{"item.edit.metadata.save-button" | translate}} + <button class="btn btn-primary save" + (click)="save.emit()"> + <i class="fas fa-save"></i> {{"item.edit.metadata.save-button" | translate}} </button> </div> </div> diff --git a/src/app/+item-page/edit-item-page/virtual-metadata/virtual-metadata.component.spec.ts b/src/app/+item-page/edit-item-page/virtual-metadata/virtual-metadata.component.spec.ts index 7b37b238e8c95d63d3432182801147ead883e56d..5b72bec46de413fc89f78147177e7e89eaa7674b 100644 --- a/src/app/+item-page/edit-item-page/virtual-metadata/virtual-metadata.component.spec.ts +++ b/src/app/+item-page/edit-item-page/virtual-metadata/virtual-metadata.component.spec.ts @@ -1,172 +1,102 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { Item } from '../../../core/shared/item.model'; -import { RouterStub } from '../../../shared/testing/router-stub'; -import { CommonModule } from '@angular/common'; -import { RouterTestingModule } from '@angular/router/testing'; -import { TranslateModule } from '@ngx-translate/core'; -import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; -import { ActivatedRoute, Router } from '@angular/router'; -import { VirtualMetadataComponent } from './virtual-metadata.component'; -import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub'; -import { NotificationsService } from '../../../shared/notifications/notifications.service'; -import { SearchService } from '../../../+search-page/search-service/search.service'; -import { of as observableOf } from 'rxjs'; -import { FormsModule } from '@angular/forms'; -import { ItemDataService } from '../../../core/data/item-data.service'; -import { RemoteData } from '../../../core/data/remote-data'; -import { PaginatedList } from '../../../core/data/paginated-list'; -import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; -import { RestResponse } from '../../../core/cache/response.models'; -import { Collection } from '../../../core/shared/collection.model'; - -// describe('ItemMoveComponent', () => { -// let comp: VirtualMetadataComponent; -// let fixture: ComponentFixture<VirtualMetadataComponent>; -// -// const mockItem = Object.assign(new Item(), { -// id: 'fake-id', -// handle: 'fake/handle', -// lastModified: '2018' -// }); -// -// const itemPageUrl = `fake-url/${mockItem.id}`; -// const routerStub = Object.assign(new RouterStub(), { -// url: `${itemPageUrl}/edit` -// }); -// -// const mockItemDataService = jasmine.createSpyObj({ -// moveToCollection: observableOf(new RestResponse(true, 200, 'Success')) -// }); -// -// const mockItemDataServiceFail = jasmine.createSpyObj({ -// moveToCollection: observableOf(new RestResponse(false, 500, 'Internal server error')) -// }); -// -// const routeStub = { -// data: observableOf({ -// item: new RemoteData(false, false, true, null, { -// id: 'item1' -// }) -// }) -// }; -// -// const collection1 = Object.assign(new Collection(),{ -// uuid: 'collection-uuid-1', -// name: 'Test collection 1', -// self: 'self-link-1', -// }); -// -// const collection2 = Object.assign(new Collection(),{ -// uuid: 'collection-uuid-2', -// name: 'Test collection 2', -// self: 'self-link-2', -// }); -// -// const mockSearchService = { -// search: () => { -// return observableOf(new RemoteData(false, false, true, null, -// new PaginatedList(null, [ -// { -// indexableObject: collection1, -// hitHighlights: {} -// }, { -// indexableObject: collection2, -// hitHighlights: {} -// } -// ]))); -// } -// }; -// -// const notificationsServiceStub = new NotificationsServiceStub(); -// -// describe('ItemMoveComponent success', () => { -// beforeEach(async(() => { -// TestBed.configureTestingModule({ -// imports: [CommonModule, FormsModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()], -// declarations: [VirtualMetadataComponent], -// providers: [ -// {provide: ActivatedRoute, useValue: routeStub}, -// {provide: Router, useValue: routerStub}, -// {provide: ItemDataService, useValue: mockItemDataService}, -// {provide: NotificationsService, useValue: notificationsServiceStub}, -// {provide: SearchService, useValue: mockSearchService}, -// ], schemas: [ -// CUSTOM_ELEMENTS_SCHEMA -// ] -// }).compileComponents(); -// })); -// -// beforeEach(() => { -// fixture = TestBed.createComponent(VirtualMetadataComponent); -// comp = fixture.componentInstance; -// fixture.detectChanges(); -// }); -// it('should load suggestions', () => { -// const expected = [ -// collection1, -// collection2 -// ]; -// -// comp.collectionSearchResults.subscribe((value) => { -// expect(value).toEqual(expected); -// } -// ); -// }); -// it('should get current url ', () => { -// expect(comp.getCurrentUrl()).toEqual('fake-url/fake-id/edit'); -// }); -// it('should on click select the correct collection name and id', () => { -// const data = collection1; -// -// comp.onClick(data); -// -// expect(comp.selectedCollectionName).toEqual('Test collection 1'); -// expect(comp.selectedCollection).toEqual(collection1); -// }); -// describe('moveCollection', () => { -// it('should call itemDataService.moveToCollection', () => { -// comp.itemId = 'item-id'; -// comp.selectedCollectionName = 'selected-collection-id'; -// comp.selectedCollection = collection1; -// comp.moveCollection(); -// -// expect(mockItemDataService.moveToCollection).toHaveBeenCalledWith('item-id', collection1); -// }); -// it('should call notificationsService success message on success', () => { -// comp.moveCollection(); -// -// expect(notificationsServiceStub.success).toHaveBeenCalled(); -// }); -// }); -// }); -// -// describe('ItemMoveComponent fail', () => { -// beforeEach(async(() => { -// TestBed.configureTestingModule({ -// imports: [CommonModule, FormsModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()], -// declarations: [VirtualMetadataComponent], -// providers: [ -// {provide: ActivatedRoute, useValue: routeStub}, -// {provide: Router, useValue: routerStub}, -// {provide: ItemDataService, useValue: mockItemDataServiceFail}, -// {provide: NotificationsService, useValue: notificationsServiceStub}, -// {provide: SearchService, useValue: mockSearchService}, -// ], schemas: [ -// CUSTOM_ELEMENTS_SCHEMA -// ] -// }).compileComponents(); -// })); -// -// beforeEach(() => { -// fixture = TestBed.createComponent(VirtualMetadataComponent); -// comp = fixture.componentInstance; -// fixture.detectChanges(); -// }); -// -// it('should call notificationsService error message on fail', () => { -// comp.moveCollection(); -// -// expect(notificationsServiceStub.error).toHaveBeenCalled(); -// }); -// }); -// }); +import {async, ComponentFixture, TestBed} from '@angular/core/testing'; +import {of as observableOf} from 'rxjs/internal/observable/of'; +import {TranslateModule} from '@ngx-translate/core'; +import {DebugElement, NO_ERRORS_SCHEMA} from '@angular/core'; +import {By} from '@angular/platform-browser'; +import {VirtualMetadataComponent} from './virtual-metadata.component'; +import {Item} from '../../../core/shared/item.model'; +import {ObjectUpdatesService} from '../../../core/data/object-updates/object-updates.service'; +import {VarDirective} from '../../../shared/utils/var.directive'; + +fdescribe('VirtualMetadataComponent', () => { + + let comp: VirtualMetadataComponent; + let fixture: ComponentFixture<VirtualMetadataComponent>; + let de: DebugElement; + + let objectUpdatesService; + + const url = 'http://test-url.com/test-url'; + + let item; + let relatedItem; + let relationshipId; + + beforeEach(() => { + + relationshipId = 'relationship id'; + + item = Object.assign(new Item(), { + uuid: 'publication', + metadata: [], + }); + + relatedItem = Object.assign(new Item(), { + uuid: 'relatedItem', + metadata: [], + }); + + objectUpdatesService = jasmine.createSpyObj('objectUpdatesService', { + isSelectedVirtualMetadata: observableOf(false), + setSelectedVirtualMetadata: null, + }); + + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot()], + declarations: [VirtualMetadataComponent, VarDirective], + providers: [ + {provide: ObjectUpdatesService, useValue: objectUpdatesService}, + ], schemas: [ + NO_ERRORS_SCHEMA + ] + }).compileComponents(); + fixture = TestBed.createComponent(VirtualMetadataComponent); + comp = fixture.componentInstance; + de = fixture.debugElement; + + comp.url = url; + comp.leftItem = item; + comp.rightItem = relatedItem; + comp.relationshipId = relationshipId; + + fixture.detectChanges(); + }); + + describe('when clicking the save button', () => { + it('should emit a save event', () => { + + spyOn(comp.save, 'emit'); + fixture.debugElement + .query(By.css('button.save')) + .triggerEventHandler('click', null); + expect(comp.save.emit).toHaveBeenCalled(); + }); + }); + + describe('when clicking the close button', () => { + it('should emit a close event', () => { + + spyOn(comp.close, 'emit'); + fixture.debugElement + .query(By.css('button.close')) + .triggerEventHandler('click', null); + expect(comp.close.emit).toHaveBeenCalled(); + }); + }); + + describe('when selecting an item', () => { + it('should call the updates service setSelectedVirtualMetadata method', () => { + fixture.debugElement + .query(By.css('input.select')) + .triggerEventHandler('click', null); + fixture.whenStable().then(() => + expect(objectUpdatesService.setSelectedVirtualMetadata).toHaveBeenCalledWith( + url, + relationshipId, + item.uuid, + true + ) + ); + }); + }) +}); diff --git a/src/app/+item-page/edit-item-page/virtual-metadata/virtual-metadata.component.ts b/src/app/+item-page/edit-item-page/virtual-metadata/virtual-metadata.component.ts index 6a99080c1f9d8a10d6d12bd12aa016523433f95f..cac46724f04a15ecf730632d204cceef7b596f41 100644 --- a/src/app/+item-page/edit-item-page/virtual-metadata/virtual-metadata.component.ts +++ b/src/app/+item-page/edit-item-page/virtual-metadata/virtual-metadata.component.ts @@ -1,10 +1,7 @@ -import {Component, EventEmitter, Input, OnChanges, Output} from '@angular/core'; -import {ActivatedRoute} from '@angular/router'; +import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; import {Observable} from 'rxjs'; import {Item} from '../../../core/shared/item.model'; -import {Relationship} from '../../../core/shared/item-relationships/relationship.model'; import {MetadataValue} from '../../../core/shared/metadata.models'; -import {getRemoteDataPayload, getSucceededRemoteData} from '../../../core/shared/operators'; import {ObjectUpdatesService} from '../../../core/data/object-updates/object-updates.service'; @Component({ @@ -12,47 +9,67 @@ import {ObjectUpdatesService} from '../../../core/data/object-updates/object-upd templateUrl: './virtual-metadata.component.html' }) /** - * Component that handles the moving of an item to a different collection + * Component that lists both items of a relationship, along with their virtual metadata of the relationship. + * The component is shown when a relationship is marked to be deleted. + * Each item has a checkbox to indicate whether its virtual metadata should be saved as real metadata. */ -export class VirtualMetadataComponent implements OnChanges { +export class VirtualMetadataComponent implements OnInit { /** * The current url of this page */ @Input() url: string; - @Input() relationship: Relationship; + /** + * The id of the relationship to be deleted. + */ + @Input() relationshipId: string; + + /** + * The left item of the relationship to be deleted. + */ + @Input() leftItem: Item; + /** + * The right item of the relationship to be deleted. + */ + @Input() rightItem: Item; + + /** + * Emits when the close button is pressed. + */ @Output() close = new EventEmitter(); + + /** + * Emits when the save button is pressed. + */ @Output() save = new EventEmitter(); - leftItem$: Observable<Item>; - rightItem$: Observable<Item>; + /** + * Get an array of the left and the right item of the relationship to be deleted. + */ + get items() { + return [this.leftItem, this.rightItem]; + } + + private virtualMetadata: Map<string, VirtualMetadata[]> = new Map<string, VirtualMetadata[]>(); constructor( - protected route: ActivatedRoute, protected objectUpdatesService: ObjectUpdatesService, ) { } - ngOnChanges(): void { - this.leftItem$ = this.relationship.leftItem.pipe( - getSucceededRemoteData(), - getRemoteDataPayload(), - ); - this.rightItem$ = this.relationship.rightItem.pipe( - getSucceededRemoteData(), - getRemoteDataPayload(), - ); - } - - getVirtualMetadata(relationship: Relationship, relatedItem: Item): VirtualMetadata[] { + /** + * Get the virtual metadata of a given item corresponding to this relationship. + * @param item the item to get the virtual metadata for + */ + getVirtualMetadata(item: Item): VirtualMetadata[] { - return Object.entries(relatedItem.metadata) + return Object.entries(item.metadata) .map(([key, value]) => value .filter((metadata: MetadataValue) => - metadata.authority && metadata.authority.endsWith(relationship.id)) + !key.startsWith('relation') && metadata.authority && metadata.authority.endsWith(this.relationshipId)) .map((metadata: MetadataValue) => { return { metadataField: key, @@ -60,18 +77,43 @@ export class VirtualMetadataComponent implements OnChanges { } }) ) - .reduce((previous, current) => previous.concat(current)); + .reduce((previous, current) => previous.concat(current), []); } + /** + * Select/deselect the virtual metadata of an item to be saved as real metadata. + * @param item the item for which (not) to save the virtual metadata as real metadata + * @param selected whether or not to save the virtual metadata as real metadata + */ setSelectedVirtualMetadataItem(item: Item, selected: boolean) { - this.objectUpdatesService.setSelectedVirtualMetadata(this.url, this.relationship.id, item.uuid, selected); + this.objectUpdatesService.setSelectedVirtualMetadata(this.url, this.relationshipId, item.uuid, selected); } + /** + * Check whether the virtual metadata of a given item is selected to be saved as real metadata + * @param item the item for which to check whether the virtual metadata is selected to be saved as real metadata + */ isSelectedVirtualMetadataItem(item: Item): Observable<boolean> { - return this.objectUpdatesService.isSelectedVirtualMetadata(this.url, this.relationship.id, item.uuid); + return this.objectUpdatesService.isSelectedVirtualMetadata(this.url, this.relationshipId, item.uuid); + } + + /** + * Prevent unnecessary rerendering so fields don't lose focus + */ + trackItem(index, item: Item) { + return item && item.uuid; + } + + ngOnInit(): void { + this.items.forEach((item) => { + this.virtualMetadata.set(item.uuid, this.getVirtualMetadata(item)); + }); } } +/** + * Represents a virtual metadata entry. + */ export interface VirtualMetadata { metadataField: string, metadataValue: MetadataValue, diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index a03cc6a9cb2dc9e1a8eb99dfd99e954c16767ff0..8b623e38e4ddb46987416ee60a5ad4f5f6d31615 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -121,6 +121,7 @@ import { NormalizedBrowseEntry } from './shared/normalized-browse-entry.model'; import { BrowseDefinition } from './shared/browse-definition.model'; import { MappedCollectionsReponseParsingService } from './data/mapped-collections-reponse-parsing.service'; import { ObjectSelectService } from '../shared/object-select/object-select.service'; +import {EntityTypeService} from './data/entity-type.service'; const IMPORTS = [ CommonModule, @@ -211,6 +212,7 @@ const PROVIDERS = [ TaskResponseParsingService, ClaimedTaskDataService, PoolTaskDataService, + EntityTypeService, // register AuthInterceptor as HttpInterceptor { provide: HTTP_INTERCEPTORS, diff --git a/src/app/core/data/entity-type.service.ts b/src/app/core/data/entity-type.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..583601d898d4f771cb10a53c00409986409ff93d --- /dev/null +++ b/src/app/core/data/entity-type.service.ts @@ -0,0 +1,103 @@ +import { DataService } from './data.service'; +import { RequestService } from './request.service'; +import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; +import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; +import { Store } from '@ngrx/store'; +import { CoreState } from '../core.reducers'; +import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { ObjectCacheService } from '../cache/object-cache.service'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { HttpClient } from '@angular/common/http'; +import { DefaultChangeAnalyzer } from './default-change-analyzer.service'; +import { Injectable } from '@angular/core'; +import { GetRequest } from './request.models'; +import { Observable } from 'rxjs/internal/Observable'; +import {switchMap, take, tap} from 'rxjs/operators'; +import { RemoteData } from './remote-data'; +import {RelationshipType} from '../shared/item-relationships/relationship-type.model'; +import {PaginatedList} from './paginated-list'; +import {ItemType} from '../shared/item-relationships/item-type.model'; + +/** + * Service handling all ItemType requests + */ +@Injectable() +export class EntityTypeService extends DataService<ItemType> { + + protected linkPath = 'entitytypes'; + protected forceBypassCache = false; + + constructor(protected requestService: RequestService, + protected rdbService: RemoteDataBuildService, + protected dataBuildService: NormalizedObjectBuildService, + protected store: Store<CoreState>, + protected halService: HALEndpointService, + protected objectCache: ObjectCacheService, + protected notificationsService: NotificationsService, + protected http: HttpClient, + protected comparator: DefaultChangeAnalyzer<ItemType>) { + super(); + } + + getBrowseEndpoint(options, linkPath?: string): Observable<string> { + return this.halService.getEndpoint(this.linkPath); + } + + /** + * Get the endpoint for the item type's allowed relationship types + * @param entityTypeId + */ + getRelationshipTypesEndpoint(entityTypeId: string): Observable<string> { + return this.halService.getEndpoint(this.linkPath).pipe( + switchMap((href) => this.halService.getEndpoint('relationshiptypes', `${href}/${entityTypeId}`)) + ); + } + + /** + * Get the allowed relationship types for an entity type + * @param entityTypeId + */ + getEntityTypeRelationships(entityTypeId: string): Observable<RemoteData<PaginatedList<RelationshipType>>> { + + const href$ = this.getRelationshipTypesEndpoint(entityTypeId); + + href$.pipe(take(1)).subscribe((href) => { + const request = new GetRequest(this.requestService.generateRequestId(), href); + this.requestService.configure(request); + }); + + return this.rdbService.buildList(href$); + } + + /** + * Get an entity type by their label + * @param label + */ + getEntityTypeByLabel(label: string): Observable<RemoteData<ItemType>> { + + // TODO: Remove mock data once REST API supports this + /* + href$.pipe(take(1)).subscribe((href) => { + const request = new GetRequest(this.requestService.generateRequestId(), href); + this.requestService.configure(request); + }); + + return this.rdbService.buildSingle<EntityType>(href$); + */ + + // Mock: + const index = [ + 'Publication', + 'Person', + 'Project', + 'OrgUnit', + 'Journal', + 'JournalVolume', + 'JournalIssue', + 'DataPackage', + 'DataFile', + ].indexOf(label); + + return this.findById((index + 1) + ''); + } +} diff --git a/src/app/core/data/object-updates/object-updates.actions.ts b/src/app/core/data/object-updates/object-updates.actions.ts index 17ad145eb6fbdb19cf167fb13dc800eeed3984a9..a3a95369fd6d9999eaf1468ec466d07b2ca627a2 100644 --- a/src/app/core/data/object-updates/object-updates.actions.ts +++ b/src/app/core/data/object-updates/object-updates.actions.ts @@ -84,6 +84,9 @@ export class AddFieldUpdateAction implements Action { } } +/** + * An ngrx action to select/deselect virtual metadata in the ObjectUpdates state for a certain page url + */ export class SelectVirtualMetadataAction implements Action { type = ObjectUpdatesActionTypes.SELECT_VIRTUAL_METADATA; @@ -95,12 +98,16 @@ export class SelectVirtualMetadataAction implements Action { }; /** - * Create a new AddFieldUpdateAction + * Create a new SelectVirtualMetadataAction * * @param url * the unique url of the page for which a field update is added - * @param field The identifiable field of which a new update is added - * @param changeType The update's change type + * @param source + * the id of the relationship which adds the virtual metadata + * @param uuid + * the id of the item which has the virtual metadata + * @param select + * whether to select or deselect the virtual metadata to be saved as real metadata */ constructor( url: string, diff --git a/src/app/core/data/object-updates/object-updates.service.ts b/src/app/core/data/object-updates/object-updates.service.ts index 448f7c9f385c78d8ba544604caaba88458f723a5..5872a500f7957d16f5d9c634fb1099a99c763e8c 100644 --- a/src/app/core/data/object-updates/object-updates.service.ts +++ b/src/app/core/data/object-updates/object-updates.service.ts @@ -23,13 +23,9 @@ import { SetEditableFieldUpdateAction, SetValidFieldUpdateAction } from './object-updates.actions'; -import {distinctUntilChanged, filter, map} from 'rxjs/operators'; +import {distinctUntilChanged, filter, map, switchMap} from 'rxjs/operators'; import {hasNoValue, hasValue, isEmpty, isNotEmpty} from '../../../shared/empty.util'; import {INotification} from '../../../shared/notifications/models/notification.model'; -import {Item} from '../../shared/item.model'; -import {Relationship} from '../../shared/item-relationships/relationship.model'; -import {MetadataValue} from '../../shared/metadata.models'; -import {VirtualMetadata} from '../../../+item-page/edit-item-page/virtual-metadata/virtual-metadata.component'; function objectUpdatesStateSelector(): MemoizedSelector<CoreState, ObjectUpdatesState> { return createSelector(coreSelector, (state: CoreState) => state['cache/object-updates']); @@ -101,18 +97,22 @@ export class ObjectUpdatesService { */ getFieldUpdates(url: string, initialFields: Identifiable[]): Observable<FieldUpdates> { const objectUpdates = this.getObjectEntry(url); - return objectUpdates.pipe(map((objectEntry) => { - const fieldUpdates: FieldUpdates = {}; - Object.keys(objectEntry.fieldStates).forEach((uuid) => { - let fieldUpdate = objectEntry.fieldUpdates[uuid]; - if (isEmpty(fieldUpdate)) { - const identifiable = initialFields.find((object: Identifiable) => object.uuid === uuid); - fieldUpdate = { field: identifiable, changeType: undefined }; - } - fieldUpdates[uuid] = fieldUpdate; - }); - return fieldUpdates; - })) + return objectUpdates.pipe( + switchMap((objectEntry) => { + const fieldUpdates: FieldUpdates = {}; + Object.keys(objectEntry.fieldStates).forEach((uuid) => { + fieldUpdates[uuid] = objectEntry.fieldUpdates[uuid]; + }); + return this.getFieldUpdatesExclusive(url, initialFields).pipe( + map((fieldUpdatesExclusive) => { + Object.keys(fieldUpdatesExclusive).forEach((uuid) => { + fieldUpdates[uuid] = fieldUpdatesExclusive[uuid]; + }); + return fieldUpdates; + }) + ); + }), + ); } /** @@ -204,6 +204,15 @@ export class ObjectUpdatesService { saveChangeFieldUpdate(url: string, field: Identifiable) { this.saveFieldUpdate(url, field, FieldChangeType.UPDATE); } + + /** + * Check whether the virtual metadata of a given item is selected to be saved as real metadata + * @param url The URL of the page on which the field resides + * @param relationship The id of the relationship for which to check whether the virtual metadata is selected to be + * saved as real metadata + * @param item The id of the item for which to check whether the virtual metadata is selected to be + * saved as real metadata + */ isSelectedVirtualMetadata(url: string, relationship: string, item: string): Observable<boolean> { return this.store diff --git a/src/app/core/data/relationship.service.ts b/src/app/core/data/relationship.service.ts index 895cb46ed1f93cb7fa09a451b8e435e81244cd4f..377703f3351e992585632cb8abd03654108a7a0f 100644 --- a/src/app/core/data/relationship.service.ts +++ b/src/app/core/data/relationship.service.ts @@ -1,39 +1,40 @@ -import { Injectable } from '@angular/core'; -import { RequestService } from './request.service'; -import { HALEndpointService } from '../shared/hal-endpoint.service'; -import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; -import { hasValue, hasValueOperator, isNotEmptyOperator } from '../../shared/empty.util'; -import { distinctUntilChanged, filter, flatMap, map, switchMap, take, tap } from 'rxjs/operators'; +import {Injectable} from '@angular/core'; +import {RequestService} from './request.service'; +import {HALEndpointService} from '../shared/hal-endpoint.service'; +import {RemoteDataBuildService} from '../cache/builders/remote-data-build.service'; +import {hasValue, hasValueOperator, isNotEmptyOperator} from '../../shared/empty.util'; +import {distinctUntilChanged, filter, flatMap, map, switchMap, take, tap} from 'rxjs/operators'; import { configureRequest, - filterSuccessfulResponses, - getRemoteDataPayload, getResponseFromEntry, + getRemoteDataPayload, + getResponseFromEntry, getSucceededRemoteData } from '../shared/operators'; -import { DeleteRequest, FindAllOptions, RestRequest } from './request.models'; -import { Observable } from 'rxjs/internal/Observable'; -import { RestResponse } from '../cache/response.models'; -import { Item } from '../shared/item.model'; -import { Relationship } from '../shared/item-relationships/relationship.model'; -import { RelationshipType } from '../shared/item-relationships/relationship-type.model'; -import { RemoteData } from './remote-data'; -import { combineLatest as observableCombineLatest } from 'rxjs/internal/observable/combineLatest'; -import { zip as observableZip } from 'rxjs'; -import { PaginatedList } from './paginated-list'; -import { ItemDataService } from './item-data.service'; +import {DeleteRequest, FindAllOptions, RestRequest} from './request.models'; +import {Observable} from 'rxjs/internal/Observable'; +import {RestResponse} from '../cache/response.models'; +import {Item} from '../shared/item.model'; +import {Relationship} from '../shared/item-relationships/relationship.model'; +import {RelationshipType} from '../shared/item-relationships/relationship-type.model'; +import {RemoteData} from './remote-data'; +import {combineLatest as observableCombineLatest} from 'rxjs/internal/observable/combineLatest'; +import {zip as observableZip} from 'rxjs'; +import {PaginatedList} from './paginated-list'; +import {ItemDataService} from './item-data.service'; import { - compareArraysUsingIds, filterRelationsByTypeLabel, paginatedRelationsToItems, + compareArraysUsingIds, + paginatedRelationsToItems, relationsToItems } from '../../+item-page/simple/item-types/shared/item-relationships-utils'; -import { ObjectCacheService } from '../cache/object-cache.service'; -import { DataService } from './data.service'; -import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; -import { Store } from '@ngrx/store'; -import { CoreState } from '../core.reducers'; -import { NotificationsService } from '../../shared/notifications/notifications.service'; +import {ObjectCacheService} from '../cache/object-cache.service'; +import {DataService} from './data.service'; +import {NormalizedObjectBuildService} from '../cache/builders/normalized-object-build.service'; +import {Store} from '@ngrx/store'; +import {CoreState} from '../core.reducers'; +import {NotificationsService} from '../../shared/notifications/notifications.service'; import {HttpClient} from '@angular/common/http'; -import { DefaultChangeAnalyzer } from './default-change-analyzer.service'; -import { SearchParam } from '../cache/models/search-param.model'; +import {DefaultChangeAnalyzer} from './default-change-analyzer.service'; +import {SearchParam} from '../cache/models/search-param.model'; /** * The service handling all relationship requests @@ -93,19 +94,12 @@ export class RelationshipService extends DataService<Relationship> { configureRequest(this.requestService), switchMap((restRequest: RestRequest) => this.requestService.getByUUID(restRequest.uuid)), getResponseFromEntry(), - tap(() => this.clearRelatedCache(uuid)) - ); - } - - /** - * Get a combined observable containing an array of all relationships in an item, as well as an array of the relationships their types - * This is used for easier access of a relationship's type because they exist as observables - * @param item - */ - getItemResolvedRelsAndTypes(item: Item): Observable<[Relationship[], RelationshipType[]]> { - return observableCombineLatest( - this.getItemRelationshipsArray(item), - this.getItemRelationshipTypesArray(item) + take(1), + switchMap((response) => + this.clearRelatedCache(uuid).pipe( + map(() => response), + ) + ), ); } @@ -252,23 +246,33 @@ export class RelationshipService extends DataService<Relationship> { } else { findAllOptions.searchParams = searchParams; } - return this.searchBy('byLabel', findAllOptions); + return this.searchBy('byLabel', findAllOptions).pipe( + tap((relationshipsRD) => relationshipsRD.payload.page.forEach( + (relationship) => console.log('relationship: ' + relationship.id)) + ), + ); } /** * Clear object and request caches of the items related to a relationship (left and right items) * @param uuid */ - clearRelatedCache(uuid: string) { - this.findById(uuid).pipe( + clearRelatedCache(uuid: string): Observable<void> { + return this.findById(uuid).pipe( getSucceededRemoteData(), - flatMap((rd: RemoteData<Relationship>) => observableCombineLatest(rd.payload.leftItem.pipe(getSucceededRemoteData()), rd.payload.rightItem.pipe(getSucceededRemoteData()))), - take(1) - ).subscribe(([leftItem, rightItem]) => { - this.objectCache.remove(leftItem.payload.self); - this.objectCache.remove(rightItem.payload.self); - this.requestService.removeByHrefSubstring(leftItem.payload.self); - this.requestService.removeByHrefSubstring(rightItem.payload.self); - }); + switchMap((rd: RemoteData<Relationship>) => + observableCombineLatest( + rd.payload.leftItem.pipe(getSucceededRemoteData()), + rd.payload.rightItem.pipe(getSucceededRemoteData()) + ) + ), + take(1), + map(([leftItem, rightItem]) => { + this.objectCache.remove(leftItem.payload.self); + this.objectCache.remove(rightItem.payload.self); + this.requestService.removeByHrefSubstring(leftItem.payload.self); + this.requestService.removeByHrefSubstring(rightItem.payload.self); + }), + ); } } diff --git a/src/app/core/shared/hal-endpoint.service.ts b/src/app/core/shared/hal-endpoint.service.ts index a93d54db64974b1e86d95c54e8ce81aa6d1d4f69..117cc074ca995f44be17c5048ce1bb720bc2cf2c 100644 --- a/src/app/core/shared/hal-endpoint.service.ts +++ b/src/app/core/shared/hal-endpoint.service.ts @@ -43,8 +43,8 @@ export class HALEndpointService { ); } - public getEndpoint(linkPath: string): Observable<string> { - return this.getEndpointAt(this.getRootHref(), ...linkPath.split('/')); + public getEndpoint(linkPath: string, startHref?: string): Observable<string> { + return this.getEndpointAt(startHref || this.getRootHref(), ...linkPath.split('/')); } /**