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 1e678884b1..2030b07f50 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;
+ relationshipTypes$: Observable;
/**
* 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;
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- ) => {
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 8a0269d6b7..f5dab71884 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 @@
-
-
-
-
-
-
-
-
-
-
-
- {{metadata.metadataField}}
-
-
- {{metadata.metadataValue.value}}
-
-
+
+
+
+
+
+
+
+
+
+
+ {{metadata.metadataField}}
+
+
+ {{metadata.metadataValue.value}}
-
+
-
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 7b37b238e8..5b72bec46d 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';
+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';
-// describe('ItemMoveComponent', () => {
-// let comp: VirtualMetadataComponent;
-// let fixture: ComponentFixture
;
-//
-// 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();
-// });
-// });
-// });
+fdescribe('VirtualMetadataComponent', () => {
+
+ let comp: VirtualMetadataComponent;
+ let fixture: ComponentFixture;
+ 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 6a99080c1f..cac46724f0 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- ;
- rightItem$: Observable
- ;
+ /**
+ * 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 = new Map();
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(),
- );
- }
+ /**
+ * 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[] {
- getVirtualMetadata(relationship: Relationship, relatedItem: 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 {
- 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 a03cc6a9cb..8b623e38e4 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 0000000000..583601d898
--- /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 {
+
+ protected linkPath = 'entitytypes';
+ protected forceBypassCache = false;
+
+ constructor(protected requestService: RequestService,
+ protected rdbService: RemoteDataBuildService,
+ protected dataBuildService: NormalizedObjectBuildService,
+ protected store: Store,
+ protected halService: HALEndpointService,
+ protected objectCache: ObjectCacheService,
+ protected notificationsService: NotificationsService,
+ protected http: HttpClient,
+ protected comparator: DefaultChangeAnalyzer) {
+ super();
+ }
+
+ getBrowseEndpoint(options, linkPath?: string): Observable {
+ return this.halService.getEndpoint(this.linkPath);
+ }
+
+ /**
+ * Get the endpoint for the item type's allowed relationship types
+ * @param entityTypeId
+ */
+ getRelationshipTypesEndpoint(entityTypeId: string): Observable {
+ 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>> {
+
+ 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> {
+
+ // 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(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 17ad145eb6..a3a95369fd 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 448f7c9f38..5872a500f7 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 {
return createSelector(coreSelector, (state: CoreState) => state['cache/object-updates']);
@@ -101,18 +97,22 @@ export class ObjectUpdatesService {
*/
getFieldUpdates(url: string, initialFields: Identifiable[]): Observable {
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 {
return this.store
diff --git a/src/app/core/data/relationship.service.ts b/src/app/core/data/relationship.service.ts
index 895cb46ed1..377703f335 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 {
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 {
} 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 {
+ return this.findById(uuid).pipe(
getSucceededRemoteData(),
- flatMap((rd: RemoteData) => 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) =>
+ 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 a93d54db64..117cc074ca 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 {
- return this.getEndpointAt(this.getRootHref(), ...linkPath.split('/'));
+ public getEndpoint(linkPath: string, startHref?: string): Observable {
+ return this.getEndpointAt(startHref || this.getRootHref(), ...linkPath.split('/'));
}
/**