mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-08 10:34:15 +00:00
tests and documentation
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { Component, Inject } from '@angular/core';
|
import { Component, Inject } from '@angular/core';
|
||||||
|
import { LinkService } from '../../../core/cache/builders/link.service';
|
||||||
import { Item } from '../../../core/shared/item.model';
|
import { Item } from '../../../core/shared/item.model';
|
||||||
import { ItemDataService } from '../../../core/data/item-data.service';
|
import { ItemDataService } from '../../../core/data/item-data.service';
|
||||||
import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service';
|
import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service';
|
||||||
|
@@ -3,15 +3,18 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { of as observableOf } from 'rxjs/internal/observable/of';
|
import { of as observableOf } from 'rxjs/internal/observable/of';
|
||||||
|
import { LinkService } from '../../../../core/cache/builders/link.service';
|
||||||
import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions';
|
import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions';
|
||||||
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
|
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
|
||||||
import { PaginatedList } from '../../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../../core/data/paginated-list';
|
||||||
|
import { RelationshipTypeService } from '../../../../core/data/relationship-type.service';
|
||||||
import { RemoteData } from '../../../../core/data/remote-data';
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
import { ItemType } from '../../../../core/shared/item-relationships/item-type.model';
|
import { ItemType } from '../../../../core/shared/item-relationships/item-type.model';
|
||||||
import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
|
import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
|
||||||
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
|
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
|
||||||
import { Item } from '../../../../core/shared/item.model';
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
import { PageInfo } from '../../../../core/shared/page-info.model';
|
import { PageInfo } from '../../../../core/shared/page-info.model';
|
||||||
|
import { getMockLinkService } from '../../../../shared/mocks/mock-link-service';
|
||||||
import { SharedModule } from '../../../../shared/shared.module';
|
import { SharedModule } from '../../../../shared/shared.module';
|
||||||
import { EditRelationshipListComponent } from './edit-relationship-list.component';
|
import { EditRelationshipListComponent } from './edit-relationship-list.component';
|
||||||
|
|
||||||
@@ -152,6 +155,8 @@ describe('EditRelationshipListComponent', () => {
|
|||||||
declarations: [EditRelationshipListComponent],
|
declarations: [EditRelationshipListComponent],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: ObjectUpdatesService, useValue: objectUpdatesService },
|
{ provide: ObjectUpdatesService, useValue: objectUpdatesService },
|
||||||
|
{ provide: RelationshipTypeService, useValue: {} },
|
||||||
|
{ provide: LinkService, useValue: getMockLinkService() },
|
||||||
], schemas: [
|
], schemas: [
|
||||||
NO_ERRORS_SCHEMA
|
NO_ERRORS_SCHEMA
|
||||||
]
|
]
|
||||||
|
@@ -1,15 +1,21 @@
|
|||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
import { LinkService } from '../../../../core/cache/builders/link.service';
|
||||||
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
|
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import {FieldUpdate, FieldUpdates} from '../../../../core/data/object-updates/object-updates.reducer';
|
import {FieldUpdate, FieldUpdates} from '../../../../core/data/object-updates/object-updates.reducer';
|
||||||
import {Item} from '../../../../core/shared/item.model';
|
import {Item} from '../../../../core/shared/item.model';
|
||||||
import {map, switchMap} from 'rxjs/operators';
|
import { map, switchMap, tap } from 'rxjs/operators';
|
||||||
import {hasValue} from '../../../../shared/empty.util';
|
import {hasValue} from '../../../../shared/empty.util';
|
||||||
import {Relationship} from '../../../../core/shared/item-relationships/relationship.model';
|
import {Relationship} from '../../../../core/shared/item-relationships/relationship.model';
|
||||||
import {RelationshipType} from '../../../../core/shared/item-relationships/relationship-type.model';
|
import {RelationshipType} from '../../../../core/shared/item-relationships/relationship-type.model';
|
||||||
import {getRemoteDataPayload, getSucceededRemoteData} from '../../../../core/shared/operators';
|
import {
|
||||||
import {combineLatest as observableCombineLatest, combineLatest} from 'rxjs';
|
getAllSucceededRemoteData,
|
||||||
import {ItemType} from '../../../../core/shared/item-relationships/item-type.model';
|
getRemoteDataPayload,
|
||||||
|
getSucceededRemoteData
|
||||||
|
} from '../../../../core/shared/operators';
|
||||||
|
import { combineLatest as observableCombineLatest } from 'rxjs';
|
||||||
|
import { ItemType } from '../../../../core/shared/item-relationships/item-type.model';
|
||||||
|
import { followLink } from '../../../../shared/utils/follow-link-config.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-edit-relationship-list',
|
selector: 'ds-edit-relationship-list',
|
||||||
@@ -47,6 +53,7 @@ export class EditRelationshipListComponent implements OnInit {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected objectUpdatesService: ObjectUpdatesService,
|
protected objectUpdatesService: ObjectUpdatesService,
|
||||||
|
protected linkService: LinkService
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +78,7 @@ export class EditRelationshipListComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
private getLabel(): Observable<string> {
|
private getLabel(): Observable<string> {
|
||||||
|
|
||||||
return combineLatest([
|
return observableCombineLatest([
|
||||||
this.relationshipType.leftType,
|
this.relationshipType.leftType,
|
||||||
this.relationshipType.rightType,
|
this.relationshipType.rightType,
|
||||||
].map((itemTypeRD) => itemTypeRD.pipe(
|
].map((itemTypeRD) => itemTypeRD.pipe(
|
||||||
@@ -94,8 +101,20 @@ export class EditRelationshipListComponent implements OnInit {
|
|||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.updates$ = this.item.relationships.pipe(
|
this.updates$ = this.item.relationships.pipe(
|
||||||
|
getAllSucceededRemoteData(),
|
||||||
map((relationships) => relationships.payload.page.filter((relationship) => relationship)),
|
map((relationships) => relationships.payload.page.filter((relationship) => relationship)),
|
||||||
switchMap((itemRelationships) =>
|
map((relationships: Relationship[]) =>
|
||||||
|
relationships.map((relationship: Relationship) => {
|
||||||
|
this.linkService.resolveLinks(
|
||||||
|
relationship,
|
||||||
|
followLink('relationshipType'),
|
||||||
|
followLink('leftItem'),
|
||||||
|
followLink('rightItem'),
|
||||||
|
);
|
||||||
|
return relationship;
|
||||||
|
})
|
||||||
|
),
|
||||||
|
switchMap((itemRelationships: Relationship[]) =>
|
||||||
observableCombineLatest(
|
observableCombineLatest(
|
||||||
itemRelationships
|
itemRelationships
|
||||||
.map((relationship) => relationship.relationshipType.pipe(
|
.map((relationship) => relationship.relationshipType.pipe(
|
||||||
|
@@ -26,6 +26,9 @@ import {
|
|||||||
INotification,
|
INotification,
|
||||||
Notification
|
Notification
|
||||||
} from '../../../shared/notifications/models/notification.model';
|
} from '../../../shared/notifications/models/notification.model';
|
||||||
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
|
import { SharedModule } from '../../../shared/shared.module';
|
||||||
|
import { RouterStub } from '../../../shared/testing/router-stub';
|
||||||
import { ItemRelationshipsComponent } from './item-relationships.component';
|
import { ItemRelationshipsComponent } from './item-relationships.component';
|
||||||
|
|
||||||
let comp: any;
|
let comp: any;
|
||||||
|
@@ -98,7 +98,11 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent impl
|
|||||||
|
|
||||||
this.relationshipTypes$ = this.entityType$.pipe(
|
this.relationshipTypes$ = this.entityType$.pipe(
|
||||||
switchMap((entityType) =>
|
switchMap((entityType) =>
|
||||||
this.entityTypeService.getEntityTypeRelationships(entityType.id).pipe(
|
this.entityTypeService.getEntityTypeRelationships(
|
||||||
|
entityType.id,
|
||||||
|
followLink('leftType'),
|
||||||
|
followLink('rightType'))
|
||||||
|
.pipe(
|
||||||
getSucceededRemoteData(),
|
getSucceededRemoteData(),
|
||||||
getRemoteDataPayload(),
|
getRemoteDataPayload(),
|
||||||
map((relationshipTypes) => relationshipTypes.page),
|
map((relationshipTypes) => relationshipTypes.page),
|
||||||
|
@@ -5,6 +5,7 @@ import { ActivatedRoute, Router } from '@angular/router';
|
|||||||
import { Store, StoreModule } from '@ngrx/store';
|
import { Store, StoreModule } from '@ngrx/store';
|
||||||
import { REQUEST } from '@nguniversal/express-engine/tokens';
|
import { REQUEST } from '@nguniversal/express-engine/tokens';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { LinkService } from '../cache/builders/link.service';
|
||||||
|
|
||||||
import { authReducer, AuthState } from './auth.reducer';
|
import { authReducer, AuthState } from './auth.reducer';
|
||||||
import { NativeWindowRef, NativeWindowService } from '../services/window.service';
|
import { NativeWindowRef, NativeWindowService } from '../services/window.service';
|
||||||
@@ -38,7 +39,7 @@ describe('AuthService test', () => {
|
|||||||
let storage: CookieService;
|
let storage: CookieService;
|
||||||
let token: AuthTokenInfo;
|
let token: AuthTokenInfo;
|
||||||
let authenticatedState;
|
let authenticatedState;
|
||||||
let rdbService;
|
let linkService;
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
mockStore = jasmine.createSpyObj('store', {
|
mockStore = jasmine.createSpyObj('store', {
|
||||||
@@ -58,8 +59,10 @@ describe('AuthService test', () => {
|
|||||||
};
|
};
|
||||||
authRequest = new AuthRequestServiceStub();
|
authRequest = new AuthRequestServiceStub();
|
||||||
routeStub = new ActivatedRouteStub();
|
routeStub = new ActivatedRouteStub();
|
||||||
rdbService = getMockRemoteDataBuildService();
|
linkService = {
|
||||||
spyOn(rdbService, 'build').and.returnValue({authenticated: true, eperson: observableOf({payload: {}})});
|
resolveLinks: {}
|
||||||
|
};
|
||||||
|
spyOn(linkService, 'resolveLinks').and.returnValue({authenticated: true, eperson: observableOf({payload: {}})});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +83,7 @@ describe('AuthService test', () => {
|
|||||||
{ provide: RouteService, useValue: routeServiceStub },
|
{ provide: RouteService, useValue: routeServiceStub },
|
||||||
{ provide: ActivatedRoute, useValue: routeStub },
|
{ provide: ActivatedRoute, useValue: routeStub },
|
||||||
{ provide: Store, useValue: mockStore },
|
{ provide: Store, useValue: mockStore },
|
||||||
{ provide: RemoteDataBuildService, useValue: rdbService },
|
{ provide: LinkService, useValue: linkService },
|
||||||
CookieService,
|
CookieService,
|
||||||
AuthService
|
AuthService
|
||||||
],
|
],
|
||||||
@@ -143,7 +146,7 @@ describe('AuthService test', () => {
|
|||||||
{ provide: REQUEST, useValue: {} },
|
{ provide: REQUEST, useValue: {} },
|
||||||
{ provide: Router, useValue: routerStub },
|
{ provide: Router, useValue: routerStub },
|
||||||
{ provide: RouteService, useValue: routeServiceStub },
|
{ provide: RouteService, useValue: routeServiceStub },
|
||||||
{ provide: RemoteDataBuildService, useValue: rdbService },
|
{ provide: RemoteDataBuildService, useValue: linkService },
|
||||||
CookieService,
|
CookieService,
|
||||||
AuthService
|
AuthService
|
||||||
]
|
]
|
||||||
@@ -156,7 +159,7 @@ describe('AuthService test', () => {
|
|||||||
(state as any).core = Object.create({});
|
(state as any).core = Object.create({});
|
||||||
(state as any).core.auth = authenticatedState;
|
(state as any).core.auth = authenticatedState;
|
||||||
});
|
});
|
||||||
authService = new AuthService({}, window, undefined, authReqService, router, routeService, cookieService, store, rdbService);
|
authService = new AuthService({}, window, undefined, authReqService, router, routeService, cookieService, store, linkService);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should return true when user is logged in', () => {
|
it('should return true when user is logged in', () => {
|
||||||
@@ -195,7 +198,7 @@ describe('AuthService test', () => {
|
|||||||
{ provide: REQUEST, useValue: {} },
|
{ provide: REQUEST, useValue: {} },
|
||||||
{ provide: Router, useValue: routerStub },
|
{ provide: Router, useValue: routerStub },
|
||||||
{ provide: RouteService, useValue: routeServiceStub },
|
{ provide: RouteService, useValue: routeServiceStub },
|
||||||
{ provide: RemoteDataBuildService, useValue: rdbService },
|
{ provide: RemoteDataBuildService, useValue: linkService },
|
||||||
ClientCookieService,
|
ClientCookieService,
|
||||||
CookieService,
|
CookieService,
|
||||||
AuthService
|
AuthService
|
||||||
@@ -218,7 +221,7 @@ describe('AuthService test', () => {
|
|||||||
(state as any).core = Object.create({});
|
(state as any).core = Object.create({});
|
||||||
(state as any).core.auth = authenticatedState;
|
(state as any).core.auth = authenticatedState;
|
||||||
});
|
});
|
||||||
authService = new AuthService({}, window, undefined, authReqService, router, routeService, cookieService, store, rdbService);
|
authService = new AuthService({}, window, undefined, authReqService, router, routeService, cookieService, store, linkService);
|
||||||
storage = (authService as any).storage;
|
storage = (authService as any).storage;
|
||||||
routeServiceMock = TestBed.get(RouteService);
|
routeServiceMock = TestBed.get(RouteService);
|
||||||
routerStub = TestBed.get(Router);
|
routerStub = TestBed.get(Router);
|
||||||
|
@@ -9,6 +9,7 @@ import { RouterReducerState } from '@ngrx/router-store';
|
|||||||
import { select, Store } from '@ngrx/store';
|
import { select, Store } from '@ngrx/store';
|
||||||
import { CookieAttributes } from 'js-cookie';
|
import { CookieAttributes } from 'js-cookie';
|
||||||
import { followLink } from '../../shared/utils/follow-link-config.model';
|
import { followLink } from '../../shared/utils/follow-link-config.model';
|
||||||
|
import { LinkService } from '../cache/builders/link.service';
|
||||||
|
|
||||||
import { EPerson } from '../eperson/models/eperson.model';
|
import { EPerson } from '../eperson/models/eperson.model';
|
||||||
import { AuthRequestService } from './auth-request.service';
|
import { AuthRequestService } from './auth-request.service';
|
||||||
@@ -22,8 +23,7 @@ import { AppState, routerStateSelector } from '../../app.reducer';
|
|||||||
import { ResetAuthenticationMessagesAction, SetRedirectUrlAction } from './auth.actions';
|
import { ResetAuthenticationMessagesAction, SetRedirectUrlAction } from './auth.actions';
|
||||||
import { NativeWindowRef, NativeWindowService } from '../services/window.service';
|
import { NativeWindowRef, NativeWindowService } from '../services/window.service';
|
||||||
import { Base64EncodeUrl } from '../../shared/utils/encode-decode.util';
|
import { Base64EncodeUrl } from '../../shared/utils/encode-decode.util';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RouteService } from '../services/route.service';
|
||||||
import {RouteService} from '../services/route.service';
|
|
||||||
|
|
||||||
export const LOGIN_ROUTE = '/login';
|
export const LOGIN_ROUTE = '/login';
|
||||||
export const LOGOUT_ROUTE = '/logout';
|
export const LOGOUT_ROUTE = '/logout';
|
||||||
@@ -50,7 +50,7 @@ export class AuthService {
|
|||||||
protected routeService: RouteService,
|
protected routeService: RouteService,
|
||||||
protected storage: CookieService,
|
protected storage: CookieService,
|
||||||
protected store: Store<AppState>,
|
protected store: Store<AppState>,
|
||||||
protected rdbService: RemoteDataBuildService
|
protected linkService: LinkService
|
||||||
) {
|
) {
|
||||||
this.store.pipe(
|
this.store.pipe(
|
||||||
select(isAuthenticated),
|
select(isAuthenticated),
|
||||||
@@ -134,7 +134,7 @@ export class AuthService {
|
|||||||
headers = headers.append('Authorization', `Bearer ${token.accessToken}`);
|
headers = headers.append('Authorization', `Bearer ${token.accessToken}`);
|
||||||
options.headers = headers;
|
options.headers = headers;
|
||||||
return this.authRequestService.getRequest('status', options).pipe(
|
return this.authRequestService.getRequest('status', options).pipe(
|
||||||
map((status) => this.rdbService.build(status, followLink<AuthStatus>('eperson'))),
|
map((status) => this.linkService.resolveLinks(status, followLink<AuthStatus>('eperson'))),
|
||||||
switchMap((status: AuthStatus) => {
|
switchMap((status: AuthStatus) => {
|
||||||
if (status.authenticated) {
|
if (status.authenticated) {
|
||||||
return status.eperson.pipe(map((eperson) => eperson.payload));
|
return status.eperson.pipe(map((eperson) => eperson.payload));
|
||||||
|
@@ -35,7 +35,7 @@ export class ServerAuthService extends AuthService {
|
|||||||
|
|
||||||
options.headers = headers;
|
options.headers = headers;
|
||||||
return this.authRequestService.getRequest('status', options).pipe(
|
return this.authRequestService.getRequest('status', options).pipe(
|
||||||
map((status) => this.rdbService.build(status, followLink<AuthStatus>('eperson'))),
|
map((status) => this.linkService.resolveLinks(status, followLink<AuthStatus>('eperson'))),
|
||||||
switchMap((status: AuthStatus) => {
|
switchMap((status: AuthStatus) => {
|
||||||
if (status.authenticated) {
|
if (status.authenticated) {
|
||||||
return status.eperson.pipe(map((eperson) => eperson.payload));
|
return status.eperson.pipe(map((eperson) => eperson.payload));
|
||||||
|
46
src/app/core/cache/builders/build-decorators.ts
vendored
46
src/app/core/cache/builders/build-decorators.ts
vendored
@@ -33,6 +33,15 @@ export function getClassForType(type: string | ResourceType) {
|
|||||||
return typeMap.get(type);
|
return typeMap.get(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class decorator to indicate that this class is a dataservice
|
||||||
|
* for a given resource type.
|
||||||
|
*
|
||||||
|
* "dataservice" in this context means that it has findByHref and
|
||||||
|
* findAllByHref methods.
|
||||||
|
*
|
||||||
|
* @param resourceType the resource type the class is a dataservice for
|
||||||
|
*/
|
||||||
export function dataService(resourceType: ResourceType): any {
|
export function dataService(resourceType: ResourceType): any {
|
||||||
return (target: any) => {
|
return (target: any) => {
|
||||||
if (hasNoValue(resourceType)) {
|
if (hasNoValue(resourceType)) {
|
||||||
@@ -48,6 +57,11 @@ export function dataService(resourceType: ResourceType): any {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the dataservice matching the given resource type
|
||||||
|
*
|
||||||
|
* @param resourceType the resource type you want the matching dataservice for
|
||||||
|
*/
|
||||||
export function getDataServiceFor<T extends CacheableObject>(resourceType: ResourceType) {
|
export function getDataServiceFor<T extends CacheableObject>(resourceType: ResourceType) {
|
||||||
return dataServiceMap.get(resourceType.value);
|
return dataServiceMap.get(resourceType.value);
|
||||||
}
|
}
|
||||||
@@ -71,6 +85,9 @@ export function resolvedLink<T extends DataService<any>, K extends keyof T>(prov
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class to represent the data that can be set by the @link decorator
|
||||||
|
*/
|
||||||
export class LinkDefinition<T extends HALResource> {
|
export class LinkDefinition<T extends HALResource> {
|
||||||
resourceType: ResourceType;
|
resourceType: ResourceType;
|
||||||
isList = false;
|
isList = false;
|
||||||
@@ -78,6 +95,19 @@ export class LinkDefinition<T extends HALResource> {
|
|||||||
propertyName: keyof T;
|
propertyName: keyof T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A property decorator to indicate that a certain property is the placeholder
|
||||||
|
* where the contents of a resolved link should be stored.
|
||||||
|
*
|
||||||
|
* e.g. if an Item has an hal link for bundles, and an item.bundles property
|
||||||
|
* this decorator should decorate that item.bundles property.
|
||||||
|
*
|
||||||
|
* @param resourceType the resource type of the object(s) the link retrieves
|
||||||
|
* @param isList an optional boolean indicating whether or not it concerns a list,
|
||||||
|
* defaults to false
|
||||||
|
* @param linkName an optional string in case the HALLink name differs from the
|
||||||
|
* property name
|
||||||
|
*/
|
||||||
export const link = <T extends HALResource>(
|
export const link = <T extends HALResource>(
|
||||||
resourceType: ResourceType,
|
resourceType: ResourceType,
|
||||||
isList = false,
|
isList = false,
|
||||||
@@ -105,10 +135,20 @@ export const link = <T extends HALResource>(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all LinkDefinitions for a model class
|
||||||
|
* @param source
|
||||||
|
*/
|
||||||
export const getLinkDefinitions = <T extends HALResource>(source: GenericConstructor<T>): Map<keyof T['_links'], LinkDefinition<T>> => {
|
export const getLinkDefinitions = <T extends HALResource>(source: GenericConstructor<T>): Map<keyof T['_links'], LinkDefinition<T>> => {
|
||||||
return linkMap.get(source);
|
return linkMap.get(source);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a specific LinkDefinition for a model class
|
||||||
|
*
|
||||||
|
* @param source the model class
|
||||||
|
* @param linkName the name of the link
|
||||||
|
*/
|
||||||
export const getLinkDefinition = <T extends HALResource>(source: GenericConstructor<T>, linkName: keyof T['_links']): LinkDefinition<T> => {
|
export const getLinkDefinition = <T extends HALResource>(source: GenericConstructor<T>, linkName: keyof T['_links']): LinkDefinition<T> => {
|
||||||
const sourceMap = linkMap.get(source);
|
const sourceMap = linkMap.get(source);
|
||||||
if (hasValue(sourceMap)) {
|
if (hasValue(sourceMap)) {
|
||||||
@@ -118,6 +158,12 @@ export const getLinkDefinition = <T extends HALResource>(source: GenericConstruc
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class level decorator to indicate you want to inherit @link annotations
|
||||||
|
* from a parent class.
|
||||||
|
*
|
||||||
|
* @param parent the parent class to inherit @link annotations from
|
||||||
|
*/
|
||||||
export const inheritLinkAnnotations = (parent: any): any => {
|
export const inheritLinkAnnotations = (parent: any): any => {
|
||||||
return (child: any) => {
|
return (child: any) => {
|
||||||
const parentMap: Map<string, LinkDefinition<any>> = linkMap.get(parent) || new Map();
|
const parentMap: Map<string, LinkDefinition<any>> = linkMap.get(parent) || new Map();
|
||||||
|
222
src/app/core/cache/builders/link.service.spec.ts
vendored
Normal file
222
src/app/core/cache/builders/link.service.spec.ts
vendored
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
import { followLink, FollowLinkConfig } from '../../../shared/utils/follow-link-config.model';
|
||||||
|
import { FindListOptions } from '../../data/request.models';
|
||||||
|
import { HALLink } from '../../shared/hal-link.model';
|
||||||
|
import { HALResource } from '../../shared/hal-resource.model';
|
||||||
|
import { ResourceType } from '../../shared/resource-type';
|
||||||
|
import * as decorators from './build-decorators';
|
||||||
|
import { getDataServiceFor } from './build-decorators';
|
||||||
|
import { LinkService } from './link.service';
|
||||||
|
|
||||||
|
const spyOnFunction = <T>(obj: T, func: keyof T) => {
|
||||||
|
const spy = jasmine.createSpy(func as string);
|
||||||
|
spyOnProperty(obj, func, 'get').and.returnValue(spy);
|
||||||
|
|
||||||
|
return spy;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TEST_MODEL = new ResourceType('testmodel');
|
||||||
|
let result: any;
|
||||||
|
|
||||||
|
/* tslint:disable:max-classes-per-file */
|
||||||
|
class TestModel implements HALResource {
|
||||||
|
static type = TEST_MODEL;
|
||||||
|
|
||||||
|
type = TEST_MODEL;
|
||||||
|
|
||||||
|
value: string;
|
||||||
|
|
||||||
|
_links: {
|
||||||
|
self: HALLink;
|
||||||
|
predecessor: HALLink;
|
||||||
|
successor: HALLink;
|
||||||
|
};
|
||||||
|
|
||||||
|
predecessor?: TestModel;
|
||||||
|
successor?: TestModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class TestDataService {
|
||||||
|
findAllByHref(href: string, findListOptions: FindListOptions = {}, ...linksToFollow: Array<FollowLinkConfig<any>>) {
|
||||||
|
return 'findAllByHref'
|
||||||
|
}
|
||||||
|
findByHref(href: string, ...linksToFollow: Array<FollowLinkConfig<any>>) {
|
||||||
|
return 'findByHref'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let testDataService: TestDataService;
|
||||||
|
|
||||||
|
let testModel: TestModel;
|
||||||
|
|
||||||
|
describe('LinkService', () => {
|
||||||
|
let service: LinkService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
testModel = Object.assign(new TestModel(), {
|
||||||
|
value: 'a test value',
|
||||||
|
_links: {
|
||||||
|
self: {
|
||||||
|
href: 'http://self.link'
|
||||||
|
},
|
||||||
|
predecessor: {
|
||||||
|
href: 'http://predecessor.link'
|
||||||
|
},
|
||||||
|
successor: {
|
||||||
|
href: 'http://successor.link'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
testDataService = new TestDataService();
|
||||||
|
spyOn(testDataService, 'findAllByHref').and.callThrough();
|
||||||
|
spyOn(testDataService, 'findByHref').and.callThrough();
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [LinkService, {
|
||||||
|
provide: TestDataService,
|
||||||
|
useValue: testDataService
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
service = TestBed.get(LinkService);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('resolveLink', () => {
|
||||||
|
describe(`when the linkdefinition concerns a single object`, () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOnFunction(decorators, 'getLinkDefinition').and.returnValue({
|
||||||
|
resourceType: TEST_MODEL,
|
||||||
|
linkName: 'predecessor',
|
||||||
|
propertyName: 'predecessor'
|
||||||
|
});
|
||||||
|
spyOnFunction(decorators, 'getDataServiceFor').and.returnValue(TestDataService);
|
||||||
|
service.resolveLink(testModel, followLink('predecessor', {}, followLink('successor')))
|
||||||
|
});
|
||||||
|
it('should call dataservice.findByHref with the correct href and nested links', () => {
|
||||||
|
expect(testDataService.findByHref).toHaveBeenCalledWith(testModel._links.predecessor.href, followLink('successor'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe(`when the linkdefinition concerns a list`, () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOnFunction(decorators, 'getLinkDefinition').and.returnValue({
|
||||||
|
resourceType: TEST_MODEL,
|
||||||
|
linkName: 'predecessor',
|
||||||
|
propertyName: 'predecessor',
|
||||||
|
isList: true
|
||||||
|
});
|
||||||
|
spyOnFunction(decorators, 'getDataServiceFor').and.returnValue(TestDataService);
|
||||||
|
service.resolveLink(testModel, followLink('predecessor', { some: 'options '} as any, followLink('successor')))
|
||||||
|
});
|
||||||
|
it('should call dataservice.findAllByHref with the correct href, findListOptions, and nested links', () => {
|
||||||
|
expect(testDataService.findAllByHref).toHaveBeenCalledWith(testModel._links.predecessor.href, { some: 'options '} as any, followLink('successor'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('either way', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOnFunction(decorators, 'getLinkDefinition').and.returnValue({
|
||||||
|
resourceType: TEST_MODEL,
|
||||||
|
linkName: 'predecessor',
|
||||||
|
propertyName: 'predecessor'
|
||||||
|
});
|
||||||
|
spyOnFunction(decorators, 'getDataServiceFor').and.returnValue(TestDataService);
|
||||||
|
result = service.resolveLink(testModel, followLink('predecessor', {}, followLink('successor')))
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call getLinkDefinition with the correct model and link', () => {
|
||||||
|
expect(decorators.getLinkDefinition).toHaveBeenCalledWith(testModel.constructor, 'predecessor');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call getDataServiceFor with the correct resource type', () => {
|
||||||
|
expect(decorators.getDataServiceFor).toHaveBeenCalledWith(TEST_MODEL);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the model with the resolved link', () => {
|
||||||
|
expect(result.type).toBe(TEST_MODEL);
|
||||||
|
expect(result.value).toBe('a test value');
|
||||||
|
expect(result._links.self.href).toBe('http://self.link');
|
||||||
|
expect(result.predecessor).toBe('findByHref');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(`when the specified link doesn't exist on the model's class`, () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOnFunction(decorators, 'getLinkDefinition').and.returnValue(undefined);
|
||||||
|
});
|
||||||
|
it('should throw an error', () => {
|
||||||
|
expect(() => {
|
||||||
|
service.resolveLink(testModel, followLink('predecessor', {}, followLink('successor')))
|
||||||
|
}).toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(`when there is no dataservice for the resourcetype in the link`, () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOnFunction(decorators, 'getLinkDefinition').and.returnValue({
|
||||||
|
resourceType: TEST_MODEL,
|
||||||
|
linkName: 'predecessor',
|
||||||
|
propertyName: 'predecessor'
|
||||||
|
});
|
||||||
|
spyOnFunction(decorators, 'getDataServiceFor').and.returnValue(undefined);
|
||||||
|
});
|
||||||
|
it('should throw an error', () => {
|
||||||
|
expect(() => {
|
||||||
|
service.resolveLink(testModel, followLink('predecessor', {}, followLink('successor')))
|
||||||
|
}).toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('resolveLinks', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(service, 'resolveLink');
|
||||||
|
service.resolveLinks(testModel, followLink('predecessor'), followLink('successor'))
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call resolveLink with the model for each of the provided links', () => {
|
||||||
|
expect(service.resolveLink).toHaveBeenCalledWith(testModel, followLink('predecessor'));
|
||||||
|
expect(service.resolveLink).toHaveBeenCalledWith(testModel, followLink('successor'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the model', () => {
|
||||||
|
expect(result.type).toBe(TEST_MODEL);
|
||||||
|
expect(result.value).toBe('a test value');
|
||||||
|
expect(result._links.self.href).toBe('http://self.link');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('removeResolvedLinks', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
testModel.predecessor = 'predecessor value';
|
||||||
|
testModel.successor = 'successor value';
|
||||||
|
spyOnFunction(decorators, 'getLinkDefinitions').and.returnValue([
|
||||||
|
{
|
||||||
|
resourceType: TEST_MODEL,
|
||||||
|
linkName: 'predecessor',
|
||||||
|
propertyName: 'predecessor',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resourceType: TEST_MODEL,
|
||||||
|
linkName: 'successor',
|
||||||
|
propertyName: 'successor',
|
||||||
|
}
|
||||||
|
])
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a new version of the object without any resolved links', () => {
|
||||||
|
result = service.removeResolvedLinks(testModel);
|
||||||
|
expect(result.value).toBe(testModel.value);
|
||||||
|
expect(result.type).toBe(testModel.type);
|
||||||
|
expect(result._links).toBe(testModel._links);
|
||||||
|
expect(result.predecessor).toBeUndefined();
|
||||||
|
expect(result.successor).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should leave the original object untouched', () => {
|
||||||
|
service.removeResolvedLinks(testModel);
|
||||||
|
expect(testModel.predecessor).toBe('predecessor value');
|
||||||
|
expect(testModel.successor).toBe('successor value');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
/* tslint:enable:max-classes-per-file */
|
25
src/app/core/cache/builders/link.service.ts
vendored
25
src/app/core/cache/builders/link.service.ts
vendored
@@ -5,6 +5,10 @@ import { GenericConstructor } from '../../shared/generic-constructor';
|
|||||||
import { HALResource } from '../../shared/hal-resource.model';
|
import { HALResource } from '../../shared/hal-resource.model';
|
||||||
import { getDataServiceFor, getLinkDefinition, getLinkDefinitions, LinkDefinition } from './build-decorators';
|
import { getDataServiceFor, getLinkDefinition, getLinkDefinitions, LinkDefinition } from './build-decorators';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Service to handle the resolving and removing
|
||||||
|
* of resolved HALLinks on HALResources
|
||||||
|
*/
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
@@ -15,13 +19,26 @@ export class LinkService {
|
|||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public resolveLinks<T extends HALResource>(model: T, ...linksToFollow: Array<FollowLinkConfig<T>>) {
|
/**
|
||||||
|
* Resolve the given {@link FollowLinkConfig}s for the given model
|
||||||
|
*
|
||||||
|
* @param model the {@link HALResource} to resolve the links for
|
||||||
|
* @param linksToFollow the {@link FollowLinkConfig}s to resolve
|
||||||
|
*/
|
||||||
|
public resolveLinks<T extends HALResource>(model: T, ...linksToFollow: Array<FollowLinkConfig<T>>): T {
|
||||||
linksToFollow.forEach((linkToFollow: FollowLinkConfig<T>) => {
|
linksToFollow.forEach((linkToFollow: FollowLinkConfig<T>) => {
|
||||||
this.resolveLink(model, linkToFollow);
|
this.resolveLink(model, linkToFollow);
|
||||||
});
|
});
|
||||||
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
public resolveLink<T extends HALResource>(model, linkToFollow: FollowLinkConfig<T>) {
|
/**
|
||||||
|
* Resolve the given {@link FollowLinkConfig} for the given model
|
||||||
|
*
|
||||||
|
* @param model the {@link HALResource} to resolve the link for
|
||||||
|
* @param linkToFollow the {@link FollowLinkConfig} to resolve
|
||||||
|
*/
|
||||||
|
public resolveLink<T extends HALResource>(model, linkToFollow: FollowLinkConfig<T>): T {
|
||||||
const matchingLinkDef = getLinkDefinition(model.constructor, linkToFollow.name);
|
const matchingLinkDef = getLinkDefinition(model.constructor, linkToFollow.name);
|
||||||
|
|
||||||
if (hasNoValue(matchingLinkDef)) {
|
if (hasNoValue(matchingLinkDef)) {
|
||||||
@@ -50,10 +67,14 @@ export class LinkService {
|
|||||||
throw new Error(`Something went wrong when using @dataService(${matchingLinkDef.resourceType.value}) ${hasValue(service) ? '' : '(undefined) '}to resolve link ${linkToFollow.name} from ${href}`);
|
throw new Error(`Something went wrong when using @dataService(${matchingLinkDef.resourceType.value}) ${hasValue(service) ? '' : '(undefined) '}to resolve link ${linkToFollow.name} from ${href}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove any resolved links that the model may have.
|
* Remove any resolved links that the model may have.
|
||||||
|
*
|
||||||
|
* @param model the {@link HALResource} to remove the links from
|
||||||
|
* @returns a copy of the given model, without resolved links.
|
||||||
*/
|
*/
|
||||||
public removeResolvedLinks<T extends HALResource>(model: T): T {
|
public removeResolvedLinks<T extends HALResource>(model: T): T {
|
||||||
const result = Object.assign(new (model.constructor as GenericConstructor<T>)(), model);
|
const result = Object.assign(new (model.constructor as GenericConstructor<T>)(), model);
|
||||||
|
@@ -80,9 +80,9 @@ export class RemoteDataBuildService {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
hasValueOperator(),
|
hasValueOperator(),
|
||||||
map((obj: T) => {
|
map((obj: T) =>
|
||||||
return this.build<T>(obj, ...linksToFollow);
|
this.linkService.resolveLinks(obj, ...linksToFollow)
|
||||||
}),
|
),
|
||||||
startWith(undefined),
|
startWith(undefined),
|
||||||
distinctUntilChanged()
|
distinctUntilChanged()
|
||||||
);
|
);
|
||||||
@@ -135,9 +135,9 @@ export class RemoteDataBuildService {
|
|||||||
switchMap((resourceUUIDs: string[]) => {
|
switchMap((resourceUUIDs: string[]) => {
|
||||||
return this.objectCache.getList(resourceUUIDs).pipe(
|
return this.objectCache.getList(resourceUUIDs).pipe(
|
||||||
map((objs: T[]) => {
|
map((objs: T[]) => {
|
||||||
return objs.map((obj: T) => {
|
return objs.map((obj: T) =>
|
||||||
return this.build<T>(obj, ...linksToFollow);
|
this.linkService.resolveLinks(obj, ...linksToFollow)
|
||||||
});
|
);
|
||||||
}));
|
}));
|
||||||
}),
|
}),
|
||||||
startWith([]),
|
startWith([]),
|
||||||
@@ -166,11 +166,6 @@ export class RemoteDataBuildService {
|
|||||||
return this.toRemoteDataObservable(requestEntry$, payload$);
|
return this.toRemoteDataObservable(requestEntry$, payload$);
|
||||||
}
|
}
|
||||||
|
|
||||||
build<T extends CacheableObject>(model: T, ...linksToFollow: Array<FollowLinkConfig<T>>): T {
|
|
||||||
this.linkService.resolveLinks(model, ...linksToFollow);
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
aggregate<T>(input: Array<Observable<RemoteData<T>>>): Observable<RemoteData<T[]>> {
|
aggregate<T>(input: Array<Observable<RemoteData<T>>>): Observable<RemoteData<T[]>> {
|
||||||
|
|
||||||
if (isEmpty(input)) {
|
if (isEmpty(input)) {
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
|
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
import { DataService } from './data.service';
|
import { DataService } from './data.service';
|
||||||
import { RequestService } from './request.service';
|
import { RequestService } from './request.service';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.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 { Store } from '@ngrx/store';
|
||||||
import { CoreState } from '../core.reducers';
|
import { CoreState } from '../core.reducers';
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
@@ -25,11 +25,9 @@ import {ItemType} from '../shared/item-relationships/item-type.model';
|
|||||||
export class EntityTypeService extends DataService<ItemType> {
|
export class EntityTypeService extends DataService<ItemType> {
|
||||||
|
|
||||||
protected linkPath = 'entitytypes';
|
protected linkPath = 'entitytypes';
|
||||||
protected forceBypassCache = false;
|
|
||||||
|
|
||||||
constructor(protected requestService: RequestService,
|
constructor(protected requestService: RequestService,
|
||||||
protected rdbService: RemoteDataBuildService,
|
protected rdbService: RemoteDataBuildService,
|
||||||
protected dataBuildService: NormalizedObjectBuildService,
|
|
||||||
protected store: Store<CoreState>,
|
protected store: Store<CoreState>,
|
||||||
protected halService: HALEndpointService,
|
protected halService: HALEndpointService,
|
||||||
protected objectCache: ObjectCacheService,
|
protected objectCache: ObjectCacheService,
|
||||||
@@ -56,8 +54,9 @@ export class EntityTypeService extends DataService<ItemType> {
|
|||||||
/**
|
/**
|
||||||
* Get the allowed relationship types for an entity type
|
* Get the allowed relationship types for an entity type
|
||||||
* @param entityTypeId
|
* @param entityTypeId
|
||||||
|
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which HALLinks should be automatically resolved
|
||||||
*/
|
*/
|
||||||
getEntityTypeRelationships(entityTypeId: string): Observable<RemoteData<PaginatedList<RelationshipType>>> {
|
getEntityTypeRelationships(entityTypeId: string, ...linksToFollow: Array<FollowLinkConfig<RelationshipType>>): Observable<RemoteData<PaginatedList<RelationshipType>>> {
|
||||||
|
|
||||||
const href$ = this.getRelationshipTypesEndpoint(entityTypeId);
|
const href$ = this.getRelationshipTypesEndpoint(entityTypeId);
|
||||||
|
|
||||||
@@ -66,7 +65,7 @@ export class EntityTypeService extends DataService<ItemType> {
|
|||||||
this.requestService.configure(request);
|
this.requestService.configure(request);
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.rdbService.buildList(href$);
|
return this.rdbService.buildList(href$, ...linksToFollow);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -33,25 +33,6 @@ describe('RelationshipService', () => {
|
|||||||
rightwardType: 'isPublicationOfAuthor'
|
rightwardType: 'isPublicationOfAuthor'
|
||||||
});
|
});
|
||||||
|
|
||||||
const relationship1 = Object.assign(new Relationship(), {
|
|
||||||
_links: {
|
|
||||||
self: { href: relationshipsEndpointURL + '/2' }
|
|
||||||
},
|
|
||||||
id: '2',
|
|
||||||
uuid: '2',
|
|
||||||
relationshipType: observableOf(new RemoteData(false, false, true, undefined, relationshipType))
|
|
||||||
});
|
|
||||||
const relationship2 = Object.assign(new Relationship(), {
|
|
||||||
_links: {
|
|
||||||
self: { href: relationshipsEndpointURL + '/3' }
|
|
||||||
},
|
|
||||||
id: '3',
|
|
||||||
uuid: '3',
|
|
||||||
relationshipType: observableOf(new RemoteData(false, false, true, undefined, relationshipType))
|
|
||||||
});
|
|
||||||
|
|
||||||
const relationships = [relationship1, relationship2];
|
|
||||||
|
|
||||||
const item = Object.assign(new Item(), {
|
const item = Object.assign(new Item(), {
|
||||||
id: 'publication',
|
id: 'publication',
|
||||||
uuid: 'publication',
|
uuid: 'publication',
|
||||||
@@ -76,6 +57,42 @@ describe('RelationshipService', () => {
|
|||||||
self: { href: restEndpointURL + '/author2' }
|
self: { href: restEndpointURL + '/author2' }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const relationship1 = Object.assign(new Relationship(), {
|
||||||
|
_links: {
|
||||||
|
self: {
|
||||||
|
href: relationshipsEndpointURL + '/2'
|
||||||
|
},
|
||||||
|
leftItem: {
|
||||||
|
href: relatedItem1._links.self.href
|
||||||
|
},
|
||||||
|
rightItem: {
|
||||||
|
href: item._links.self.href
|
||||||
|
}
|
||||||
|
},
|
||||||
|
id: '2',
|
||||||
|
uuid: '2',
|
||||||
|
relationshipType: observableOf(new RemoteData(false, false, true, undefined, relationshipType))
|
||||||
|
});
|
||||||
|
const relationship2 = Object.assign(new Relationship(), {
|
||||||
|
_links: {
|
||||||
|
self: {
|
||||||
|
href: relationshipsEndpointURL + '/3'
|
||||||
|
},
|
||||||
|
leftItem: {
|
||||||
|
href: relatedItem2._links.self.href
|
||||||
|
},
|
||||||
|
rightItem: {
|
||||||
|
href: item._links.self.href
|
||||||
|
},
|
||||||
|
},
|
||||||
|
id: '3',
|
||||||
|
uuid: '3',
|
||||||
|
relationshipType: observableOf(new RemoteData(false, false, true, undefined, relationshipType))
|
||||||
|
});
|
||||||
|
|
||||||
|
const relationships = [relationship1, relationship2];
|
||||||
|
|
||||||
relationship1.leftItem = getRemotedataObservable(relatedItem1);
|
relationship1.leftItem = getRemotedataObservable(relatedItem1);
|
||||||
relationship1.rightItem = getRemotedataObservable(item);
|
relationship1.rightItem = getRemotedataObservable(item);
|
||||||
relationship2.leftItem = getRemotedataObservable(relatedItem2);
|
relationship2.leftItem = getRemotedataObservable(relatedItem2);
|
||||||
|
@@ -446,19 +446,12 @@ export class RelationshipService extends DataService<Relationship> {
|
|||||||
clearRelatedCache(uuid: string): Observable<void> {
|
clearRelatedCache(uuid: string): Observable<void> {
|
||||||
return this.findById(uuid).pipe(
|
return this.findById(uuid).pipe(
|
||||||
getSucceededRemoteData(),
|
getSucceededRemoteData(),
|
||||||
switchMap((rd: RemoteData<Relationship>) =>
|
map((rd: RemoteData<Relationship>) => {
|
||||||
observableCombineLatest(
|
this.objectCache.remove(rd.payload._links.leftItem.href);
|
||||||
rd.payload.leftItem.pipe(getSucceededRemoteData()),
|
this.objectCache.remove(rd.payload._links.rightItem.href);
|
||||||
rd.payload.rightItem.pipe(getSucceededRemoteData())
|
this.requestService.removeByHrefSubstring(rd.payload._links.leftItem.href);
|
||||||
)
|
this.requestService.removeByHrefSubstring(rd.payload._links.rightItem.href);
|
||||||
),
|
})
|
||||||
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);
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -87,7 +87,7 @@ export class Item extends DSpaceObject {
|
|||||||
* The list of Relationships this Item has with others
|
* The list of Relationships this Item has with others
|
||||||
* Will be undefined unless the relationships HALLink has been resolved.
|
* Will be undefined unless the relationships HALLink has been resolved.
|
||||||
*/
|
*/
|
||||||
@link(RELATIONSHIP)
|
@link(RELATIONSHIP, true)
|
||||||
relationships?: Observable<RemoteData<PaginatedList<Relationship>>>;
|
relationships?: Observable<RemoteData<PaginatedList<Relationship>>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -342,8 +342,7 @@ export class SearchService implements OnDestroy {
|
|||||||
switchMap((dsoRD: RemoteData<DSpaceObject>) => {
|
switchMap((dsoRD: RemoteData<DSpaceObject>) => {
|
||||||
if ((dsoRD.payload as any).type === Community.type.value) {
|
if ((dsoRD.payload as any).type === Community.type.value) {
|
||||||
const community: Community = dsoRD.payload as Community;
|
const community: Community = dsoRD.payload as Community;
|
||||||
this.linkService.resolveLink(community, followLink('subcommunities'));
|
this.linkService.resolveLinks(community, followLink('subcommunities'), followLink('collections'));
|
||||||
this.linkService.resolveLink(community, followLink('collections'));
|
|
||||||
return observableCombineLatest(community.subcommunities, community.collections).pipe(
|
return observableCombineLatest(community.subcommunities, community.collections).pipe(
|
||||||
map(([subCommunities, collections]) => {
|
map(([subCommunities, collections]) => {
|
||||||
/*if this is a community, we also need to show the direct children*/
|
/*if this is a community, we also need to show the direct children*/
|
||||||
|
@@ -21,7 +21,6 @@ export function getMockRemoteDataBuildService(toRemoteDataObservable$?: Observab
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
buildSingle: (href$: string | Observable<string>) => createSuccessfulRemoteDataObject$({}),
|
buildSingle: (href$: string | Observable<string>) => createSuccessfulRemoteDataObject$({}),
|
||||||
build: (obj: any) => Object.create({}),
|
|
||||||
buildList: (href$: string | Observable<string>) => {
|
buildList: (href$: string | Observable<string>) => {
|
||||||
if (hasValue(buildList$)) {
|
if (hasValue(buildList$)) {
|
||||||
return buildList$;
|
return buildList$;
|
||||||
@@ -46,7 +45,6 @@ export function getMockRemoteDataBuildServiceHrefMap(toRemoteDataObservable$?: O
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
buildSingle: (href$: string | Observable<string>) => createSuccessfulRemoteDataObject$({}),
|
buildSingle: (href$: string | Observable<string>) => createSuccessfulRemoteDataObject$({}),
|
||||||
build: (obj: any) => Object.create({}),
|
|
||||||
buildList: (href$: string | Observable<string>) => {
|
buildList: (href$: string | Observable<string>) => {
|
||||||
if (typeof href$ === 'string') {
|
if (typeof href$ === 'string') {
|
||||||
if (hasValue(buildListHrefMap$[href$])) {
|
if (hasValue(buildListHrefMap$[href$])) {
|
||||||
|
@@ -1,12 +1,42 @@
|
|||||||
import { FindListOptions } from '../../core/data/request.models';
|
import { FindListOptions } from '../../core/data/request.models';
|
||||||
import { HALResource } from '../../core/shared/hal-resource.model';
|
import { HALResource } from '../../core/shared/hal-resource.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class to configure the retrieval of a HALLink
|
||||||
|
*/
|
||||||
export class FollowLinkConfig<R extends HALResource> {
|
export class FollowLinkConfig<R extends HALResource> {
|
||||||
|
/**
|
||||||
|
* The name of the link to fetch.
|
||||||
|
* Can only be a HALLink of the object you're working with
|
||||||
|
*/
|
||||||
name: keyof R['_links'];
|
name: keyof R['_links'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link FindListOptions} for the query,
|
||||||
|
* allows you to resolve the link using a certain page, or sorted
|
||||||
|
* in a certain way
|
||||||
|
*/
|
||||||
findListOptions?: FindListOptions;
|
findListOptions?: FindListOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of {@link FollowLinkConfig}s to
|
||||||
|
* use on the retrieved object.
|
||||||
|
*/
|
||||||
linksToFollow?: Array<FollowLinkConfig<any>>;
|
linksToFollow?: Array<FollowLinkConfig<any>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A factory function for {@link FollowLinkConfig}s,
|
||||||
|
* in order to create them in a less verbose way.
|
||||||
|
*
|
||||||
|
* @param linkName: the name of the link to fetch.
|
||||||
|
* Can only be a HALLink of the object you're working with
|
||||||
|
* @param findListOptions: {@link FindListOptions} for the query,
|
||||||
|
* allows you to resolve the link using a certain page, or sorted
|
||||||
|
* in a certain way
|
||||||
|
* @param linksToFollow: a list of {@link FollowLinkConfig}s to
|
||||||
|
* use on the retrieved object.
|
||||||
|
*/
|
||||||
export const followLink = <R extends HALResource>(
|
export const followLink = <R extends HALResource>(
|
||||||
linkName: keyof R['_links'],
|
linkName: keyof R['_links'],
|
||||||
findListOptions?: FindListOptions,
|
findListOptions?: FindListOptions,
|
||||||
|
Reference in New Issue
Block a user