mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Merge pull request #3415 from alexandrevryghem/w2p-107155_Performance-re-request-embeds-main
Added support for caching embedded objects without a self link & null responses
This commit is contained in:
@@ -7,7 +7,7 @@ import {
|
|||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
import { BreadcrumbConfig } from '../../breadcrumbs/breadcrumb/breadcrumb-config.model';
|
import { BreadcrumbConfig } from '../../breadcrumbs/breadcrumb/breadcrumb-config.model';
|
||||||
import { ITEM_PAGE_LINKS_TO_FOLLOW } from '../../item-page/item.resolver';
|
import { getItemPageLinksToFollow } from '../../item-page/item.resolver';
|
||||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
import { ItemDataService } from '../data/item-data.service';
|
import { ItemDataService } from '../data/item-data.service';
|
||||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||||
@@ -24,7 +24,7 @@ export const itemBreadcrumbResolver: ResolveFn<BreadcrumbConfig<Item>> = (
|
|||||||
breadcrumbService: DSOBreadcrumbsService = inject(DSOBreadcrumbsService),
|
breadcrumbService: DSOBreadcrumbsService = inject(DSOBreadcrumbsService),
|
||||||
dataService: ItemDataService = inject(ItemDataService),
|
dataService: ItemDataService = inject(ItemDataService),
|
||||||
): Observable<BreadcrumbConfig<Item>> => {
|
): Observable<BreadcrumbConfig<Item>> => {
|
||||||
const linksToFollow: FollowLinkConfig<DSpaceObject>[] = ITEM_PAGE_LINKS_TO_FOLLOW as FollowLinkConfig<DSpaceObject>[];
|
const linksToFollow: FollowLinkConfig<DSpaceObject>[] = getItemPageLinksToFollow() as FollowLinkConfig<DSpaceObject>[];
|
||||||
return DSOBreadcrumbResolver(
|
return DSOBreadcrumbResolver(
|
||||||
route,
|
route,
|
||||||
state,
|
state,
|
||||||
|
@@ -7,7 +7,6 @@ import { of as observableOf } from 'rxjs';
|
|||||||
import { TestScheduler } from 'rxjs/testing';
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
|
|
||||||
import { getMockHrefOnlyDataService } from '../../shared/mocks/href-only-data.service.mock';
|
import { getMockHrefOnlyDataService } from '../../shared/mocks/href-only-data.service.mock';
|
||||||
import { getMockRemoteDataBuildService } from '../../shared/mocks/remote-data-build.service.mock';
|
|
||||||
import { getMockRequestService } from '../../shared/mocks/request.service.mock';
|
import { getMockRequestService } from '../../shared/mocks/request.service.mock';
|
||||||
import {
|
import {
|
||||||
createSuccessfulRemoteDataObject,
|
createSuccessfulRemoteDataObject,
|
||||||
@@ -18,7 +17,6 @@ import {
|
|||||||
createPaginatedList,
|
createPaginatedList,
|
||||||
getFirstUsedArgumentOfSpyMethod,
|
getFirstUsedArgumentOfSpyMethod,
|
||||||
} from '../../shared/testing/utils.test';
|
} from '../../shared/testing/utils.test';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
|
||||||
import { RequestService } from '../data/request.service';
|
import { RequestService } from '../data/request.service';
|
||||||
import { RequestEntry } from '../data/request-entry.model';
|
import { RequestEntry } from '../data/request-entry.model';
|
||||||
import { FlatBrowseDefinition } from '../shared/flat-browse-definition.model';
|
import { FlatBrowseDefinition } from '../shared/flat-browse-definition.model';
|
||||||
@@ -31,7 +29,6 @@ describe('BrowseService', () => {
|
|||||||
let scheduler: TestScheduler;
|
let scheduler: TestScheduler;
|
||||||
let service: BrowseService;
|
let service: BrowseService;
|
||||||
let requestService: RequestService;
|
let requestService: RequestService;
|
||||||
let rdbService: RemoteDataBuildService;
|
|
||||||
|
|
||||||
const browsesEndpointURL = 'https://rest.api/browses';
|
const browsesEndpointURL = 'https://rest.api/browses';
|
||||||
const halService: any = new HALEndpointServiceStub(browsesEndpointURL);
|
const halService: any = new HALEndpointServiceStub(browsesEndpointURL);
|
||||||
@@ -129,7 +126,6 @@ describe('BrowseService', () => {
|
|||||||
halService,
|
halService,
|
||||||
browseDefinitionDataService,
|
browseDefinitionDataService,
|
||||||
hrefOnlyDataService,
|
hrefOnlyDataService,
|
||||||
rdbService,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,11 +137,9 @@ describe('BrowseService', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
requestService = getMockRequestService(getRequestEntry$(true));
|
requestService = getMockRequestService(getRequestEntry$(true));
|
||||||
rdbService = getMockRemoteDataBuildService();
|
|
||||||
service = initTestService();
|
service = initTestService();
|
||||||
spyOn(halService, 'getEndpoint').and
|
spyOn(halService, 'getEndpoint').and
|
||||||
.returnValue(hot('--a-', { a: browsesEndpointURL }));
|
.returnValue(hot('--a-', { a: browsesEndpointURL }));
|
||||||
spyOn(rdbService, 'buildList').and.callThrough();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call BrowseDefinitionDataService to create the RemoteData Observable', () => {
|
it('should call BrowseDefinitionDataService to create the RemoteData Observable', () => {
|
||||||
@@ -162,9 +156,7 @@ describe('BrowseService', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
requestService = getMockRequestService(getRequestEntry$(true));
|
requestService = getMockRequestService(getRequestEntry$(true));
|
||||||
rdbService = getMockRemoteDataBuildService();
|
|
||||||
service = initTestService();
|
service = initTestService();
|
||||||
spyOn(rdbService, 'buildList').and.callThrough();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when getBrowseEntriesFor is called with a valid browse definition id', () => {
|
describe('when getBrowseEntriesFor is called with a valid browse definition id', () => {
|
||||||
@@ -215,7 +207,6 @@ describe('BrowseService', () => {
|
|||||||
describe('if getBrowseDefinitions fires', () => {
|
describe('if getBrowseDefinitions fires', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
requestService = getMockRequestService(getRequestEntry$(true));
|
requestService = getMockRequestService(getRequestEntry$(true));
|
||||||
rdbService = getMockRemoteDataBuildService();
|
|
||||||
service = initTestService();
|
service = initTestService();
|
||||||
spyOn(service, 'getBrowseDefinitions').and
|
spyOn(service, 'getBrowseDefinitions').and
|
||||||
.returnValue(hot('--a-', {
|
.returnValue(hot('--a-', {
|
||||||
@@ -270,7 +261,6 @@ describe('BrowseService', () => {
|
|||||||
describe('if getBrowseDefinitions doesn\'t fire', () => {
|
describe('if getBrowseDefinitions doesn\'t fire', () => {
|
||||||
it('should return undefined', () => {
|
it('should return undefined', () => {
|
||||||
requestService = getMockRequestService(getRequestEntry$(true));
|
requestService = getMockRequestService(getRequestEntry$(true));
|
||||||
rdbService = getMockRemoteDataBuildService();
|
|
||||||
service = initTestService();
|
service = initTestService();
|
||||||
spyOn(service, 'getBrowseDefinitions').and
|
spyOn(service, 'getBrowseDefinitions').and
|
||||||
.returnValue(hot('----'));
|
.returnValue(hot('----'));
|
||||||
@@ -288,9 +278,7 @@ describe('BrowseService', () => {
|
|||||||
describe('getFirstItemFor', () => {
|
describe('getFirstItemFor', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
requestService = getMockRequestService();
|
requestService = getMockRequestService();
|
||||||
rdbService = getMockRemoteDataBuildService();
|
|
||||||
service = initTestService();
|
service = initTestService();
|
||||||
spyOn(rdbService, 'buildList').and.callThrough();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when getFirstItemFor is called with a valid browse definition id', () => {
|
describe('when getFirstItemFor is called with a valid browse definition id', () => {
|
||||||
|
@@ -6,6 +6,7 @@ import {
|
|||||||
startWith,
|
startWith,
|
||||||
} from 'rxjs/operators';
|
} from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { environment } from '../../../environments/environment';
|
||||||
import {
|
import {
|
||||||
hasValue,
|
hasValue,
|
||||||
hasValueOperator,
|
hasValueOperator,
|
||||||
@@ -16,7 +17,6 @@ import {
|
|||||||
followLink,
|
followLink,
|
||||||
FollowLinkConfig,
|
FollowLinkConfig,
|
||||||
} from '../../shared/utils/follow-link-config.model';
|
} from '../../shared/utils/follow-link-config.model';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
|
||||||
import { SortDirection } from '../cache/models/sort-options.model';
|
import { SortDirection } from '../cache/models/sort-options.model';
|
||||||
import { HrefOnlyDataService } from '../data/href-only-data.service';
|
import { HrefOnlyDataService } from '../data/href-only-data.service';
|
||||||
import { PaginatedList } from '../data/paginated-list.model';
|
import { PaginatedList } from '../data/paginated-list.model';
|
||||||
@@ -38,9 +38,15 @@ import { URLCombiner } from '../url-combiner/url-combiner';
|
|||||||
import { BrowseDefinitionDataService } from './browse-definition-data.service';
|
import { BrowseDefinitionDataService } from './browse-definition-data.service';
|
||||||
import { BrowseEntrySearchOptions } from './browse-entry-search-options.model';
|
import { BrowseEntrySearchOptions } from './browse-entry-search-options.model';
|
||||||
|
|
||||||
export const BROWSE_LINKS_TO_FOLLOW: FollowLinkConfig<BrowseEntry | Item>[] = [
|
export function getBrowseLinksToFollow(): FollowLinkConfig<BrowseEntry | Item>[] {
|
||||||
followLink('thumbnail'),
|
const followLinks = [
|
||||||
];
|
followLink('thumbnail'),
|
||||||
|
];
|
||||||
|
if (environment.item.showAccessStatuses) {
|
||||||
|
followLinks.push(followLink('accessStatus'));
|
||||||
|
}
|
||||||
|
return followLinks;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The service handling all browse requests
|
* The service handling all browse requests
|
||||||
@@ -67,7 +73,6 @@ export class BrowseService {
|
|||||||
protected halService: HALEndpointService,
|
protected halService: HALEndpointService,
|
||||||
private browseDefinitionDataService: BrowseDefinitionDataService,
|
private browseDefinitionDataService: BrowseDefinitionDataService,
|
||||||
private hrefOnlyDataService: HrefOnlyDataService,
|
private hrefOnlyDataService: HrefOnlyDataService,
|
||||||
private rdb: RemoteDataBuildService,
|
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +122,7 @@ export class BrowseService {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
if (options.fetchThumbnail ) {
|
if (options.fetchThumbnail ) {
|
||||||
return this.hrefOnlyDataService.findListByHref<BrowseEntry>(href$, {}, undefined, undefined, ...BROWSE_LINKS_TO_FOLLOW);
|
return this.hrefOnlyDataService.findListByHref<BrowseEntry>(href$, {}, undefined, undefined, ...getBrowseLinksToFollow());
|
||||||
}
|
}
|
||||||
return this.hrefOnlyDataService.findListByHref<BrowseEntry>(href$);
|
return this.hrefOnlyDataService.findListByHref<BrowseEntry>(href$);
|
||||||
}
|
}
|
||||||
@@ -165,7 +170,7 @@ export class BrowseService {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
if (options.fetchThumbnail) {
|
if (options.fetchThumbnail) {
|
||||||
return this.hrefOnlyDataService.findListByHref<Item>(href$, {}, undefined, undefined, ...BROWSE_LINKS_TO_FOLLOW);
|
return this.hrefOnlyDataService.findListByHref<Item>(href$, {}, undefined, undefined, ...getBrowseLinksToFollow());
|
||||||
}
|
}
|
||||||
return this.hrefOnlyDataService.findListByHref<Item>(href$);
|
return this.hrefOnlyDataService.findListByHref<Item>(href$);
|
||||||
}
|
}
|
||||||
|
31
src/app/core/cache/object-cache.reducer.ts
vendored
31
src/app/core/cache/object-cache.reducer.ts
vendored
@@ -174,20 +174,25 @@ export function objectCacheReducer(state = initialState, action: ObjectCacheActi
|
|||||||
* the new state, with the object added, or overwritten.
|
* the new state, with the object added, or overwritten.
|
||||||
*/
|
*/
|
||||||
function addToObjectCache(state: ObjectCacheState, action: AddToObjectCacheAction): ObjectCacheState {
|
function addToObjectCache(state: ObjectCacheState, action: AddToObjectCacheAction): ObjectCacheState {
|
||||||
const existing = state[action.payload.objectToCache._links.self.href] || {} as any;
|
const cacheLink = hasValue(action.payload.objectToCache?._links?.self) ? action.payload.objectToCache._links.self.href : action.payload.alternativeLink;
|
||||||
|
const existing = state[cacheLink] || {} as any;
|
||||||
const newAltLinks = hasValue(action.payload.alternativeLink) ? [action.payload.alternativeLink] : [];
|
const newAltLinks = hasValue(action.payload.alternativeLink) ? [action.payload.alternativeLink] : [];
|
||||||
return Object.assign({}, state, {
|
if (hasValue(cacheLink)) {
|
||||||
[action.payload.objectToCache._links.self.href]: {
|
return Object.assign({}, state, {
|
||||||
data: action.payload.objectToCache,
|
[cacheLink]: {
|
||||||
timeCompleted: action.payload.timeCompleted,
|
data: action.payload.objectToCache,
|
||||||
msToLive: action.payload.msToLive,
|
timeCompleted: action.payload.timeCompleted,
|
||||||
requestUUIDs: [action.payload.requestUUID, ...(existing.requestUUIDs || [])],
|
msToLive: action.payload.msToLive,
|
||||||
dependentRequestUUIDs: existing.dependentRequestUUIDs || [],
|
requestUUIDs: [action.payload.requestUUID, ...(existing.requestUUIDs || [])],
|
||||||
isDirty: isNotEmpty(existing.patches),
|
dependentRequestUUIDs: existing.dependentRequestUUIDs || [],
|
||||||
patches: existing.patches || [],
|
isDirty: isNotEmpty(existing.patches),
|
||||||
alternativeLinks: [...(existing.alternativeLinks || []), ...newAltLinks],
|
patches: existing.patches || [],
|
||||||
} as ObjectCacheEntry,
|
alternativeLinks: [...(existing.alternativeLinks || []), ...newAltLinks],
|
||||||
});
|
} as ObjectCacheEntry,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
16
src/app/core/cache/object-cache.service.ts
vendored
16
src/app/core/cache/object-cache.service.ts
vendored
@@ -99,7 +99,9 @@ export class ObjectCacheService {
|
|||||||
* An optional alternative link to this object
|
* An optional alternative link to this object
|
||||||
*/
|
*/
|
||||||
add(object: CacheableObject, msToLive: number, requestUUID: string, alternativeLink?: string): void {
|
add(object: CacheableObject, msToLive: number, requestUUID: string, alternativeLink?: string): void {
|
||||||
object = this.linkService.removeResolvedLinks(object); // Ensure the object we're storing has no resolved links
|
if (hasValue(object)) {
|
||||||
|
object = this.linkService.removeResolvedLinks(object); // Ensure the object we're storing has no resolved links
|
||||||
|
}
|
||||||
this.store.dispatch(new AddToObjectCacheAction(object, new Date().getTime(), msToLive, requestUUID, alternativeLink));
|
this.store.dispatch(new AddToObjectCacheAction(object, new Date().getTime(), msToLive, requestUUID, alternativeLink));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,11 +177,15 @@ export class ObjectCacheService {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
map((entry: ObjectCacheEntry) => {
|
map((entry: ObjectCacheEntry) => {
|
||||||
const type: GenericConstructor<T> = getClassForType((entry.data as any).type);
|
if (hasValue(entry.data)) {
|
||||||
if (typeof type !== 'function') {
|
const type: GenericConstructor<T> = getClassForType((entry.data as any).type);
|
||||||
throw new Error(`${type} is not a valid constructor for ${JSON.stringify(entry.data)}`);
|
if (typeof type !== 'function') {
|
||||||
|
throw new Error(`${type} is not a valid constructor for ${JSON.stringify(entry.data)}`);
|
||||||
|
}
|
||||||
|
return Object.assign(new type(), entry.data) as T;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return Object.assign(new type(), entry.data) as T;
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -120,6 +120,13 @@ export class DspaceRestResponseParsingService implements ResponseParsingService
|
|||||||
if (hasValue(match)) {
|
if (hasValue(match)) {
|
||||||
embedAltUrl = new URLCombiner(embedAltUrl, `?size=${match.size}`).toString();
|
embedAltUrl = new URLCombiner(embedAltUrl, `?size=${match.size}`).toString();
|
||||||
}
|
}
|
||||||
|
if (data._embedded[property] == null) {
|
||||||
|
// Embedded object is null, meaning it exists (not undefined), but had an empty response (204) -> cache it as null
|
||||||
|
this.addToObjectCache(null, request, data, embedAltUrl);
|
||||||
|
} else if (!isCacheableObject(data._embedded[property])) {
|
||||||
|
// Embedded object exists, but doesn't contain a self link -> cache it using the alternative link instead
|
||||||
|
this.objectCache.add(data._embedded[property], hasValue(request.responseMsToLive) ? request.responseMsToLive : environment.cache.msToLive.default, request.uuid, embedAltUrl);
|
||||||
|
}
|
||||||
this.process<ObjectDomain>(data._embedded[property], request, embedAltUrl);
|
this.process<ObjectDomain>(data._embedded[property], request, embedAltUrl);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -237,7 +244,7 @@ export class DspaceRestResponseParsingService implements ResponseParsingService
|
|||||||
* @param alternativeURL an alternative url that can be used to retrieve the object
|
* @param alternativeURL an alternative url that can be used to retrieve the object
|
||||||
*/
|
*/
|
||||||
addToObjectCache(co: CacheableObject, request: RestRequest, data: any, alternativeURL?: string): void {
|
addToObjectCache(co: CacheableObject, request: RestRequest, data: any, alternativeURL?: string): void {
|
||||||
if (!isCacheableObject(co)) {
|
if (hasValue(co) && !isCacheableObject(co)) {
|
||||||
const type = hasValue(data) && hasValue(data.type) ? data.type : 'object';
|
const type = hasValue(data) && hasValue(data.type) ? data.type : 'object';
|
||||||
let dataJSON: string;
|
let dataJSON: string;
|
||||||
if (hasValue(data._embedded)) {
|
if (hasValue(data._embedded)) {
|
||||||
@@ -251,7 +258,7 @@ export class DspaceRestResponseParsingService implements ResponseParsingService
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (alternativeURL === co._links.self.href) {
|
if (hasValue(co) && alternativeURL === co._links.self.href) {
|
||||||
alternativeURL = undefined;
|
alternativeURL = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,6 +3,8 @@ import { Store } from '@ngrx/store';
|
|||||||
import { provideMockStore } from '@ngrx/store/testing';
|
import { provideMockStore } from '@ngrx/store/testing';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
|
import { APP_CONFIG } from '../../../config/app-config.interface';
|
||||||
|
import { environment } from '../../../environments/environment.test';
|
||||||
import { PAGINATED_RELATIONS_TO_ITEMS_OPERATOR } from '../../item-page/simple/item-types/shared/item-relationships-utils';
|
import { PAGINATED_RELATIONS_TO_ITEMS_OPERATOR } from '../../item-page/simple/item-types/shared/item-relationships-utils';
|
||||||
import { getMockRemoteDataBuildServiceHrefMap } from '../../shared/mocks/remote-data-build.service.mock';
|
import { getMockRemoteDataBuildServiceHrefMap } from '../../shared/mocks/remote-data-build.service.mock';
|
||||||
import { getMockRequestService } from '../../shared/mocks/request.service.mock';
|
import { getMockRequestService } from '../../shared/mocks/request.service.mock';
|
||||||
@@ -150,6 +152,7 @@ describe('RelationshipDataService', () => {
|
|||||||
{ provide: RequestService, useValue: requestService },
|
{ provide: RequestService, useValue: requestService },
|
||||||
{ provide: PAGINATED_RELATIONS_TO_ITEMS_OPERATOR, useValue: jasmine.createSpy('paginatedRelationsToItems').and.returnValue((v) => v) },
|
{ provide: PAGINATED_RELATIONS_TO_ITEMS_OPERATOR, useValue: jasmine.createSpy('paginatedRelationsToItems').and.returnValue((v) => v) },
|
||||||
{ provide: Store, useValue: provideMockStore() },
|
{ provide: Store, useValue: provideMockStore() },
|
||||||
|
{ provide: APP_CONFIG, useValue: environment },
|
||||||
RelationshipDataService,
|
RelationshipDataService,
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@@ -157,7 +160,7 @@ describe('RelationshipDataService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('composition', () => {
|
describe('composition', () => {
|
||||||
const initService = () => new RelationshipDataService(null, null, null, null, null, null, null, null);
|
const initService = () => new RelationshipDataService(null, null, null, null, null, null, null, null, environment);
|
||||||
|
|
||||||
testSearchDataImplementation(initService);
|
testSearchDataImplementation(initService);
|
||||||
});
|
});
|
||||||
|
@@ -24,6 +24,10 @@ import {
|
|||||||
tap,
|
tap,
|
||||||
} from 'rxjs/operators';
|
} from 'rxjs/operators';
|
||||||
|
|
||||||
|
import {
|
||||||
|
APP_CONFIG,
|
||||||
|
AppConfig,
|
||||||
|
} from '../../../config/app-config.interface';
|
||||||
import {
|
import {
|
||||||
AppState,
|
AppState,
|
||||||
keySelector,
|
keySelector,
|
||||||
@@ -133,6 +137,7 @@ export class RelationshipDataService extends IdentifiableDataService<Relationshi
|
|||||||
protected itemService: ItemDataService,
|
protected itemService: ItemDataService,
|
||||||
protected appStore: Store<AppState>,
|
protected appStore: Store<AppState>,
|
||||||
@Inject(PAGINATED_RELATIONS_TO_ITEMS_OPERATOR) private paginatedRelationsToItems: (thisId: string) => (source: Observable<RemoteData<PaginatedList<Relationship>>>) => Observable<RemoteData<PaginatedList<Item>>>,
|
@Inject(PAGINATED_RELATIONS_TO_ITEMS_OPERATOR) private paginatedRelationsToItems: (thisId: string) => (source: Observable<RemoteData<PaginatedList<Relationship>>>) => Observable<RemoteData<PaginatedList<Item>>>,
|
||||||
|
@Inject(APP_CONFIG) private appConfig: AppConfig,
|
||||||
) {
|
) {
|
||||||
super('relationships', requestService, rdbService, objectCache, halService, 15 * 60 * 1000);
|
super('relationships', requestService, rdbService, objectCache, halService, 15 * 60 * 1000);
|
||||||
|
|
||||||
@@ -319,7 +324,7 @@ export class RelationshipDataService extends IdentifiableDataService<Relationshi
|
|||||||
* @param options
|
* @param options
|
||||||
*/
|
*/
|
||||||
getRelatedItemsByLabel(item: Item, label: string, options?: FindListOptions): Observable<RemoteData<PaginatedList<Item>>> {
|
getRelatedItemsByLabel(item: Item, label: string, options?: FindListOptions): Observable<RemoteData<PaginatedList<Item>>> {
|
||||||
const linksToFollow: FollowLinkConfig<Relationship>[] = itemLinksToFollow(options.fetchThumbnail);
|
const linksToFollow: FollowLinkConfig<Relationship>[] = itemLinksToFollow(options.fetchThumbnail, this.appConfig.item.showAccessStatuses);
|
||||||
linksToFollow.push(followLink('relationshipType'));
|
linksToFollow.push(followLink('relationshipType'));
|
||||||
|
|
||||||
return this.getItemRelationshipsByLabel(item, label, options, true, true, ...linksToFollow).pipe(this.paginatedRelationsToItems(item.uuid));
|
return this.getItemRelationshipsByLabel(item, label, options, true, true, ...linksToFollow).pipe(this.paginatedRelationsToItems(item.uuid));
|
||||||
|
@@ -190,7 +190,7 @@ export class VersionHistoryDataService extends IdentifiableDataService<VersionHi
|
|||||||
return this.versionDataService.findByHref(versionHref, false, true, followLink('versionhistory')).pipe(
|
return this.versionDataService.findByHref(versionHref, false, true, followLink('versionhistory')).pipe(
|
||||||
getFirstCompletedRemoteData(),
|
getFirstCompletedRemoteData(),
|
||||||
switchMap((versionRD: RemoteData<Version>) => {
|
switchMap((versionRD: RemoteData<Version>) => {
|
||||||
if (versionRD.hasSucceeded && !versionRD.hasNoContent) {
|
if (versionRD.hasSucceeded && !versionRD.hasNoContent && hasValue(versionRD.payload)) {
|
||||||
return versionRD.payload.versionhistory.pipe(
|
return versionRD.payload.versionhistory.pipe(
|
||||||
getFirstCompletedRemoteData(),
|
getFirstCompletedRemoteData(),
|
||||||
map((versionHistoryRD: RemoteData<VersionHistory>) => {
|
map((versionHistoryRD: RemoteData<VersionHistory>) => {
|
||||||
|
@@ -45,7 +45,7 @@ export class UUIDIndexEffects {
|
|||||||
addObject$ = createEffect(() => this.actions$
|
addObject$ = createEffect(() => this.actions$
|
||||||
.pipe(
|
.pipe(
|
||||||
ofType(ObjectCacheActionTypes.ADD),
|
ofType(ObjectCacheActionTypes.ADD),
|
||||||
filter((action: AddToObjectCacheAction) => hasValue(action.payload.objectToCache.uuid)),
|
filter((action: AddToObjectCacheAction) => hasValue(action.payload.objectToCache) && hasValue(action.payload.objectToCache.uuid)),
|
||||||
map((action: AddToObjectCacheAction) => {
|
map((action: AddToObjectCacheAction) => {
|
||||||
return new AddToIndexAction(
|
return new AddToIndexAction(
|
||||||
IndexName.OBJECT,
|
IndexName.OBJECT,
|
||||||
@@ -64,7 +64,7 @@ export class UUIDIndexEffects {
|
|||||||
ofType(ObjectCacheActionTypes.ADD),
|
ofType(ObjectCacheActionTypes.ADD),
|
||||||
map((action: AddToObjectCacheAction) => {
|
map((action: AddToObjectCacheAction) => {
|
||||||
const alternativeLink = action.payload.alternativeLink;
|
const alternativeLink = action.payload.alternativeLink;
|
||||||
const selfLink = action.payload.objectToCache._links.self.href;
|
const selfLink = hasValue(action.payload.objectToCache?._links?.self) ? action.payload.objectToCache._links.self.href : alternativeLink;
|
||||||
if (hasValue(alternativeLink) && alternativeLink !== selfLink) {
|
if (hasValue(alternativeLink) && alternativeLink !== selfLink) {
|
||||||
return new AddToIndexAction(
|
return new AddToIndexAction(
|
||||||
IndexName.ALTERNATIVE_OBJECT_LINK,
|
IndexName.ALTERNATIVE_OBJECT_LINK,
|
||||||
|
@@ -98,6 +98,9 @@ export class RecentItemListComponent implements OnInit, OnDestroy {
|
|||||||
if (this.appConfig.browseBy.showThumbnails) {
|
if (this.appConfig.browseBy.showThumbnails) {
|
||||||
linksToFollow.push(followLink('thumbnail'));
|
linksToFollow.push(followLink('thumbnail'));
|
||||||
}
|
}
|
||||||
|
if (this.appConfig.item.showAccessStatuses) {
|
||||||
|
linksToFollow.push(followLink('accessStatus'));
|
||||||
|
}
|
||||||
|
|
||||||
this.itemRD$ = this.searchService.search(
|
this.itemRD$ = this.searchService.search(
|
||||||
new PaginatedSearchOptions({
|
new PaginatedSearchOptions({
|
||||||
|
@@ -33,7 +33,7 @@ import { getAllSucceededRemoteData } from '../../../core/shared/operators';
|
|||||||
import { hasValue } from '../../../shared/empty.util';
|
import { hasValue } from '../../../shared/empty.util';
|
||||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
import { AbstractTrackableComponent } from '../../../shared/trackable/abstract-trackable.component';
|
import { AbstractTrackableComponent } from '../../../shared/trackable/abstract-trackable.component';
|
||||||
import { ITEM_PAGE_LINKS_TO_FOLLOW } from '../../item.resolver';
|
import { getItemPageLinksToFollow } from '../../item.resolver';
|
||||||
import { getItemPageRoute } from '../../item-page-routing-paths';
|
import { getItemPageRoute } from '../../item-page-routing-paths';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -92,7 +92,7 @@ export class AbstractItemUpdateComponent extends AbstractTrackableComponent impl
|
|||||||
this.item = rd.payload;
|
this.item = rd.payload;
|
||||||
}),
|
}),
|
||||||
switchMap((rd: RemoteData<Item>) => {
|
switchMap((rd: RemoteData<Item>) => {
|
||||||
return this.itemService.findByHref(rd.payload._links.self.href, true, true, ...ITEM_PAGE_LINKS_TO_FOLLOW);
|
return this.itemService.findByHref(rd.payload._links.self.href, true, true, ...getItemPageLinksToFollow());
|
||||||
}),
|
}),
|
||||||
getAllSucceededRemoteData(),
|
getAllSucceededRemoteData(),
|
||||||
).subscribe((rd: RemoteData<Item>) => {
|
).subscribe((rd: RemoteData<Item>) => {
|
||||||
|
@@ -320,8 +320,8 @@ export class ItemDeleteComponent
|
|||||||
this.linkService.resolveLinks(
|
this.linkService.resolveLinks(
|
||||||
relationship,
|
relationship,
|
||||||
followLink('relationshipType'),
|
followLink('relationshipType'),
|
||||||
followLink('leftItem'),
|
followLink('leftItem', undefined, followLink<Item>('accessStatus')),
|
||||||
followLink('rightItem'),
|
followLink('rightItem', undefined, followLink<Item>('accessStatus')),
|
||||||
);
|
);
|
||||||
return relationship.relationshipType.pipe(
|
return relationship.relationshipType.pipe(
|
||||||
getFirstSucceededRemoteData(),
|
getFirstSucceededRemoteData(),
|
||||||
|
@@ -478,7 +478,7 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// this adds thumbnail images when required by configuration
|
// this adds thumbnail images when required by configuration
|
||||||
const linksToFollow: FollowLinkConfig<Relationship>[] = itemLinksToFollow(this.fetchThumbnail);
|
const linksToFollow: FollowLinkConfig<Relationship>[] = itemLinksToFollow(this.fetchThumbnail, this.appConfig.item.showAccessStatuses);
|
||||||
|
|
||||||
this.subs.push(
|
this.subs.push(
|
||||||
observableCombineLatest([
|
observableCombineLatest([
|
||||||
|
@@ -18,7 +18,7 @@ import { redirectOn4xx } from '../core/shared/authorized.operators';
|
|||||||
import { Item } from '../core/shared/item.model';
|
import { Item } from '../core/shared/item.model';
|
||||||
import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
||||||
import { hasValue } from '../shared/empty.util';
|
import { hasValue } from '../shared/empty.util';
|
||||||
import { ITEM_PAGE_LINKS_TO_FOLLOW } from './item.resolver';
|
import { getItemPageLinksToFollow } from './item.resolver';
|
||||||
import { getItemPageRoute } from './item-page-routing-paths';
|
import { getItemPageRoute } from './item-page-routing-paths';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -44,7 +44,7 @@ export const itemPageResolver: ResolveFn<RemoteData<Item>> = (
|
|||||||
route.params.id,
|
route.params.id,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
...ITEM_PAGE_LINKS_TO_FOLLOW,
|
...getItemPageLinksToFollow(),
|
||||||
).pipe(
|
).pipe(
|
||||||
getFirstCompletedRemoteData(),
|
getFirstCompletedRemoteData(),
|
||||||
redirectOn4xx(router, authService),
|
redirectOn4xx(router, authService),
|
||||||
|
@@ -7,6 +7,7 @@ import {
|
|||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
import { environment } from '../../environments/environment';
|
||||||
import { AppState } from '../app.reducer';
|
import { AppState } from '../app.reducer';
|
||||||
import { ItemDataService } from '../core/data/item-data.service';
|
import { ItemDataService } from '../core/data/item-data.service';
|
||||||
import { RemoteData } from '../core/data/remote-data';
|
import { RemoteData } from '../core/data/remote-data';
|
||||||
@@ -22,15 +23,21 @@ import {
|
|||||||
* The self links defined in this list are expected to be requested somewhere in the near future
|
* The self links defined in this list are expected to be requested somewhere in the near future
|
||||||
* Requesting them as embeds will limit the number of requests
|
* Requesting them as embeds will limit the number of requests
|
||||||
*/
|
*/
|
||||||
export const ITEM_PAGE_LINKS_TO_FOLLOW: FollowLinkConfig<Item>[] = [
|
export function getItemPageLinksToFollow(): FollowLinkConfig<Item>[] {
|
||||||
followLink('owningCollection', {},
|
const followLinks: FollowLinkConfig<Item>[] = [
|
||||||
followLink('parentCommunity', {},
|
followLink('owningCollection', {},
|
||||||
followLink('parentCommunity')),
|
followLink('parentCommunity', {},
|
||||||
),
|
followLink('parentCommunity')),
|
||||||
followLink('relationships'),
|
),
|
||||||
followLink('version', {}, followLink('versionhistory')),
|
followLink('relationships'),
|
||||||
followLink('thumbnail'),
|
followLink('version', {}, followLink('versionhistory')),
|
||||||
];
|
followLink('thumbnail'),
|
||||||
|
];
|
||||||
|
if (environment.item.showAccessStatuses) {
|
||||||
|
followLinks.push(followLink('accessStatus'));
|
||||||
|
}
|
||||||
|
return followLinks;
|
||||||
|
}
|
||||||
|
|
||||||
export const itemResolver: ResolveFn<RemoteData<Item>> = (
|
export const itemResolver: ResolveFn<RemoteData<Item>> = (
|
||||||
route: ActivatedRouteSnapshot,
|
route: ActivatedRouteSnapshot,
|
||||||
@@ -42,7 +49,7 @@ export const itemResolver: ResolveFn<RemoteData<Item>> = (
|
|||||||
route.params.id,
|
route.params.id,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
...ITEM_PAGE_LINKS_TO_FOLLOW,
|
...getItemPageLinksToFollow(),
|
||||||
).pipe(
|
).pipe(
|
||||||
getFirstCompletedRemoteData(),
|
getFirstCompletedRemoteData(),
|
||||||
);
|
);
|
||||||
|
@@ -280,7 +280,7 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
|
|||||||
const relationship$ = this.relationshipService.findById(this.metadataService.virtualValue(this.value),
|
const relationship$ = this.relationshipService.findById(this.metadataService.virtualValue(this.value),
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
... itemLinksToFollow(this.fetchThumbnail)).pipe(
|
... itemLinksToFollow(this.fetchThumbnail, this.appConfig.item.showAccessStatuses)).pipe(
|
||||||
getAllSucceededRemoteData(),
|
getAllSucceededRemoteData(),
|
||||||
getRemoteDataPayload());
|
getRemoteDataPayload());
|
||||||
this.relationshipValue$ = observableCombineLatest([this.item$.pipe(take(1)), relationship$]).pipe(
|
this.relationshipValue$ = observableCombineLatest([this.item$.pipe(take(1)), relationship$]).pipe(
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import { Item } from '../../core/shared/item.model';
|
||||||
import { Relationship } from '../../core/shared/item-relationships/relationship.model';
|
import { Relationship } from '../../core/shared/item-relationships/relationship.model';
|
||||||
import {
|
import {
|
||||||
followLink,
|
followLink,
|
||||||
@@ -24,19 +25,22 @@ export function getFilterByRelation(relationType: string, itemUUID: string): str
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates links to follow for the leftItem and rightItem. Links will include
|
* Creates links to follow for the leftItem and rightItem. Optionally additional links for `thumbnail` & `accessStatus`
|
||||||
* @param showThumbnail thumbnail image configuration
|
* can be embedded as well.
|
||||||
* @returns followLink array
|
*
|
||||||
|
* @param showThumbnail Whether the `thumbnail` needs to be embedded on the {@link Item}
|
||||||
|
* @param showAccessStatus Whether the `accessStatus` needs to be embedded on the {@link Item}
|
||||||
*/
|
*/
|
||||||
export function itemLinksToFollow(showThumbnail: boolean): FollowLinkConfig<Relationship>[] {
|
export function itemLinksToFollow(showThumbnail: boolean, showAccessStatus: boolean): FollowLinkConfig<Relationship>[] {
|
||||||
let linksToFollow: FollowLinkConfig<Relationship>[];
|
const conditionalLinksToFollow: FollowLinkConfig<Item>[] = [];
|
||||||
if (showThumbnail) {
|
if (showThumbnail) {
|
||||||
linksToFollow = [
|
conditionalLinksToFollow.push(followLink<Item>('thumbnail'));
|
||||||
followLink('leftItem',{}, followLink('thumbnail')),
|
|
||||||
followLink('rightItem',{}, followLink('thumbnail')),
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
linksToFollow = [followLink('leftItem'), followLink('rightItem')];
|
|
||||||
}
|
}
|
||||||
return linksToFollow;
|
if (showAccessStatus) {
|
||||||
|
conditionalLinksToFollow.push(followLink<Item>('accessStatus'));
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
followLink('leftItem', undefined, ...conditionalLinksToFollow),
|
||||||
|
followLink('rightItem', undefined, ...conditionalLinksToFollow),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user