Cache redesign part 2

This commit is contained in:
Art Lowel
2021-01-20 14:34:08 +01:00
parent 64ba6293c9
commit c66de4fe91
155 changed files with 1610 additions and 1637 deletions

View File

@@ -15,8 +15,8 @@ import { EpersonDtoModel } from '../../../core/eperson/models/eperson-dto.model'
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
import {
getAllSucceededRemoteDataPayload,
getFirstCompletedRemoteData
getFirstCompletedRemoteData,
getAllSucceededRemoteData
} from '../../../core/shared/operators';
import { ConfirmationModalComponent } from '../../../shared/confirmation-modal/confirmation-modal.component';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
@@ -39,7 +39,7 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
/**
* A list of all the current EPeople within the repository or the result of the search
*/
ePeople$: BehaviorSubject<RemoteData<PaginatedList<EPerson>>> = new BehaviorSubject<RemoteData<PaginatedList<EPerson>>>({} as any);
ePeople$: BehaviorSubject<PaginatedList<EPerson>> = new BehaviorSubject(buildPaginatedList<EPerson>(new PageInfo(), []));
/**
* A BehaviorSubject with the list of EpersonDtoModel objects made from the EPeople in the repository or
* as the result of the search
@@ -72,6 +72,11 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
currentSearchQuery: string;
currentSearchScope: string;
/**
* The subscription for the search method
*/
searchSub: Subscription;
/**
* List of subscriptions
*/
@@ -108,6 +113,29 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
this.isEPersonFormShown = true;
}
}));
this.subs.push(this.ePeople$.pipe(
switchMap((epeople: PaginatedList<EPerson>) => {
if (epeople.pageInfo.totalElements > 0) {
return combineLatest(...epeople.page.map((eperson) => {
return this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(eperson) ? eperson.self : undefined).pipe(
map((authorized) => {
const epersonDtoModel: EpersonDtoModel = new EpersonDtoModel();
epersonDtoModel.ableToDelete = authorized;
epersonDtoModel.eperson = eperson;
return epersonDtoModel;
})
);
})).pipe(map((dtos: EpersonDtoModel[]) => {
return buildPaginatedList(epeople.pageInfo, dtos);
}));
} else {
// if it's empty, simply forward the empty list
return [epeople];
}
})).subscribe((value: PaginatedList<EpersonDtoModel>) => {
this.ePeopleDto$.next(value);
this.pageInfoState$.next(value.pageInfo);
}));
}
/**
@@ -138,34 +166,21 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
this.currentSearchScope = scope;
this.config.currentPage = 1;
}
this.subs.push(this.epersonService.searchByScope(this.currentSearchScope, this.currentSearchQuery, {
if (hasValue(this.searchSub)) {
this.searchSub.unsubscribe();
this.subs = this.subs.filter((sub: Subscription) => sub !== this.searchSub);
}
this.searchSub = this.epersonService.searchByScope(this.currentSearchScope, this.currentSearchQuery, {
currentPage: this.config.currentPage,
elementsPerPage: this.config.pageSize
}).subscribe((peopleRD) => {
this.ePeople$.next(peopleRD);
}).pipe(
getAllSucceededRemoteData(),
).subscribe((peopleRD) => {
this.ePeople$.next(peopleRD.payload);
this.pageInfoState$.next(peopleRD.payload.pageInfo);
}
));
this.subs.push(this.ePeople$.pipe(
getAllSucceededRemoteDataPayload(),
switchMap((epeople) => {
return combineLatest(...epeople.page.map((eperson) => {
return this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(eperson) ? eperson.self : undefined).pipe(
map((authorized) => {
const epersonDtoModel: EpersonDtoModel = new EpersonDtoModel();
epersonDtoModel.ableToDelete = authorized;
epersonDtoModel.eperson = eperson;
return epersonDtoModel;
})
);
})).pipe(map((dtos: EpersonDtoModel[]) => {
return buildPaginatedList(epeople.pageInfo, dtos);
}));
})).subscribe((value) => {
this.ePeopleDto$.next(value);
this.pageInfoState$.next(value.pageInfo);
}));
);
this.subs.push(this.searchSub);
}
/**
@@ -224,7 +239,8 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
this.notificationsService.error('Error occured when trying to delete EPerson with id: ' + ePerson.id + ' with code: ' + restResponse.statusCode + ' and message: ' + restResponse.errorMessage);
}
});
}}
}
}
});
}
}
@@ -261,16 +277,13 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
}
/**
* This method will ensure that the page gets reset and that the cache is cleared
* This method will set everything to stale, which will cause the lists on this page to update.
*/
reset() {
this.epersonService.getBrowseEndpoint().pipe(
switchMap((href) => this.requestService.removeByHrefSubstring(href)),
filter((isCached) => isCached),
take(1)
).subscribe(() => {
this.cleanupSubscribes();
this.initialisePage();
take(1)
).subscribe((href: string) => {
this.requestService.setStaleByHrefSubstring(href);
});
}
}

View File

@@ -345,7 +345,7 @@ export class GroupFormComponent implements OnInit, OnDestroy {
this.groupDataService.getActiveGroup().pipe(take(1)).subscribe((activeGroup: Group) => {
if (activeGroup === null) {
this.groupDataService.cancelEditGroup();
this.groupDataService.findByHref(groupSelfLink, false, followLink('subgroups'), followLink('epersons'), followLink('object'))
this.groupDataService.findByHref(groupSelfLink, false, false, followLink('subgroups'), followLink('epersons'), followLink('object'))
.pipe(
getFirstSucceededRemoteData(),
getRemoteDataPayload())

View File

@@ -160,7 +160,7 @@ export class MembersListComponent implements OnInit, OnDestroy {
if (group != null) {
return this.ePersonDataService.findAllByHref(group._links.epersons.href, {
currentPage: 0,
elementsPerPage: Number.MAX_SAFE_INTEGER
elementsPerPage: 9999
})
.pipe(
getFirstSucceededRemoteData(),
@@ -206,12 +206,11 @@ export class MembersListComponent implements OnInit, OnDestroy {
if (ePersonToUpdate != null) {
this.ePersonDataService.clearLinkRequests(ePersonToUpdate._links.groups.href);
}
this.ePersonDataService.clearLinkRequests(activeGroup._links.epersons.href);
this.router.navigateByUrl(this.groupDataService.getGroupEditPageRouterLink(activeGroup));
this.ePeopleMembersOfGroup = this.ePersonDataService.findAllByHref(activeGroup._links.epersons.href, {
currentPage: this.configSearch.currentPage,
elementsPerPage: this.configSearch.pageSize
});
}, false);
}
/**

View File

@@ -125,7 +125,7 @@ export class SubgroupsListComponent implements OnInit, OnDestroy {
} else {
return this.groupDataService.findAllByHref(activeGroup._links.subgroups.href, {
currentPage: 0,
elementsPerPage: Number.MAX_SAFE_INTEGER
elementsPerPage: 9999
})
.pipe(
getFirstSucceededRemoteData(),
@@ -212,12 +212,11 @@ export class SubgroupsListComponent implements OnInit, OnDestroy {
* @param activeGroup Group currently being edited
*/
public forceUpdateGroups(activeGroup: Group) {
this.groupDataService.clearGroupLinkRequests(activeGroup._links.subgroups.href);
this.router.navigateByUrl(this.groupDataService.getGroupEditPageRouterLink(activeGroup));
this.subgroupsOfGroup = this.groupDataService.findAllByHref(activeGroup._links.subgroups.href, {
currentPage: this.config.currentPage,
elementsPerPage: this.config.pageSize
});
}, false);
}
/**

View File

@@ -89,8 +89,9 @@ export class MetadataSchemaComponent implements OnInit {
this.metadataFields$ = combineLatest(this.metadataSchema$, this.needsUpdate$).pipe(
switchMap(([schema, update]: [MetadataSchema, boolean]) => {
if (update) {
return this.registryService.getMetadataFieldsBySchema(schema, toFindListOptions(this.config), true, followLink('schema'));
this.needsUpdate$.next(false);
}
return this.registryService.getMetadataFieldsBySchema(schema, toFindListOptions(this.config), !update, false, followLink('schema'));
})
);
}

View File

@@ -23,7 +23,7 @@ export class BitstreamPageResolver implements Resolve<RemoteData<Bitstream>> {
* or an error if something went wrong
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Bitstream>> {
return this.bitstreamService.findById(route.params.id, false, ...this.followLinks)
return this.bitstreamService.findById(route.params.id, true, false, ...this.followLinks)
.pipe(
getFirstCompletedRemoteData(),
);
@@ -35,7 +35,7 @@ export class BitstreamPageResolver implements Resolve<RemoteData<Bitstream>> {
*/
get followLinks(): FollowLinkConfig<Bitstream>[] {
return [
followLink('bundle', undefined, true, followLink('item')),
followLink('bundle', undefined, true, true, true, followLink('item')),
followLink('format')
];
}

View File

@@ -57,6 +57,9 @@ describe('CollectionItemMapperComponent', () => {
id: 'ce41d451-97ed-4a9c-94a1-7de34f16a9f4',
name: 'test-collection',
_links: {
mappedItems: {
href: 'https://rest.api/collections/ce41d451-97ed-4a9c-94a1-7de34f16a9f4/mappedItems'
},
self: {
href: 'https://rest.api/collections/ce41d451-97ed-4a9c-94a1-7de34f16a9f4'
}
@@ -82,8 +85,10 @@ describe('CollectionItemMapperComponent', () => {
const searchConfigServiceStub = {
paginatedSearchOptions: mockSearchOptions
};
const emptyList = createSuccessfulRemoteDataObject(createPaginatedList([]));
const itemDataServiceStub = {
mapToCollection: () => createSuccessfulRemoteDataObject$({})
mapToCollection: () => createSuccessfulRemoteDataObject$({}),
findAllByHref: () => of(emptyList)
};
const activatedRouteStub = new ActivatedRouteStub({}, { dso: mockCollectionRD });
const translateServiceStub = {
@@ -92,7 +97,6 @@ describe('CollectionItemMapperComponent', () => {
onTranslationChange: new EventEmitter(),
onDefaultLangChange: new EventEmitter()
};
const emptyList = createSuccessfulRemoteDataObject(createPaginatedList([]));
const searchServiceStub = Object.assign(new SearchServiceStub(), {
search: () => of(emptyList),
/* tslint:disable:no-empty */

View File

@@ -124,10 +124,11 @@ export class CollectionItemMapperComponent implements OnInit {
this.collectionItemsRD$ = collectionAndOptions$.pipe(
switchMap(([collectionRD, options, shouldUpdate]) => {
if (shouldUpdate) {
return this.collectionDataService.getMappedItems(collectionRD.payload.id, Object.assign(options, {
sort: this.defaultSortOptions
}),followLink('owningCollection'));
this.shouldUpdate$.next(false);
}
return this.itemDataService.findAllByHref(collectionRD.payload._links.mappedItems.href, Object.assign(options, {
sort: this.defaultSortOptions
}),!shouldUpdate, true, followLink('owningCollection'));
})
);
this.mappedItemsRD$ = collectionAndOptions$.pipe(

View File

@@ -23,7 +23,7 @@ export class CollectionPageResolver implements Resolve<RemoteData<Collection>> {
* or an error if something went wrong
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Collection>> {
return this.collectionService.findById(route.params.id, false, followLink('logo')).pipe(
return this.collectionService.findById(route.params.id, true, false, followLink('logo')).pipe(
getFirstCompletedRemoteData()
);
}

View File

@@ -23,7 +23,7 @@ export class ItemTemplatePageResolver implements Resolve<RemoteData<Item>> {
* or an error if something went wrong
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Item>> {
return this.itemTemplateService.findByCollectionID(route.params.id, false, followLink('templateItemOf')).pipe(
return this.itemTemplateService.findByCollectionID(route.params.id, true, false, followLink('templateItemOf')).pipe(
getFirstCompletedRemoteData(),
);
}

View File

@@ -25,6 +25,7 @@ export class CommunityPageResolver implements Resolve<RemoteData<Community>> {
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Community>> {
return this.communityService.findById(
route.params.id,
true,
false,
followLink('logo'),
followLink('subcommunities'),

View File

@@ -79,7 +79,7 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy {
getFirstSucceededRemoteDataWithNotEmptyPayload(),
map((item: Item) => this.linkService.resolveLink(
item,
followLink('bundles', new FindListOptions(), true, followLink('bitstreams'))
followLink('bundles', new FindListOptions(), true, true, true, followLink('bitstreams'))
))
) as Observable<Item>;

View File

@@ -81,11 +81,15 @@ describe('ItemCollectionMapperComponent', () => {
const itemDataServiceStub = {
mapToCollection: () => createSuccessfulRemoteDataObject$({}),
removeMappingFromCollection: () => createSuccessfulRemoteDataObject$({}),
getMappedCollectionsEndpoint: () => of('rest/api/mappedCollectionsEndpoint'),
getMappedCollections: () => of(mockCollectionsRD),
/* tslint:disable:no-empty */
clearMappedCollectionsRequests: () => {}
/* tslint:enable:no-empty */
};
const collectionDataServiceStub = {
findAllByHref: () => of(mockCollectionsRD)
};
const searchServiceStub = Object.assign(new SearchServiceStub(), {
search: () => of(mockCollectionsRD),
/* tslint:disable:no-empty */
@@ -114,7 +118,7 @@ describe('ItemCollectionMapperComponent', () => {
{ provide: ObjectSelectService, useValue: new ObjectSelectServiceStub() },
{ provide: TranslateService, useValue: translateServiceStub },
{ provide: HostWindowService, useValue: new HostWindowServiceStub(0) },
{ provide: CollectionDataService, useValue: {} }
{ provide: CollectionDataService, useValue: collectionDataServiceStub }
]
}).compileComponents();
}));

View File

@@ -11,7 +11,8 @@ import {
getFirstSucceededRemoteDataPayload,
getRemoteDataPayload,
getFirstSucceededRemoteData,
toDSpaceObjectListRD
toDSpaceObjectListRD,
getAllSucceededRemoteData
} from '../../../core/shared/operators';
import { ActivatedRoute, Router } from '@angular/router';
import { map, startWith, switchMap, take } from 'rxjs/operators';
@@ -101,14 +102,22 @@ export class ItemCollectionMapperComponent implements OnInit {
* Load mappedCollectionsRD$ to only obtain collections that don't own this item
*/
loadCollectionLists() {
console.log('loadCollectionLists');
this.shouldUpdate$ = new BehaviorSubject<boolean>(true);
this.itemCollectionsRD$ = observableCombineLatest(this.itemRD$, this.shouldUpdate$).pipe(
map(([itemRD, shouldUpdate]) => {
if (shouldUpdate) {
return itemRD.payload;
this.itemCollectionsRD$ = observableCombineLatest(this.itemRD$.pipe(getFirstSucceededRemoteDataPayload()), this.shouldUpdate$).pipe(
switchMap(([item, shouldUpdate]) => {
if (shouldUpdate === true) {
this.shouldUpdate$.next(false);
}
return this.collectionDataService.findAllByHref(
this.itemDataService.getMappedCollectionsEndpoint(item.id),
undefined,
!shouldUpdate,
false
).pipe(
getAllSucceededRemoteData()
);
}),
switchMap((item: Item) => this.itemDataService.getMappedCollections(item.id))
);
const owningCollectionRD$ = this.itemRD$.pipe(
@@ -141,13 +150,11 @@ export class ItemCollectionMapperComponent implements OnInit {
const itemIdAndExcludingIds$ = observableCombineLatest([
this.itemRD$.pipe(
getFirstSucceededRemoteData(),
take(1),
map((rd: RemoteData<Item>) => rd.payload),
map((item: Item) => item.id)
),
this.itemCollectionsRD$.pipe(
getFirstSucceededRemoteData(),
take(1),
map((rd: RemoteData<PaginatedList<Collection>>) => rd.payload.page),
map((collections: Collection[]) => collections.map((collection: Collection) => collection.id))
)
@@ -203,6 +210,7 @@ export class ItemCollectionMapperComponent implements OnInit {
successMessages.subscribe(([head, content]) => {
this.notificationsService.success(head, content);
});
this.shouldUpdate$.next(true);
}
if (unsuccessful.length > 0) {
const unsuccessMessages = observableCombineLatest([
@@ -214,8 +222,6 @@ export class ItemCollectionMapperComponent implements OnInit {
this.notificationsService.error(head, content);
});
}
// Force an update on all lists and switch back to the first tab
this.shouldUpdate$.next(true);
this.switchToFirstTab();
});
}

View File

@@ -228,7 +228,7 @@ describe('EditInPlaceFieldComponent', () => {
}));
it('it should call queryMetadataFields on the metadataFieldService with the correct query', () => {
expect(metadataFieldService.queryMetadataFields).toHaveBeenCalledWith(query, null, false, followLink('schema'));
expect(metadataFieldService.queryMetadataFields).toHaveBeenCalledWith(query, null, true, false, followLink('schema'));
});
it('it should set metadataFieldSuggestions to the right value', () => {

View File

@@ -127,7 +127,7 @@ export class EditInPlaceFieldComponent implements OnInit, OnChanges {
*/
findMetadataFieldSuggestions(query: string) {
if (isNotEmpty(query)) {
return this.registryService.queryMetadataFields(query, null, false, followLink('schema')).pipe(
return this.registryService.queryMetadataFields(query, null, true, false, followLink('schema')).pipe(
getFirstSucceededRemoteData(),
metadataFieldsToString(),
).subscribe((fieldNames: string[]) => {

View File

@@ -11,7 +11,7 @@ import {
} from '../../../../core/data/object-updates/object-updates.reducer';
import { RelationshipService } from '../../../../core/data/relationship.service';
import { Item } from '../../../../core/shared/item.model';
import { defaultIfEmpty, map, mergeMap, switchMap, take, } from 'rxjs/operators';
import { defaultIfEmpty, map, mergeMap, switchMap, take, startWith } from 'rxjs/operators';
import { hasValue, hasValueOperator } from '../../../../shared/empty.util';
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
@@ -311,12 +311,13 @@ export class EditRelationshipListComponent implements OnInit {
return fieldUpdatesFiltered;
}),
)),
startWith({}),
);
}
private getItemRelationships() {
this.linkService.resolveLink(this.item,
followLink('relationships', undefined, true,
followLink('relationships', undefined, true, true, true,
followLink('relationshipType'),
followLink('leftItem'),
followLink('rightItem'),

View File

@@ -84,6 +84,7 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent {
switchMap(() => this.itemService.findById(
this.item.uuid,
true,
true,
followLink('owningCollection'),
followLink('bundles'),
followLink('relationships')),
@@ -118,6 +119,8 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent {
switchMap((entityType) =>
this.entityTypeService.getEntityTypeRelationships(
entityType.id,
true,
true,
followLink('leftType'),
followLink('rightType'))
.pipe(

View File

@@ -67,6 +67,7 @@ export class FullFileSectionComponent extends FileSectionComponent implements On
'ORIGINAL',
{elementsPerPage: this.pageSize, currentPage: pageNumber},
true,
true,
followLink('format')
)),
tap((rd: RemoteData<PaginatedList<Bitstream>>) => {
@@ -83,6 +84,7 @@ export class FullFileSectionComponent extends FileSectionComponent implements On
'LICENSE',
{elementsPerPage: this.pageSize, currentPage: pageNumber},
true,
true,
followLink('format')
)),
tap((rd: RemoteData<PaginatedList<Bitstream>>) => {

View File

@@ -25,11 +25,12 @@ export class ItemPageResolver implements Resolve<RemoteData<Item>> {
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Item>> {
return this.itemService.findById(route.params.id,
true,
false,
followLink('owningCollection'),
followLink('bundles', new FindListOptions(), true, followLink('bitstreams')),
followLink('bundles', new FindListOptions(), true, true, true, followLink('bitstreams')),
followLink('relationships'),
followLink('version', undefined, true, followLink('versionhistory')),
followLink('version', undefined, true, true, true, followLink('versionhistory')),
).pipe(
getFirstCompletedRemoteData(),
);

View File

@@ -1,6 +1,11 @@
import { Component, Input } from '@angular/core';
import { MetadataRepresentation } from '../../../core/shared/metadata-representation/metadata-representation.model';
import { combineLatest as observableCombineLatest, Observable, of as observableOf, zip as observableZip } from 'rxjs';
import {
combineLatest as observableCombineLatest,
Observable,
of as observableOf,
zip as observableZip
} from 'rxjs';
import { RelationshipService } from '../../../core/data/relationship.service';
import { MetadataValue } from '../../../core/shared/metadata.models';
import { getFirstSucceededRemoteData } from '../../../core/shared/operators';
@@ -82,7 +87,7 @@ export class MetadataRepresentationListComponent extends AbstractIncrementalList
.map((metadatum: any) => Object.assign(new MetadataValue(), metadatum))
.map((metadatum: MetadataValue) => {
if (metadatum.isVirtual) {
return this.relationshipService.findById(metadatum.virtualValue, false, followLink('leftItem'), followLink('rightItem')).pipe(
return this.relationshipService.findById(metadatum.virtualValue, true, false, followLink('leftItem'), followLink('rightItem')).pipe(
getFirstSucceededRemoteData(),
switchMap((relRD: RemoteData<Relationship>) =>
observableCombineLatest(relRD.payload.leftItem, relRD.payload.rightItem).pipe(

View File

@@ -1,10 +1,11 @@
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { Injectable } from '@angular/core';
import { FindByIDRequest, IdentifierType } from '../core/data/request.models';
import { IdentifierType } from '../core/data/request.models';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { RemoteData } from '../core/data/remote-data';
import { DsoRedirectDataService } from '../core/data/dso-redirect-data.service';
import { DSpaceObject } from '../core/shared/dspace-object.model';
interface LookupParams {
type: IdentifierType;
@@ -20,7 +21,7 @@ export class LookupGuard implements CanActivate {
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
const params = this.getLookupParams(route);
return this.dsoService.findByIdAndIDType(params.id, params.type).pipe(
map((response: RemoteData<FindByIDRequest>) => response.hasFailed)
map((response: RemoteData<DSpaceObject>) => response.hasFailed)
);
}

View File

@@ -24,6 +24,7 @@ export class WorkflowItemPageResolver implements Resolve<RemoteData<WorkflowItem
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<WorkflowItem>> {
return this.workflowItemService.findById(route.params.id,
true,
false,
followLink('item'),
).pipe(

View File

@@ -45,11 +45,7 @@ export class AuthRequestService {
map((endpointURL) => this.getEndpointByMethod(endpointURL, method)),
distinctUntilChanged(),
map((endpointURL: string) => new PostRequest(this.requestService.generateRequestId(), endpointURL, body, options)),
map ((request: PostRequest) => {
request.responseMsToLive = 10 * 1000;
return request;
}),
tap((request: PostRequest) => this.requestService.configure(request)),
tap((request: PostRequest) => this.requestService.send(request)),
mergeMap((request: PostRequest) => this.fetchRequest(request)),
distinctUntilChanged());
}
@@ -60,11 +56,7 @@ export class AuthRequestService {
map((endpointURL) => this.getEndpointByMethod(endpointURL, method)),
distinctUntilChanged(),
map((endpointURL: string) => new GetRequest(this.requestService.generateRequestId(), endpointURL, undefined, options)),
map ((request: GetRequest) => {
request.forceBypassCache = true;
return request;
}),
tap((request: GetRequest) => this.requestService.configure(request)),
tap((request: GetRequest) => this.requestService.send(request)),
mergeMap((request: GetRequest) => this.fetchRequest(request)),
distinctUntilChanged());
}
@@ -78,7 +70,7 @@ export class AuthRequestService {
distinctUntilChanged(),
map((href: string) => new URLCombiner(href, this.shortlivedtokensEndpoint).toString()),
map((endpointURL: string) => new PostRequest(this.requestService.generateRequestId(), endpointURL)),
tap((request: PostRequest) => this.requestService.configure(request)),
tap((request: PostRequest) => this.requestService.send(request)),
switchMap((request: PostRequest) => this.rdbService.buildFromRequestUUID<ShortLivedToken>(request.uuid)),
getFirstCompletedRemoteData(),
map((response: RemoteData<ShortLivedToken>) => {

View File

@@ -23,7 +23,7 @@ export class CollectionBreadcrumbResolver extends DSOBreadcrumbResolver<Collecti
*/
get followLinks(): FollowLinkConfig<Collection>[] {
return [
followLink('parentCommunity', undefined, true,
followLink('parentCommunity', undefined, true, true, true,
followLink('parentCommunity')
)
];

View File

@@ -29,7 +29,7 @@ export abstract class DSOBreadcrumbResolver<T extends ChildHALResource & DSpaceO
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<BreadcrumbConfig<T>> {
const uuid = route.params.id;
return this.dataService.findById(uuid, false, ...this.followLinks).pipe(
return this.dataService.findById(uuid, true, false, ...this.followLinks).pipe(
getFirstCompletedRemoteData(),
getRemoteDataPayload(),
map((object: T) => {

View File

@@ -23,8 +23,8 @@ export class ItemBreadcrumbResolver extends DSOBreadcrumbResolver<Item> {
*/
get followLinks(): FollowLinkConfig<Item>[] {
return [
followLink('owningCollection', undefined, true,
followLink('parentCommunity', undefined, true,
followLink('owningCollection', undefined, true, true, true,
followLink('parentCommunity', undefined, true, true, true,
followLink('parentCommunity'))
),
followLink('bundles'),

View File

@@ -25,22 +25,22 @@ describe(`BrowseDefinitionDataService`, () => {
describe(`findAll`, () => {
it(`should call findAll on DataServiceImpl`, () => {
service.findAll(options, false, ...linksToFollow);
expect(dataServiceImplSpy.findAll).toHaveBeenCalledWith(options, false, ...linksToFollow);
service.findAll(options, true, false, ...linksToFollow);
expect(dataServiceImplSpy.findAll).toHaveBeenCalledWith(options, true, false, ...linksToFollow);
});
});
describe(`findByHref`, () => {
it(`should call findByHref on DataServiceImpl`, () => {
service.findByHref(hrefSingle, false, ...linksToFollow);
expect(dataServiceImplSpy.findByHref).toHaveBeenCalledWith(hrefSingle, false, ...linksToFollow);
service.findByHref(hrefSingle, true, false, ...linksToFollow);
expect(dataServiceImplSpy.findByHref).toHaveBeenCalledWith(hrefSingle, true, false, ...linksToFollow);
});
});
describe(`findAllByHref`, () => {
it(`should call findAllByHref on DataServiceImpl`, () => {
service.findAllByHref(hrefAll, options, false, ...linksToFollow);
expect(dataServiceImplSpy.findAllByHref).toHaveBeenCalledWith(hrefAll, options, false, ...linksToFollow);
service.findAllByHref(hrefAll, options, true, false, ...linksToFollow);
expect(dataServiceImplSpy.findAllByHref).toHaveBeenCalledWith(hrefAll, options, true, false, ...linksToFollow);
});
});

View File

@@ -63,42 +63,48 @@ export class BrowseDefinitionDataService {
* info should be added to the objects
*
* @param options Find list options object
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
* the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s
* should be automatically resolved
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
* @param reRequestOnStale Whether or not the request should automatically be re-
* requested after the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
* {@link HALLink}s should be automatically resolved
* @return {Observable<RemoteData<PaginatedList<BrowseDefinition>>>}
* Return an observable that emits object list
*/
findAll(options: FindListOptions = {}, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<BrowseDefinition>[]): Observable<RemoteData<PaginatedList<BrowseDefinition>>> {
return this.dataService.findAll(options, reRequestOnStale, ...linksToFollow);
findAll(options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<BrowseDefinition>[]): Observable<RemoteData<PaginatedList<BrowseDefinition>>> {
return this.dataService.findAll(options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
}
/**
* Returns an observable of {@link RemoteData} of an {@link BrowseDefinition}, based on an href, with a list of {@link FollowLinkConfig},
* to automatically resolve {@link HALLink}s of the {@link BrowseDefinition}
* @param href The url of {@link BrowseDefinition} we want to retrieve
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
* the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s
* should be automatically resolved
* @param href The url of {@link BrowseDefinition} we want to retrieve
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
* @param reRequestOnStale Whether or not the request should automatically be re-
* requested after the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
* {@link HALLink}s should be automatically resolved
*/
findByHref(href: string, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<BrowseDefinition>[]): Observable<RemoteData<BrowseDefinition>> {
return this.dataService.findByHref(href, reRequestOnStale, ...linksToFollow);
findByHref(href: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<BrowseDefinition>[]): Observable<RemoteData<BrowseDefinition>> {
return this.dataService.findByHref(href, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
}
/**
* Returns a list of observables of {@link RemoteData} of {@link BrowseDefinition}s, based on an href, with a list of {@link FollowLinkConfig},
* to automatically resolve {@link HALLink}s of the {@link BrowseDefinition}
* @param href The url of the {@link BrowseDefinition} we want to retrieve
* @param findListOptions Find list options object
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
* the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s
* should be automatically resolved
* @param href The url of object we want to retrieve
* @param findListOptions Find list options object
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
* @param reRequestOnStale Whether or not the request should automatically be re-
* requested after the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
* {@link HALLink}s should be automatically resolved
*/
findAllByHref(href: string, findListOptions: FindListOptions = {}, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<BrowseDefinition>[]): Observable<RemoteData<PaginatedList<BrowseDefinition>>> {
return this.dataService.findAllByHref(href, findListOptions, reRequestOnStale, ...linksToFollow);
findAllByHref(href: string, findListOptions: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<BrowseDefinition>[]): Observable<RemoteData<PaginatedList<BrowseDefinition>>> {
return this.dataService.findAllByHref(href, findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
}
}

View File

@@ -11,8 +11,8 @@ import { BrowseDefinition } from '../shared/browse-definition.model';
import { BrowseEntrySearchOptions } from './browse-entry-search-options.model';
import { BrowseService } from './browse.service';
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
import { createPaginatedList } from '../../shared/testing/utils.test';
import { GetRequest } from '../data/request.models';
import { createPaginatedList, getFirstUsedArgumentOfSpyMethod } from '../../shared/testing/utils.test';
import { getMockHrefOnlyDataService } from '../../shared/mocks/href-only-data.service.mock';
describe('BrowseService', () => {
let scheduler: TestScheduler;
@@ -82,6 +82,7 @@ describe('BrowseService', () => {
];
let browseDefinitionDataService;
let hrefOnlyDataService;
const getRequestEntry$ = (successful: boolean) => {
return observableOf({
@@ -93,10 +94,12 @@ describe('BrowseService', () => {
browseDefinitionDataService = jasmine.createSpyObj('browseDefinitionDataService', {
findAll: createSuccessfulRemoteDataObject$(createPaginatedList(browseDefinitions))
});
hrefOnlyDataService = getMockHrefOnlyDataService();
return new BrowseService(
requestService,
halService,
browseDefinitionDataService,
hrefOnlyDataService,
rdbService
);
}
@@ -123,54 +126,40 @@ describe('BrowseService', () => {
});
});
describe('getBrowseEntriesFor and getBrowseItemsFor', () => {
describe('getBrowseEntriesFor and findList', () => {
const mockAuthorName = 'Donald Smith';
beforeEach(() => {
requestService = getMockRequestService(getRequestEntry$(true));
rdbService = getMockRemoteDataBuildService();
service = initTestService();
spyOn(service, 'getBrowseDefinitions').and
.returnValue(hot('--a-', {
a: createSuccessfulRemoteDataObject(createPaginatedList(browseDefinitions))
}));
spyOn(rdbService, 'buildList').and.callThrough();
});
describe('when getBrowseEntriesFor is called with a valid browse definition id', () => {
it('should configure a new BrowseEntriesRequest', () => {
const expected = new GetRequest(requestService.generateRequestId(), browseDefinitions[1]._links.entries.href);
it('should call hrefOnlyDataService.findAllByHref with the expected href', () => {
const expected = browseDefinitions[1]._links.entries.href;
scheduler.schedule(() => service.getBrowseEntriesFor(new BrowseEntrySearchOptions(browseDefinitions[1].id)).subscribe());
scheduler.flush();
expect(requestService.configure).toHaveBeenCalledWith(expected);
});
it('should call RemoteDataBuildService to create the RemoteData Observable', () => {
service.getBrowseEntriesFor(new BrowseEntrySearchOptions(browseDefinitions[1].id));
expect(rdbService.buildList).toHaveBeenCalled();
expect(getFirstUsedArgumentOfSpyMethod(hrefOnlyDataService.findAllByHref)).toBeObservable(cold('(a|)', {
a: expected
}));
});
});
describe('when getBrowseItemsFor is called with a valid browse definition id', () => {
it('should configure a new BrowseItemsRequest', () => {
const expected = new GetRequest(requestService.generateRequestId(), browseDefinitions[1]._links.items.href + '?filterValue=' + mockAuthorName);
describe('when findList is called with a valid browse definition id', () => {
it('should call hrefOnlyDataService.findAllByHref with the expected href', () => {
const expected = browseDefinitions[1]._links.items.href + '?filterValue=' + mockAuthorName;
scheduler.schedule(() => service.getBrowseItemsFor(mockAuthorName, new BrowseEntrySearchOptions(browseDefinitions[1].id)).subscribe());
scheduler.flush();
expect(requestService.configure).toHaveBeenCalledWith(expected);
});
it('should call RemoteDataBuildService to create the RemoteData Observable', () => {
service.getBrowseItemsFor(mockAuthorName, new BrowseEntrySearchOptions(browseDefinitions[1].id));
expect(rdbService.buildList).toHaveBeenCalled();
expect(getFirstUsedArgumentOfSpyMethod(hrefOnlyDataService.findAllByHref)).toBeObservable(cold('(a|)', {
a: expected
}));
});
});
@@ -256,29 +245,19 @@ describe('BrowseService', () => {
requestService = getMockRequestService();
rdbService = getMockRemoteDataBuildService();
service = initTestService();
spyOn(service, 'getBrowseDefinitions').and
.returnValue(hot('--a-', {
a: createSuccessfulRemoteDataObject(createPaginatedList(browseDefinitions))
}));
spyOn(rdbService, 'buildList').and.callThrough();
});
describe('when getFirstItemFor is called with a valid browse definition id', () => {
const expectedURL = browseDefinitions[1]._links.items.href + '?page=0&size=1';
it('should configure a new BrowseItemsRequest', () => {
const expected = new GetRequest(requestService.generateRequestId(), expectedURL);
it('should call hrefOnlyDataService.findAllByHref with the expected href', () => {
scheduler.schedule(() => service.getFirstItemFor(browseDefinitions[1].id).subscribe());
scheduler.flush();
expect(requestService.configure).toHaveBeenCalledWith(expected);
});
it('should call RemoteDataBuildService to create the RemoteData Observable', () => {
service.getFirstItemFor(browseDefinitions[1].id);
expect(rdbService.buildList).toHaveBeenCalled();
expect(getFirstUsedArgumentOfSpyMethod(hrefOnlyDataService.findAllByHref)).toBeObservable(cold('(a|)', {
a: expectedURL
}));
});
});

View File

@@ -1,11 +1,10 @@
import { Injectable } from '@angular/core';
import { Observable, of as observableOf } from 'rxjs';
import { distinctUntilChanged, map, startWith, take } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { distinctUntilChanged, map, startWith } from 'rxjs/operators';
import { hasValue, hasValueOperator, isEmpty, isNotEmpty } from '../../shared/empty.util';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { PaginatedList } from '../data/paginated-list.model';
import { RemoteData } from '../data/remote-data';
import { GetRequest } from '../data/request.models';
import { RequestService } from '../data/request.service';
import { BrowseDefinition } from '../shared/browse-definition.model';
import { BrowseEntry } from '../shared/browse-entry.model';
@@ -21,6 +20,7 @@ import {
import { URLCombiner } from '../url-combiner/url-combiner';
import { BrowseEntrySearchOptions } from './browse-entry-search-options.model';
import { BrowseDefinitionDataService } from './browse-definition-data.service';
import { HrefOnlyDataService } from '../data/href-only-data.service';
/**
* The service handling all browse requests
@@ -46,6 +46,7 @@ export class BrowseService {
protected requestService: RequestService,
protected halService: HALEndpointService,
private browseDefinitionDataService: BrowseDefinitionDataService,
private hrefOnlyDataService: HrefOnlyDataService,
private rdb: RemoteDataBuildService,
) {
}
@@ -65,7 +66,7 @@ export class BrowseService {
* @param options
*/
getBrowseEntriesFor(options: BrowseEntrySearchOptions): Observable<RemoteData<PaginatedList<BrowseEntry>>> {
return this.getBrowseDefinitions().pipe(
const href$ = this.getBrowseDefinitions().pipe(
getBrowseDefinitionLinks(options.metadataDefinition),
hasValueOperator(),
map((_links: any) => {
@@ -93,9 +94,9 @@ export class BrowseService {
href = new URLCombiner(href, `?${args.join('&')}`).toString();
}
return href;
}),
getBrowseEntriesFor(this.requestService, this.rdb)
})
);
return this.hrefOnlyDataService.findAllByHref<BrowseEntry>(href$);
}
/**
@@ -105,7 +106,7 @@ export class BrowseService {
* @returns {Observable<RemoteData<PaginatedList<Item>>>}
*/
getBrowseItemsFor(filterValue: string, options: BrowseEntrySearchOptions): Observable<RemoteData<PaginatedList<Item>>> {
return this.getBrowseDefinitions().pipe(
const href$ = this.getBrowseDefinitions().pipe(
getBrowseDefinitionLinks(options.metadataDefinition),
hasValueOperator(),
map((_links: any) => {
@@ -136,8 +137,8 @@ export class BrowseService {
}
return href;
}),
getBrowseItemsFor(this.requestService, this.rdb)
);
return this.hrefOnlyDataService.findAllByHref<Item>(href$);
}
/**
@@ -146,7 +147,7 @@ export class BrowseService {
* @param scope
*/
getFirstItemFor(definition: string, scope?: string): Observable<RemoteData<Item>> {
return this.getBrowseDefinitions().pipe(
const href$ = this.getBrowseDefinitions().pipe(
getBrowseDefinitionLinks(definition),
hasValueOperator(),
map((_links: any) => {
@@ -165,11 +166,14 @@ export class BrowseService {
href = new URLCombiner(href, `?${args.join('&')}`).toString();
}
return href;
}),
getBrowseItemsFor(this.requestService, this.rdb),
})
);
return this.hrefOnlyDataService.findAllByHref<Item>(href$).pipe(
getFirstSucceededRemoteData(),
getFirstOccurrence()
);
}
/**
@@ -177,9 +181,7 @@ export class BrowseService {
* @param items
*/
getPrevBrowseItems(items: RemoteData<PaginatedList<Item>>): Observable<RemoteData<PaginatedList<Item>>> {
return observableOf(items.payload.prev).pipe(
getBrowseItemsFor(this.requestService, this.rdb)
);
return this.hrefOnlyDataService.findAllByHref<Item>(items.payload.prev);
}
/**
@@ -187,9 +189,7 @@ export class BrowseService {
* @param items
*/
getNextBrowseItems(items: RemoteData<PaginatedList<Item>>): Observable<RemoteData<PaginatedList<Item>>> {
return observableOf(items.payload.next).pipe(
getBrowseItemsFor(this.requestService, this.rdb)
);
return this.hrefOnlyDataService.findAllByHref<Item>(items.payload.next);
}
/**
@@ -197,9 +197,7 @@ export class BrowseService {
* @param entries
*/
getPrevBrowseEntries(entries: RemoteData<PaginatedList<BrowseEntry>>): Observable<RemoteData<PaginatedList<BrowseEntry>>> {
return observableOf(entries.payload.prev).pipe(
getBrowseEntriesFor(this.requestService, this.rdb)
);
return this.hrefOnlyDataService.findAllByHref<BrowseEntry>(entries.payload.prev);
}
/**
@@ -207,9 +205,7 @@ export class BrowseService {
* @param entries
*/
getNextBrowseEntries(entries: RemoteData<PaginatedList<BrowseEntry>>): Observable<RemoteData<PaginatedList<BrowseEntry>>> {
return observableOf(entries.payload.next).pipe(
getBrowseEntriesFor(this.requestService, this.rdb)
);
return this.hrefOnlyDataService.findAllByHref<BrowseEntry>(entries.payload.next);
}
/**
@@ -241,39 +237,3 @@ export class BrowseService {
}
}
/**
* Operator for turning a href into a PaginatedList of BrowseEntries
* @param requestService
* @param responseCache
* @param rdb
*/
export const getBrowseEntriesFor = (requestService: RequestService, rdb: RemoteDataBuildService) =>
(source: Observable<string>): Observable<RemoteData<PaginatedList<BrowseEntry>>> => {
const requestId = requestService.generateRequestId();
source.pipe(take(1)).subscribe((href: string) => {
const request = new GetRequest(requestId, href);
requestService.configure(request);
});
return rdb.buildList(source);
};
/**
* Operator for turning a href into a PaginatedList of Items
* @param requestService
* @param responseCache
* @param rdb
*/
export const getBrowseItemsFor = (requestService: RequestService, rdb: RemoteDataBuildService) =>
(source: Observable<string>): Observable<RemoteData<PaginatedList<Item>>> => {
const requestId = requestService.generateRequestId();
source.pipe(take(1)).subscribe((href: string) => {
const request = new GetRequest(requestId, href);
requestService.configure(request);
});
return rdb.buildList(source);
};

View File

@@ -38,11 +38,11 @@ class TestModel implements HALResource {
@Injectable()
class TestDataService {
findAllByHref(href: string, findListOptions: FindListOptions = {}, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<any>[]) {
findAllByHref(href: string, findListOptions: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<any>[]) {
return 'findAllByHref';
}
findByHref(href: string, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<any>[]) {
findByHref(href: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<any>[]) {
return 'findByHref';
}
}
@@ -90,10 +90,10 @@ xdescribe('LinkService', () => {
propertyName: 'predecessor'
});
spyOnFunction(decorators, 'getDataServiceFor').and.returnValue(TestDataService);
service.resolveLink(testModel, followLink('predecessor', {}, true, followLink('successor')));
service.resolveLink(testModel, followLink('predecessor', {}, true, true, true, followLink('successor')));
});
it('should call dataservice.findByHref with the correct href and nested links', () => {
expect(testDataService.findByHref).toHaveBeenCalledWith(testModel._links.predecessor.href, true, followLink('successor'));
expect(testDataService.findByHref).toHaveBeenCalledWith(testModel._links.predecessor.href, true, true, followLink('successor'));
});
});
describe(`when the linkdefinition concerns a list`, () => {
@@ -105,10 +105,10 @@ xdescribe('LinkService', () => {
isList: true
});
spyOnFunction(decorators, 'getDataServiceFor').and.returnValue(TestDataService);
service.resolveLink(testModel, followLink('predecessor', { some: 'options ' } as any, true, followLink('successor')));
service.resolveLink(testModel, followLink('predecessor', { some: 'options ' } as any, true, true, true, 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, true, followLink('successor'));
expect(testDataService.findAllByHref).toHaveBeenCalledWith(testModel._links.predecessor.href, { some: 'options ' } as any, true, true, followLink('successor'));
});
});
describe('either way', () => {
@@ -119,7 +119,7 @@ xdescribe('LinkService', () => {
propertyName: 'predecessor'
});
spyOnFunction(decorators, 'getDataServiceFor').and.returnValue(TestDataService);
result = service.resolveLink(testModel, followLink('predecessor', {}, true, followLink('successor')));
result = service.resolveLink(testModel, followLink('predecessor', {}, true, true, true, followLink('successor')));
});
it('should call getLinkDefinition with the correct model and link', () => {
@@ -144,7 +144,7 @@ xdescribe('LinkService', () => {
});
it('should throw an error', () => {
expect(() => {
service.resolveLink(testModel, followLink('predecessor', {}, true, followLink('successor')));
service.resolveLink(testModel, followLink('predecessor', {}, true, true, true, followLink('successor')));
}).toThrow();
});
});
@@ -160,7 +160,7 @@ xdescribe('LinkService', () => {
});
it('should throw an error', () => {
expect(() => {
service.resolveLink(testModel, followLink('predecessor', {}, true, followLink('successor')));
service.resolveLink(testModel, followLink('predecessor', {}, true, true, true, followLink('successor')));
}).toThrow();
});
});

View File

@@ -61,9 +61,9 @@ export class LinkService {
try {
if (matchingLinkDef.isList) {
model[linkToFollow.name] = service.findAllByHref(href, linkToFollow.findListOptions, true, ...linkToFollow.linksToFollow);
model[linkToFollow.name] = service.findAllByHref(href, linkToFollow.findListOptions, linkToFollow.useCachedVersionIfAvailable, linkToFollow.reRequestOnStale, ...linkToFollow.linksToFollow);
} else {
model[linkToFollow.name] = service.findByHref(href, true, ...linkToFollow.linksToFollow);
model[linkToFollow.name] = service.findByHref(href, linkToFollow.useCachedVersionIfAvailable, linkToFollow.reRequestOnStale, ...linkToFollow.linksToFollow);
}
} catch (e) {
console.error(`Something went wrong when using @dataService(${matchingLinkDef.resourceType.value}) ${hasValue(service) ? '' : '(undefined) '}to resolve link ${linkToFollow.name} at ${href}`);

View File

@@ -523,7 +523,7 @@ describe('RemoteDataBuildService', () => {
let paginatedLinksToFollow;
beforeEach(() => {
paginatedLinksToFollow = [
followLink('page', undefined, true, ...linksToFollow),
followLink('page', undefined, true, true, true, ...linksToFollow),
...linksToFollow
];
});

View File

@@ -271,7 +271,7 @@ export class RemoteDataBuildService {
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
*/
buildList<T extends HALResource>(href$: string | Observable<string>, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<PaginatedList<T>>> {
return this.buildFromHref<PaginatedList<T>>(href$, followLink('page', undefined, false, ...linksToFollow));
return this.buildFromHref<PaginatedList<T>>(href$, followLink('page', undefined, false, true, true, ...linksToFollow));
}
/**

View File

@@ -94,14 +94,16 @@ export class ServerSyncBufferEffects {
* @returns {Observable<Action>} ApplyPatchObjectCacheAction to be dispatched
*/
private applyPatch(href: string): Observable<Action> {
const patchObject = this.objectCache.getByHref(href).pipe(take(1));
const patchObject = this.objectCache.getByHref(href).pipe(
take(1)
);
return patchObject.pipe(
map((entry: ObjectCacheEntry) => {
if (isNotEmpty(entry.patches)) {
const flatPatch: Operation[] = [].concat(...entry.patches.map((patch) => patch.operations));
if (isNotEmpty(flatPatch)) {
this.requestService.configure(new PatchRequest(this.requestService.generateRequestId(), href, flatPatch));
this.requestService.send(new PatchRequest(this.requestService.generateRequestId(), href, flatPatch));
}
}
return new ApplyPatchObjectCacheAction(href);

View File

@@ -58,12 +58,12 @@ describe('ConfigService', () => {
describe('findByHref', () => {
it('should configure a new GetRequest', () => {
it('should send a new GetRequest', () => {
const expected = new GetRequest(requestService.generateRequestId(), scopedEndpoint);
scheduler.schedule(() => service.findByHref(scopedEndpoint).subscribe());
scheduler.flush();
expect(requestService.configure).toHaveBeenCalledWith(expected);
expect(requestService.send).toHaveBeenCalledWith(expected, true);
});
});
});

View File

@@ -52,8 +52,8 @@ export abstract class ConfigService {
this.dataService = new DataServiceImpl(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparator, this.linkPath);
}
public findByHref(href: string, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<ConfigObject>[]): Observable<RemoteData<ConfigObject>> {
return this.dataService.findByHref(href, reRequestOnStale, ...linksToFollow).pipe(
public findByHref(href: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<ConfigObject>[]): Observable<RemoteData<ConfigObject>> {
return this.dataService.findByHref(href, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow).pipe(
getFirstCompletedRemoteData(),
map((rd: RemoteData<ConfigObject>) => {
if (rd.hasFailed) {

View File

@@ -34,7 +34,7 @@ export class SubmissionFormsConfigService extends ConfigService {
super(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparator, 'submissionforms');
}
public findByHref(href: string, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<SubmissionFormsModel>[]): Observable<RemoteData<SubmissionFormsModel>> {
return super.findByHref(href, reRequestOnStale, ...linksToFollow as FollowLinkConfig<ConfigObject>[]) as Observable<RemoteData<SubmissionFormsModel>>;
public findByHref(href: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<SubmissionFormsModel>[]): Observable<RemoteData<SubmissionFormsModel>> {
return super.findByHref(href, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow as FollowLinkConfig<ConfigObject>[]) as Observable<RemoteData<SubmissionFormsModel>>;
}
}

View File

@@ -36,7 +36,7 @@ export class SubmissionUploadsConfigService extends ConfigService {
super(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparator, 'submissionuploads');
}
findByHref(href: string, reRequestOnStale = true, ...linksToFollow): Observable<RemoteData<SubmissionUploadsModel>> {
return super.findByHref(href, reRequestOnStale, ...linksToFollow as FollowLinkConfig<ConfigObject>[]) as Observable<RemoteData<SubmissionUploadsModel>>;
findByHref(href: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow): Observable<RemoteData<SubmissionUploadsModel>> {
return super.findByHref(href, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow as FollowLinkConfig<ConfigObject>[]) as Observable<RemoteData<SubmissionUploadsModel>>;
}
}

View File

@@ -2,7 +2,11 @@ import { CommonModule } from '@angular/common';
import { HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http';
import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core';
import { DynamicFormLayoutService, DynamicFormService, DynamicFormValidationService } from '@ng-dynamic-forms/core';
import {
DynamicFormLayoutService,
DynamicFormService,
DynamicFormValidationService
} from '@ng-dynamic-forms/core';
import { EffectsModule } from '@ngrx/effects';
import { Action, StoreConfig, StoreModule } from '@ngrx/store';
@@ -51,14 +55,12 @@ import { DSOChangeAnalyzer } from './data/dso-change-analyzer.service';
import { DSOResponseParsingService } from './data/dso-response-parsing.service';
import { DSpaceObjectDataService } from './data/dspace-object-data.service';
import { EndpointMapResponseParsingService } from './data/endpoint-map-response-parsing.service';
import { ItemTypeDataService } from './data/entity-type-data.service';
import { EntityTypeService } from './data/entity-type.service';
import { ExternalSourceService } from './data/external-source.service';
import { FacetConfigResponseParsingService } from './data/facet-config-response-parsing.service';
import { FacetValueResponseParsingService } from './data/facet-value-response-parsing.service';
import { FilteredDiscoveryPageResponseParsingService } from './data/filtered-discovery-page-response-parsing.service';
import { ItemDataService } from './data/item-data.service';
import { LicenseDataService } from './data/license-data.service';
import { LookupRelationService } from './data/lookup-relation.service';
import { MyDSpaceResponseParsingService } from './data/mydspace-response-parsing.service';
import { ObjectUpdatesService } from './data/object-updates/object-updates.service';
@@ -263,8 +265,6 @@ const PROVIDERS = [
LookupRelationService,
VersionDataService,
VersionHistoryDataService,
LicenseDataService,
ItemTypeDataService,
WorkflowActionDataService,
ProcessDataService,
ScriptDataService,

View File

@@ -55,8 +55,8 @@ describe('BitstreamDataService', () => {
service.updateFormat(bitstream, format);
});
it('should configure a put request', () => {
expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(PutRequest));
it('should send a put request', () => {
expect(requestService.send).toHaveBeenCalledWith(jasmine.any(PutRequest));
});
});
});

View File

@@ -25,7 +25,7 @@ import { RequestService } from './request.service';
import { BitstreamFormatDataService } from './bitstream-format-data.service';
import { BitstreamFormat } from '../shared/bitstream-format.model';
import { HttpOptions } from '../dspace-rest/dspace-rest.service';
import { configureRequest } from '../shared/operators';
import { sendRequest } from '../shared/operators';
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
import { PageInfo } from '../shared/page-info.model';
import { RequestEntryState } from './request.reducer';
@@ -62,15 +62,17 @@ export class BitstreamDataService extends DataService<Bitstream> {
/**
* Retrieves the {@link Bitstream}s in a given bundle
*
* @param bundle the bundle to retrieve bitstreams from
* @param options options for the find all request
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
* the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s
* should be automatically resolved
* @param bundle the bundle to retrieve bitstreams from
* @param options options for the find all request
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
* @param reRequestOnStale Whether or not the request should automatically be re-
* requested after the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
* {@link HALLink}s should be automatically resolved
*/
findAllByBundle(bundle: Bundle, options?: FindListOptions, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Bitstream>[]): Observable<RemoteData<PaginatedList<Bitstream>>> {
return this.findAllByHref(bundle._links.bitstreams.href, options, reRequestOnStale, ...linksToFollow);
findAllByBundle(bundle: Bundle, options?: FindListOptions, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Bitstream>[]): Observable<RemoteData<PaginatedList<Bitstream>>> {
return this.findAllByHref(bundle._links.bitstreams.href, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
}
/**
@@ -120,7 +122,7 @@ export class BitstreamDataService extends DataService<Bitstream> {
return this.bundleService.findByItemAndName(item, 'THUMBNAIL').pipe(
switchMap((bundleRD: RemoteData<Bundle>) => {
if (isNotEmpty(bundleRD.payload)) {
return this.findAllByBundle(bundleRD.payload, { elementsPerPage: Number.MAX_SAFE_INTEGER }).pipe(
return this.findAllByBundle(bundleRD.payload, { elementsPerPage: 9999 }).pipe(
map((bitstreamRD: RemoteData<PaginatedList<Bitstream>>) => {
if (hasValue(bitstreamRD.payload) && hasValue(bitstreamRD.payload.page)) {
const matchingThumbnail = bitstreamRD.payload.page.find((thumbnail: Bitstream) =>
@@ -165,19 +167,22 @@ export class BitstreamDataService extends DataService<Bitstream> {
* The {@link Item} is technically redundant, but is available
* in all current use cases, and having it simplifies this method
*
* @param item the {@link Item} the {@link Bundle} is a part of
* @param bundleName the name of the {@link Bundle} we want to find {@link Bitstream}s for
* @param options the {@link FindListOptions} for the request
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
* the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s
* should be automatically resolved
* @param item the {@link Item} the {@link Bundle} is a part of
* @param bundleName the name of the {@link Bundle} we want to find
* {@link Bitstream}s for
* @param options the {@link FindListOptions} for the request
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
* @param reRequestOnStale Whether or not the request should automatically be re-
* requested after the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
* {@link HALLink}s should be automatically resolved
*/
public findAllByItemAndBundleName(item: Item, bundleName: string, options?: FindListOptions, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Bitstream>[]): Observable<RemoteData<PaginatedList<Bitstream>>> {
public findAllByItemAndBundleName(item: Item, bundleName: string, options?: FindListOptions, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Bitstream>[]): Observable<RemoteData<PaginatedList<Bitstream>>> {
return this.bundleService.findByItemAndName(item, bundleName).pipe(
switchMap((bundleRD: RemoteData<Bundle>) => {
if (bundleRD.hasSucceeded && hasValue(bundleRD.payload)) {
return this.findAllByBundle(bundleRD.payload, options, reRequestOnStale, ...linksToFollow);
return this.findAllByBundle(bundleRD.payload, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
} else if (!bundleRD.hasSucceeded && bundleRD.statusCode === 404) {
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), []), new Date().getTime());
} else {
@@ -209,7 +214,7 @@ export class BitstreamDataService extends DataService<Bitstream> {
options.headers = headers;
return new PutRequest(requestId, bitstreamHref, formatHref, options);
}),
configureRequest(this.requestService),
sendRequest(this.requestService),
take(1)
).subscribe(() => {
this.requestService.removeByHrefSubstring(bitstream.self + '/format');

View File

@@ -73,7 +73,7 @@ describe('BitstreamFormatDataService', () => {
beforeEach(waitForAsync(() => {
scheduler = getTestScheduler();
requestService = jasmine.createSpyObj('requestService', {
configure: {},
send: {},
getByHref: observableOf(responseCacheEntry),
getByUUID: cold('a', { a: responseCacheEntry }),
generateRequestId: 'request-id',
@@ -93,7 +93,7 @@ describe('BitstreamFormatDataService', () => {
beforeEach(waitForAsync(() => {
scheduler = getTestScheduler();
requestService = jasmine.createSpyObj('requestService', {
configure: {},
send: {},
getByHref: observableOf(responseCacheEntry),
getByUUID: cold('a', { a: responseCacheEntry }),
generateRequestId: 'request-id',
@@ -115,7 +115,7 @@ describe('BitstreamFormatDataService', () => {
beforeEach(waitForAsync(() => {
scheduler = getTestScheduler();
requestService = jasmine.createSpyObj('requestService', {
configure: {},
send: {},
getByHref: observableOf(responseCacheEntry),
getByUUID: cold('a', { a: responseCacheEntry }),
generateRequestId: 'request-id',
@@ -136,7 +136,7 @@ describe('BitstreamFormatDataService', () => {
beforeEach(waitForAsync(() => {
scheduler = getTestScheduler();
requestService = jasmine.createSpyObj('requestService', {
configure: {},
send: {},
getByHref: observableOf(responseCacheEntry),
getByUUID: cold('a', { a: responseCacheEntry }),
generateRequestId: 'request-id',
@@ -160,7 +160,7 @@ describe('BitstreamFormatDataService', () => {
beforeEach(waitForAsync(() => {
scheduler = getTestScheduler();
requestService = jasmine.createSpyObj('requestService', {
configure: {},
send: {},
getByHref: observableOf(responseCacheEntry),
getByUUID: cold('a', { a: responseCacheEntry }),
generateRequestId: 'request-id',
@@ -183,7 +183,7 @@ describe('BitstreamFormatDataService', () => {
beforeEach(waitForAsync(() => {
scheduler = getTestScheduler();
requestService = jasmine.createSpyObj('requestService', {
configure: {},
send: {},
getByHref: observableOf(responseCacheEntry),
getByUUID: cold('a', { a: responseCacheEntry }),
generateRequestId: 'request-id',
@@ -206,7 +206,7 @@ describe('BitstreamFormatDataService', () => {
beforeEach(waitForAsync(() => {
scheduler = getTestScheduler();
requestService = jasmine.createSpyObj('requestService', {
configure: {},
send: {},
getByHref: observableOf(responseCacheEntry),
getByUUID: cold('a', { a: responseCacheEntry }),
generateRequestId: 'request-id',
@@ -228,7 +228,7 @@ describe('BitstreamFormatDataService', () => {
beforeEach(waitForAsync(() => {
scheduler = getTestScheduler();
requestService = jasmine.createSpyObj('requestService', {
configure: {},
send: {},
getByHref: observableOf(responseCacheEntry),
getByUUID: cold('a', { a: responseCacheEntry }),
generateRequestId: 'request-id',
@@ -250,7 +250,7 @@ describe('BitstreamFormatDataService', () => {
beforeEach(waitForAsync(() => {
scheduler = getTestScheduler();
requestService = jasmine.createSpyObj('requestService', {
configure: {},
send: {},
getByHref: observableOf(responseCacheEntry),
getByUUID: cold('a', { a: responseCacheEntry }),
generateRequestId: 'request-id',
@@ -270,7 +270,7 @@ describe('BitstreamFormatDataService', () => {
beforeEach(waitForAsync(() => {
scheduler = getTestScheduler();
requestService = jasmine.createSpyObj('requestService', {
configure: {},
send: {},
getByHref: observableOf(responseCacheEntry),
getByUUID: hot('a', { a: responseCacheEntry }),
generateRequestId: 'request-id',

View File

@@ -19,7 +19,7 @@ import { BitstreamFormat } from '../shared/bitstream-format.model';
import { BITSTREAM_FORMAT } from '../shared/bitstream-format.resource-type';
import { Bitstream } from '../shared/bitstream.model';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { configureRequest } from '../shared/operators';
import { sendRequest } from '../shared/operators';
import { DataService } from './data.service';
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
import { RemoteData } from './remote-data';
@@ -82,7 +82,7 @@ export class BitstreamFormatDataService extends DataService<BitstreamFormat> {
distinctUntilChanged(),
map((endpointURL: string) =>
new PutRequest(requestId, endpointURL, bitstreamFormat)),
configureRequest(this.requestService)).subscribe();
sendRequest(this.requestService)).subscribe();
return this.rdbService.buildFromRequestUUID(requestId);
@@ -99,7 +99,7 @@ export class BitstreamFormatDataService extends DataService<BitstreamFormat> {
map((endpointURL: string) => {
return new PostRequest(requestId, endpointURL, bitstreamFormat);
}),
configureRequest(this.requestService)
sendRequest(this.requestService)
).subscribe();
return this.rdbService.buildFromRequestUUID(requestId);

View File

@@ -81,7 +81,7 @@ describe('BundleDataService', () => {
});
it('should call findAllByHref with the item\'s bundles link', () => {
expect(service.findAllByHref).toHaveBeenCalledWith(bundleLink, undefined, true);
expect(service.findAllByHref).toHaveBeenCalledWith(bundleLink, undefined, true, true);
});
});

View File

@@ -50,28 +50,34 @@ export class BundleDataService extends DataService<Bundle> {
/**
* Retrieve all {@link Bundle}s in the given {@link Item}
*
* @param item the {@link Item} the {@link Bundle}s are a part of
* @param options the {@link FindListOptions} for the request
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
* the response becomes stale
* @param linksToFollow the {@link FollowLinkConfig}s for the request
* @param item the {@link Item} the {@link Bundle}s are a part of
* @param options the {@link FindListOptions} for the request
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
* @param reRequestOnStale Whether or not the request should automatically be re-
* requested after the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
* {@link HALLink}s should be automatically resolved
*/
findAllByItem(item: Item, options?: FindListOptions, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Bundle>[]): Observable<RemoteData<PaginatedList<Bundle>>> {
return this.findAllByHref(item._links.bundles.href, options, reRequestOnStale, ...linksToFollow);
findAllByItem(item: Item, options?: FindListOptions, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Bundle>[]): Observable<RemoteData<PaginatedList<Bundle>>> {
return this.findAllByHref(item._links.bundles.href, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
}
/**
* Retrieve a {@link Bundle} in the given {@link Item} by name
*
* @param item the {@link Item} the {@link Bundle}s are a part of
* @param bundleName the name of the {@link Bundle} to retrieve
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
* the response becomes stale
* @param linksToFollow the {@link FollowLinkConfig}s for the request
* @param item the {@link Item} the {@link Bundle}s are a part of
* @param bundleName the name of the {@link Bundle} to retrieve
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
* @param reRequestOnStale Whether or not the request should automatically be re-
* requested after the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
* {@link HALLink}s should be automatically resolved
*/
// TODO should be implemented rest side
findByItemAndName(item: Item, bundleName: string, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Bundle>[]): Observable<RemoteData<Bundle>> {
return this.findAllByItem(item, { elementsPerPage: Number.MAX_SAFE_INTEGER }, reRequestOnStale, ...linksToFollow).pipe(
findByItemAndName(item: Item, bundleName: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Bundle>[]): Observable<RemoteData<Bundle>> {
return this.findAllByItem(item, { elementsPerPage: 9999 }, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow).pipe(
map((rd: RemoteData<PaginatedList<Bundle>>) => {
if (hasValue(rd.payload) && hasValue(rd.payload.page)) {
const matchingBundle = rd.payload.page.find((bundle: Bundle) =>
@@ -129,7 +135,7 @@ export class BundleDataService extends DataService<Bundle> {
take(1)
).subscribe((href) => {
const request = new GetRequest(this.requestService.generateRequestId(), href);
this.requestService.configure(request);
this.requestService.send(request, true);
});
return this.rdbService.buildList<Bitstream>(hrefObs, ...linksToFollow);

View File

@@ -6,7 +6,7 @@ import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-servic
import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub';
import { getMockTranslateService } from '../../shared/mocks/translate.service.mock';
import { fakeAsync, tick } from '@angular/core/testing';
import { ContentSourceRequest, GetRequest, UpdateContentSourceRequest } from './request.models';
import { ContentSourceRequest, UpdateContentSourceRequest } from './request.models';
import { ContentSource } from '../shared/content-source.model';
import { ObjectCacheService } from '../cache/object-cache.service';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
@@ -87,10 +87,10 @@ describe('CollectionDataService', () => {
contentSource$ = service.getContentSource(collectionId);
});
it('should configure a new ContentSourceRequest', fakeAsync(() => {
it('should send a new ContentSourceRequest', fakeAsync(() => {
contentSource$.subscribe();
tick();
expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(ContentSourceRequest));
expect(requestService.send).toHaveBeenCalledWith(jasmine.any(ContentSourceRequest), true);
}));
});
@@ -103,25 +103,13 @@ describe('CollectionDataService', () => {
returnedContentSource$ = service.updateContentSource(collectionId, contentSource);
});
it('should configure a new UpdateContentSourceRequest', fakeAsync(() => {
it('should send a new UpdateContentSourceRequest', fakeAsync(() => {
returnedContentSource$.subscribe();
tick();
expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(UpdateContentSourceRequest));
expect(requestService.send).toHaveBeenCalledWith(jasmine.any(UpdateContentSourceRequest));
}));
});
describe('getMappedItems', () => {
let result;
beforeEach(() => {
result = service.getMappedItems('collection-id');
});
it('should configure a GET request', () => {
expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(GetRequest));
});
});
describe('when calling getAuthorizedCollection', () => {
beforeEach(() => {
scheduler = getTestScheduler();
@@ -175,10 +163,10 @@ describe('CollectionDataService', () => {
returnedContentSource$ = service.updateContentSource(collectionId, contentSource);
});
it('should configure a new UpdateContentSourceRequest', fakeAsync(() => {
it('should send a new UpdateContentSourceRequest', fakeAsync(() => {
returnedContentSource$.subscribe();
tick();
expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(UpdateContentSourceRequest));
expect(requestService.send).toHaveBeenCalledWith(jasmine.any(UpdateContentSourceRequest));
}));
it('should display an error notification', fakeAsync(() => {

View File

@@ -3,12 +3,11 @@ import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import { TranslateService } from '@ngx-translate/core';
import { Observable } from 'rxjs';
import { distinctUntilChanged, filter, map, switchMap, take } from 'rxjs/operators';
import { filter, map, switchMap, take } from 'rxjs/operators';
import { hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
import { NotificationOptions } from '../../shared/notifications/models/notification-options.model';
import { INotification } from '../../shared/notifications/models/notification.model';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
import { dataService } from '../cache/builders/build-decorators';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
@@ -20,26 +19,19 @@ import { DSpaceSerializer } from '../dspace-rest/dspace.serializer';
import { Collection } from '../shared/collection.model';
import { COLLECTION } from '../shared/collection.resource-type';
import { ContentSource } from '../shared/content-source.model';
import { DSpaceObject } from '../shared/dspace-object.model';
import { GenericConstructor } from '../shared/generic-constructor';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { Item } from '../shared/item.model';
import {
configureRequest,
getFirstCompletedRemoteData
} from '../shared/operators';
import { getFirstCompletedRemoteData } from '../shared/operators';
import { ComColDataService } from './comcol-data.service';
import { CommunityDataService } from './community-data.service';
import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
import { DSOResponseParsingService } from './dso-response-parsing.service';
import { PaginatedList } from './paginated-list.model';
import { ResponseParsingService } from './parsing.service';
import { RemoteData } from './remote-data';
import {
ContentSourceRequest,
FindListOptions,
GetRequest,
UpdateContentSourceRequest
UpdateContentSourceRequest,
RestRequest
} from './request.models';
import { RequestService } from './request.service';
import { BitstreamDataService } from './bitstream-data.service';
@@ -70,20 +62,25 @@ export class CollectionDataService extends ComColDataService<Collection> {
/**
* Get all collections the user is authorized to submit to
*
* @param query limit the returned collection to those with metadata values matching the query terms.
* @param options The [[FindListOptions]] object
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
* the response becomes stale
* @param query limit the returned collection to those with metadata values
* matching the query terms.
* @param options The [[FindListOptions]] object
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
* @param reRequestOnStale Whether or not the request should automatically be re-
* requested after the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
* {@link HALLink}s should be automatically resolved
* @return Observable<RemoteData<PaginatedList<Collection>>>
* collection list
*/
getAuthorizedCollection(query: string, options: FindListOptions = {}, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Collection>[]): Observable<RemoteData<PaginatedList<Collection>>> {
getAuthorizedCollection(query: string, options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Collection>[]): Observable<RemoteData<PaginatedList<Collection>>> {
const searchHref = 'findSubmitAuthorized';
options = Object.assign({}, options, {
searchParams: [new RequestParam('query', query)]
});
return this.searchBy(searchHref, options, reRequestOnStale, ...linksToFollow).pipe(
return this.searchBy(searchHref, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow).pipe(
filter((collections: RemoteData<PaginatedList<Collection>>) => !collections.isResponsePending));
}
@@ -149,7 +146,7 @@ export class CollectionDataService extends ComColDataService<Collection> {
href$.subscribe((href: string) => {
const request = new ContentSourceRequest(this.requestService.generateRequestId(), href);
this.requestService.configure(request);
this.requestService.send(request, true);
});
return this.rdbService.buildSingle<ContentSource>(href$);
@@ -175,9 +172,7 @@ export class CollectionDataService extends ComColDataService<Collection> {
);
// Execute the post/put request
request$.pipe(
configureRequest(this.requestService)
).subscribe();
request$.subscribe((request: RestRequest) => this.requestService.send(request));
// Return updated ContentSource
return this.rdbService.buildFromRequestUUID<ContentSource>(requestId).pipe(
@@ -205,48 +200,6 @@ export class CollectionDataService extends ComColDataService<Collection> {
);
}
/**
* Fetches the endpoint used for mapping items to a collection
* @param collectionId The id of the collection to map items to
*/
getMappedItemsEndpoint(collectionId): Observable<string> {
return this.halService.getEndpoint(this.linkPath).pipe(
map((endpoint: string) => this.getIDHref(endpoint, collectionId)),
map((endpoint: string) => `${endpoint}/mappedItems`)
);
}
/**
* Fetches a list of items that are mapped to a collection
* @param collectionId The id of the collection
* @param searchOptions Search options to sort or filter out items
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
*/
getMappedItems(collectionId: string, searchOptions?: PaginatedSearchOptions, ...linksToFollow: FollowLinkConfig<Item>[]): Observable<RemoteData<PaginatedList<DSpaceObject>>> {
const requestUuid = this.requestService.generateRequestId();
const href$ = this.getMappedItemsEndpoint(collectionId).pipe(
isNotEmptyOperator(),
distinctUntilChanged(),
map((endpoint: string) => hasValue(searchOptions) ? searchOptions.toRestUrl(endpoint) : endpoint)
);
href$.pipe(
map((endpoint: string) => {
const request = new GetRequest(requestUuid, endpoint);
return Object.assign(request, {
responseMsToLive: 0,
getResponseParser(): GenericConstructor<ResponseParsingService> {
return DSOResponseParsingService;
}
});
}),
configureRequest(this.requestService)
).subscribe();
return this.rdbService.buildList(href$, ...linksToFollow);
}
protected getFindByParentHref(parentUUID: string): Observable<string> {
return this.halService.getEndpoint('communities').pipe(
switchMap((communityEndpointHref: string) =>
@@ -261,5 +214,4 @@ export class CollectionDataService extends ComColDataService<Collection> {
findOwningCollectionFor(item: Item): Observable<RemoteData<Collection>> {
return this.findByHref(item._links.owningCollection.href);
}
}

View File

@@ -13,11 +13,12 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
import { ComColDataService } from './comcol-data.service';
import { CommunityDataService } from './community-data.service';
import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
import { FindByIDRequest, FindListOptions } from './request.models';
import { FindListOptions, GetRequest } from './request.models';
import { RequestEntry } from './request.reducer';
import { RequestService } from './request.service';
import {
createFailedRemoteDataObject$, createNoContentRemoteDataObject$,
createFailedRemoteDataObject$,
createNoContentRemoteDataObject$,
createSuccessfulRemoteDataObject$
} from '../../shared/remote-data.utils';
import { BitstreamDataService } from './bitstream-data.service';
@@ -147,18 +148,18 @@ describe('ComColDataService', () => {
scheduler = getTestScheduler();
});
it('should configure a new FindByIDRequest for the scope Community', () => {
it('should send a new FindByIDRequest for the scope Community', () => {
cds = initMockCommunityDataService();
requestService = getMockRequestService(getRequestEntry$(true));
objectCache = initMockObjectCacheService();
service = initTestService();
const expected = new FindByIDRequest(requestService.generateRequestId(), communityEndpoint, scopeID);
const expected = new GetRequest(requestService.generateRequestId(), communityEndpoint);
scheduler.schedule(() => service.getBrowseEndpoint(options).subscribe());
scheduler.flush();
expect(requestService.configure).toHaveBeenCalledWith(expected);
expect(requestService.send).toHaveBeenCalledWith(expected, true);
});
describe('if the scope Community can\'t be found', () => {

View File

@@ -1,4 +1,4 @@
import { distinctUntilChanged, filter, map, switchMap, take, tap } from 'rxjs/operators';
import { distinctUntilChanged, filter, map, switchMap, take } from 'rxjs/operators';
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
import { hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util';
import { ObjectCacheService } from '../cache/object-cache.service';
@@ -7,7 +7,7 @@ import { HALLink } from '../shared/hal-link.model';
import { CommunityDataService } from './community-data.service';
import { DataService } from './data.service';
import { FindByIDRequest, FindListOptions } from './request.models';
import { FindListOptions } from './request.models';
import { PaginatedList } from './paginated-list.model';
import { RemoteData } from './remote-data';
import { HALEndpointService } from '../shared/hal-endpoint.service';
@@ -42,11 +42,10 @@ export abstract class ComColDataService<T extends Community | Collection> extend
const scopeCommunityHrefObs = this.cds.getEndpoint().pipe(
map((endpoint: string) => this.cds.getIDHref(endpoint, options.scopeID)),
filter((href: string) => isNotEmpty(href)),
take(1),
tap((href: string) => {
const request = new FindByIDRequest(this.requestService.generateRequestId(), href, options.scopeID);
this.requestService.configure(request);
}));
take(1)
);
this.createAndSendGetRequest(scopeCommunityHrefObs, true);
return scopeCommunityHrefObs.pipe(
switchMap((href: string) => this.rdbService.buildSingle<Community>(href)),
@@ -71,7 +70,7 @@ export abstract class ComColDataService<T extends Community | Collection> extend
const href$ = this.getFindByParentHref(parentUUID).pipe(
map((href: string) => this.buildHrefFromFindOptions(href, options))
);
return this.findList(href$, options);
return this.findAllByHref(href$);
}
/**

View File

@@ -3,8 +3,7 @@ import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { filter, switchMap, take } from 'rxjs/operators';
import { hasValue } from '../../shared/empty.util';
import { switchMap } from 'rxjs/operators';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { dataService } from '../cache/builders/build-decorators';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
@@ -17,7 +16,7 @@ import { ComColDataService } from './comcol-data.service';
import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
import { PaginatedList } from './paginated-list.model';
import { RemoteData } from './remote-data';
import { FindListOptions, FindListRequest } from './request.models';
import { FindListOptions } from './request.models';
import { RequestService } from './request.service';
import { BitstreamDataService } from './bitstream-data.service';
@@ -48,15 +47,7 @@ export class CommunityDataService extends ComColDataService<Community> {
findTop(options: FindListOptions = {}): Observable<RemoteData<PaginatedList<Community>>> {
const hrefObs = this.getFindAllHref(options, this.topLinkPath);
hrefObs.pipe(
filter((href: string) => hasValue(href)),
take(1))
.subscribe((href: string) => {
const request = new FindListRequest(this.requestService.generateRequestId(), href, options);
this.requestService.configure(request);
});
return this.rdbService.buildList<Community>(hrefObs) as Observable<RemoteData<PaginatedList<Community>>>;
return this.findAllByHref(hrefObs, undefined);
}
protected getFindByParentHref(parentUUID: string): Observable<string> {

View File

@@ -2,7 +2,7 @@ import { cold, getTestScheduler } from 'jasmine-marbles';
import { TestScheduler } from 'rxjs/testing';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { FindByIDRequest } from './request.models';
import { GetRequest } from './request.models';
import { RequestService } from './request.service';
import { ObjectCacheService } from '../cache/object-cache.service';
import { NotificationsService } from '../../shared/notifications/notifications.service';
@@ -34,7 +34,7 @@ describe('ConfigurationDataService', () => {
});
requestService = jasmine.createSpyObj('requestService', {
generateRequestId: requestUUID,
configure: true
send: true
});
rdbService = jasmine.createSpyObj('rdbService', {
buildSingle: cold('a', {
@@ -67,11 +67,11 @@ describe('ConfigurationDataService', () => {
expect(halService.getEndpoint).toHaveBeenCalledWith('properties');
});
it('should configure the proper FindByIDRequest', () => {
it('should send the proper FindByIDRequest', () => {
scheduler.schedule(() => service.findByPropertyName(testObject.name));
scheduler.flush();
expect(requestService.configure).toHaveBeenCalledWith(new FindByIDRequest(requestUUID, requestURL, testObject.name));
expect(requestService.send).toHaveBeenCalledWith(new GetRequest(requestUUID, requestURL), true);
});
it('should return a RemoteData<ConfigurationProperty> for the object with the given name', () => {

View File

@@ -212,7 +212,7 @@ describe('DataService', () => {
it('should include nested linksToFollow 3lvl', () => {
const expected = `${endpoint}?embed=owningCollection/itemtemplate/relationships`;
(service as any).getFindAllHref({}, null, followLink('owningCollection', undefined, true, followLink('itemtemplate', undefined, true, followLink('relationships')))).subscribe((value) => {
(service as any).getFindAllHref({}, null, followLink('owningCollection', undefined, true, true, true, followLink('itemtemplate', undefined, true, true, true, followLink('relationships')))).subscribe((value) => {
expect(value).toBe(expected);
});
});
@@ -247,7 +247,7 @@ describe('DataService', () => {
it('should include nested linksToFollow 3lvl', () => {
const expected = `${endpointMock}/${resourceIdMock}?embed=owningCollection/itemtemplate/relationships`;
const result = (service as any).getIDHref(endpointMock, resourceIdMock, followLink('owningCollection', undefined, true, followLink('itemtemplate', undefined, true, followLink('relationships'))));
const result = (service as any).getIDHref(endpointMock, resourceIdMock, followLink('owningCollection', undefined, true, true, true,followLink('itemtemplate', undefined, true, true, true, followLink('relationships'))));
expect(result).toEqual(expected);
});
});
@@ -268,8 +268,8 @@ describe('DataService', () => {
service.patch(dso, operations);
});
it('should configure a PatchRequest', () => {
expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(PatchRequest));
it('should send a PatchRequest', () => {
expect(requestService.send).toHaveBeenCalledWith(jasmine.any(PatchRequest));
});
});

View File

@@ -1,16 +1,17 @@
import { HttpClient } from '@angular/common/http';
import { Store } from '@ngrx/store';
import { Operation } from 'fast-json-patch';
import { Observable } from 'rxjs';
import { Observable, of as observableOf } from 'rxjs';
import {
distinctUntilChanged,
filter,
find,
first,
map,
mergeMap,
take,
takeWhile, switchMap, tap,
takeWhile,
switchMap,
tap,
} from 'rxjs/operators';
import { hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
import { NotificationOptions } from '../../shared/notifications/models/notification-options.model';
@@ -25,22 +26,18 @@ import { CoreState } from '../core.reducers';
import { DSpaceSerializer } from '../dspace-rest/dspace.serializer';
import { DSpaceObject } from '../shared/dspace-object.model';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import {
getRemoteDataPayload,
getFirstSucceededRemoteData,
} from '../shared/operators';
import { getRemoteDataPayload, getFirstSucceededRemoteData, } from '../shared/operators';
import { URLCombiner } from '../url-combiner/url-combiner';
import { ChangeAnalyzer } from './change-analyzer';
import { PaginatedList } from './paginated-list.model';
import { RemoteData } from './remote-data';
import {
CreateRequest,
FindByIDRequest,
FindListOptions,
FindListRequest,
GetRequest,
FindListOptions,
PatchRequest,
PutRequest, DeleteRequest
PutRequest,
DeleteRequest
} from './request.models';
import { RequestService } from './request.service';
import { RestRequestMethod } from './rest-request-method';
@@ -159,23 +156,23 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
if (hasValue(options.currentPage) && typeof options.currentPage === 'number') {
/* TODO: this is a temporary fix for the pagination start index (0 or 1) discrepancy between the rest and the frontend respectively */
args = [...args, `page=${options.currentPage - 1}`];
args = this.addHrefArg(href, args, `page=${options.currentPage - 1}`);
}
if (hasValue(options.elementsPerPage)) {
args = [...args, `size=${options.elementsPerPage}`];
args = this.addHrefArg(href, args, `size=${options.elementsPerPage}`);
}
if (hasValue(options.sort)) {
args = [...args, `sort=${options.sort.field},${options.sort.direction}`];
args = this.addHrefArg(href, args, `sort=${options.sort.field},${options.sort.direction}`);
}
if (hasValue(options.startsWith)) {
args = [...args, `startsWith=${options.startsWith}`];
args = this.addHrefArg(href, args, `startsWith=${options.startsWith}`);
}
if (hasValue(options.searchParams)) {
options.searchParams.forEach((param: RequestParam) => {
args = [...args, `${param.fieldName}=${param.fieldValue}`];
args = this.addHrefArg(href, args, `${param.fieldName}=${param.fieldValue}`);
});
}
args = this.addEmbedParams(args, ...linksToFollow);
args = this.addEmbedParams(href, args, ...linksToFollow);
if (isNotEmpty(args)) {
return new URLCombiner(href, `?${args.join('&')}`).toString();
} else {
@@ -198,11 +195,11 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
let args = [];
if (hasValue(params)) {
params.forEach((param: RequestParam) => {
args.push(`${param.fieldName}=${param.fieldValue}`);
args = this.addHrefArg(href, args, `${param.fieldName}=${param.fieldValue}`);
});
}
args = this.addEmbedParams(args, ...linksToFollow);
args = this.addEmbedParams(href, args, ...linksToFollow);
if (isNotEmpty(args)) {
return new URLCombiner(href, `?${args.join('&')}`).toString();
@@ -212,20 +209,39 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
}
/**
* Adds the embed options to the link for the request
* @param href The href the params are to be added to
* @param args params for the query string
* @param linksToFollow links we want to embed in query string if shouldEmbed is true
*/
protected addEmbedParams(args: string[], ...linksToFollow: FollowLinkConfig<T>[]) {
protected addEmbedParams(href: string, args: string[], ...linksToFollow: FollowLinkConfig<T>[]) {
linksToFollow.forEach((linkToFollow: FollowLinkConfig<T>) => {
if (linkToFollow !== undefined && linkToFollow.shouldEmbed) {
const embedString = 'embed=' + String(linkToFollow.name);
const embedWithNestedString = this.addNestedEmbeds(embedString, ...linkToFollow.linksToFollow);
args = [...args, embedWithNestedString];
args = this.addHrefArg(href, args, embedWithNestedString);
}
});
return args;
}
/**
* Add a new argument to the list of arguments, only if it doesn't already exist in the given href,
* or the current list of arguments
*
* @param href The href the arguments are to be added to
* @param currentArgs The current list of arguments
* @param newArg The new argument to add
* @return The next list of arguments, with newArg included if it wasn't already.
* Note this function will not modify any of the input params.
*/
protected addHrefArg(href: string, currentArgs: string[], newArg: string): string[] {
if (href.includes(newArg) || currentArgs.includes(newArg)) {
return [...currentArgs];
} else {
return [...currentArgs, newArg];
}
}
/**
* Add the nested followLinks to the embed param, recursively, separated by a /
* @param embedString embedString so far (recursive)
@@ -248,45 +264,18 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
* Returns {@link RemoteData} of all object with a list of {@link FollowLinkConfig}, to indicate which embedded
* info should be added to the objects
*
* @param options Find list options object
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
* the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s
* should be automatically resolved
* @param options Find list options object
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
* @param reRequestOnStale Whether or not the request should automatically be re-
* requested after the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
* {@link HALLink}s should be automatically resolved
* @return {Observable<RemoteData<PaginatedList<T>>>}
* Return an observable that emits object list
*/
findAll(options: FindListOptions = {}, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<PaginatedList<T>>> {
return this.findList(this.getFindAllHref(options), options, reRequestOnStale, ...linksToFollow);
}
/**
* Returns an observable of {@link RemoteData} of an object, based on href observable,
* with a list of {@link FollowLinkConfig}, to automatically resolve {@link HALLink}s of the object
* @param href$ Observable of href of object we want to retrieve
* @param options Find list options object
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
* the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s
* should be automatically resolved
*/
protected findList(href$, options: FindListOptions, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]) {
const requestId = this.requestService.generateRequestId();
href$.pipe(
first((href: string) => hasValue(href)))
.subscribe((href: string) => {
const request = new FindListRequest(requestId, href, options);
if (hasValue(this.responseMsToLive)) {
request.responseMsToLive = this.responseMsToLive;
}
this.requestService.configure(request);
});
return this.rdbService.buildList<T>(href$, ...linksToFollow).pipe(
reRequestStaleRemoteData(reRequestOnStale, () =>
this.findList(href$, options, reRequestOnStale, ...linksToFollow))
) as Observable<RemoteData<PaginatedList<T>>>;
findAll(options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<PaginatedList<T>>> {
return this.findAllByHref(this.getFindAllHref(options), options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
}
/**
@@ -312,79 +301,108 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
/**
* Returns an observable of {@link RemoteData} of an object, based on its ID, with a list of
* {@link FollowLinkConfig}, to automatically resolve {@link HALLink}s of the object
* @param id ID of object we want to retrieve
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
* the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s
* should be automatically resolved
* @param id ID of object we want to retrieve
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
* @param reRequestOnStale Whether or not the request should automatically be re-
* requested after the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
* {@link HALLink}s should be automatically resolved
*/
findById(id: string, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<T>> {
const requestId = this.requestService.generateRequestId();
const href$ = this.getIDHrefObs(encodeURIComponent(id), ...linksToFollow).pipe(
isNotEmptyOperator(),
take(1)
);
href$.subscribe((href: string) => {
const request = new FindByIDRequest(requestId, href, id);
if (hasValue(this.responseMsToLive)) {
request.responseMsToLive = this.responseMsToLive;
}
this.requestService.configure(request);
});
return this.rdbService.buildSingle<T>(href$, ...linksToFollow).pipe(
reRequestStaleRemoteData(reRequestOnStale, () =>
this.findById(id, reRequestOnStale, ...linksToFollow))
);
findById(id: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<T>> {
const href$ = this.getIDHrefObs(encodeURIComponent(id), ...linksToFollow);
return this.findByHref(href$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
}
/**
* Returns an observable of {@link RemoteData} of an object, based on an href, with a list of
* {@link FollowLinkConfig}, to automatically resolve {@link HALLink}s of the object
* @param href The url of object we want to retrieve
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
* the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s
* should be automatically resolved
* @param href$ The url of object we want to retrieve. Can be a string or
* an Observable<string>
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
* @param reRequestOnStale Whether or not the request should automatically be re-
* requested after the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
* {@link HALLink}s should be automatically resolved
*/
findByHref(href: string, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<T>> {
const requestHref = this.buildHrefFromFindOptions(href, {}, [], ...linksToFollow);
const requestId = this.requestService.generateRequestId();
const request = new GetRequest(requestId, requestHref);
if (hasValue(this.responseMsToLive)) {
request.responseMsToLive = this.responseMsToLive;
findByHref(href$: string | Observable<string>, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<T>> {
if (typeof href$ === 'string') {
href$ = observableOf(href$);
}
this.requestService.configure(request);
return this.rdbService.buildSingle<T>(href, ...linksToFollow).pipe(
const requestHref$ = href$.pipe(
isNotEmptyOperator(),
take(1),
map((href: string) => this.buildHrefFromFindOptions(href, {}, [], ...linksToFollow))
);
this.createAndSendGetRequest(requestHref$, useCachedVersionIfAvailable);
return this.rdbService.buildSingle<T>(href$, ...linksToFollow).pipe(
reRequestStaleRemoteData(reRequestOnStale, () =>
this.findByHref(href, reRequestOnStale, ...linksToFollow))
this.findByHref(href$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow))
);
}
/**
* Returns a list of observables of {@link RemoteData} of objects, based on an href, with a list
* of {@link FollowLinkConfig}, to automatically resolve {@link HALLink}s of the object
* @param href The url of object we want to retrieve
* @param findListOptions Find list options object
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
* the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s
* should be automatically resolved
* @param href$ The url of object we want to retrieve. Can be a string or
* an Observable<string>
* @param findListOptions Find list options object
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
* @param reRequestOnStale Whether or not the request should automatically be re-
* requested after the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
* {@link HALLink}s should be automatically resolved
*/
findAllByHref(href: string, findListOptions: FindListOptions = {}, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<PaginatedList<T>>> {
const requestHref = this.buildHrefFromFindOptions(href, findListOptions, [], ...linksToFollow);
const requestId = this.requestService.generateRequestId();
const request = new GetRequest(requestId, requestHref);
if (hasValue(this.responseMsToLive)) {
request.responseMsToLive = this.responseMsToLive;
findAllByHref(href$: string | Observable<string>, findListOptions: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<PaginatedList<T>>> {
if (typeof href$ === 'string') {
href$ = observableOf(href$);
}
this.requestService.configure(request);
return this.rdbService.buildList<T>(requestHref, ...linksToFollow).pipe(
reRequestStaleRemoteData(reRequestOnStale, () =>
this.findAllByHref(href, findListOptions, reRequestOnStale, ...linksToFollow))
const requestHref$ = href$.pipe(
isNotEmptyOperator(),
take(1),
map((href: string) => this.buildHrefFromFindOptions(href, findListOptions, [], ...linksToFollow))
);
this.createAndSendGetRequest(requestHref$, useCachedVersionIfAvailable);
return this.rdbService.buildList<T>(requestHref$, ...linksToFollow).pipe(
reRequestStaleRemoteData(reRequestOnStale, () =>
this.findAllByHref(href$, findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow))
);
}
/**
* Create a GET request for the given href, and send it.
*
* @param href$ The url of object we want to retrieve. Can be a string or
* an Observable<string>
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
*/
protected createAndSendGetRequest(href$: string | Observable<string>, useCachedVersionIfAvailable = true): void {
if (isNotEmpty(href$)) {
if (typeof href$ === 'string') {
href$ = observableOf(href$);
}
href$.pipe(
isNotEmptyOperator(),
take(1)
).subscribe((href: string) => {
const requestId = this.requestService.generateRequestId();
const request = new GetRequest(requestId, href);
if (hasValue(this.responseMsToLive)) {
request.responseMsToLive = this.responseMsToLive;
}
this.requestService.send(request, useCachedVersionIfAvailable);
});
}
}
/**
@@ -401,32 +419,21 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
/**
* Make a new FindListRequest with given search method
*
* @param searchMethod The search method for the object
* @param options The [[FindListOptions]] object
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
* the response becomes stale
* @param linksToFollow The array of [[FollowLinkConfig]]
* @param searchMethod The search method for the object
* @param options The [[FindListOptions]] object
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
* @param reRequestOnStale Whether or not the request should automatically be re-
* requested after the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
* {@link HALLink}s should be automatically resolved
* @return {Observable<RemoteData<PaginatedList<T>>}
* Return an observable that emits response from the server
*/
searchBy(searchMethod: string, options: FindListOptions = {}, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<PaginatedList<T>>> {
const requestId = this.requestService.generateRequestId();
searchBy(searchMethod: string, options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<PaginatedList<T>>> {
const hrefObs = this.getSearchByHref(searchMethod, options, ...linksToFollow);
hrefObs.pipe(
find((href: string) => hasValue(href))
).subscribe((href: string) => {
const request = new FindListRequest(requestId, href, options);
if (hasValue(this.responseMsToLive)) {
request.responseMsToLive = this.responseMsToLive;
}
this.requestService.configure(request);
});
return this.rdbService.buildList(hrefObs, ...linksToFollow).pipe(
reRequestStaleRemoteData(reRequestOnStale, () =>
this.searchBy(searchMethod, options, reRequestOnStale, ...linksToFollow))
);
return this.findAllByHref(hrefObs, undefined, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
}
/**
@@ -447,14 +454,14 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
if (hasValue(this.responseMsToLive)) {
request.responseMsToLive = this.responseMsToLive;
}
this.requestService.configure(request);
this.requestService.send(request);
});
return this.rdbService.buildFromRequestUUID(requestId);
}
createPatchFromCache(object: T): Observable<Operation[]> {
const oldVersion$ = this.findByHref(object._links.self.href, false);
const oldVersion$ = this.findByHref(object._links.self.href, true, false);
return oldVersion$.pipe(
getFirstSucceededRemoteData(),
getRemoteDataPayload(),
@@ -475,7 +482,7 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
request.responseMsToLive = this.responseMsToLive;
}
this.requestService.configure(request);
this.requestService.send(request);
return this.rdbService.buildFromRequestUUID(requestId);
}
@@ -492,7 +499,7 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
if (isNotEmpty(operations)) {
this.objectCache.addPatch(object._links.self.href, operations);
}
return this.findByHref(object._links.self.href, true);
return this.findByHref(object._links.self.href, true, true);
}
)
);
@@ -524,7 +531,7 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
if (hasValue(this.responseMsToLive)) {
request.responseMsToLive = this.responseMsToLive;
}
this.requestService.configure(request);
this.requestService.send(request);
});
const result$ = this.rdbService.buildFromRequestUUID<T>(requestId);
@@ -579,7 +586,7 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
if (hasValue(this.responseMsToLive)) {
request.responseMsToLive = this.responseMsToLive;
}
this.requestService.configure(request);
this.requestService.send(request);
return this.rdbService.buildFromRequestUUID(requestId);
}

View File

@@ -9,7 +9,7 @@ import { ObjectCacheService } from '../cache/object-cache.service';
import { CoreState } from '../core.reducers';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { DsoRedirectDataService } from './dso-redirect-data.service';
import { FindByIDRequest, IdentifierType } from './request.models';
import { GetRequest, IdentifierType } from './request.models';
import { RequestService } from './request.service';
import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils';
@@ -42,7 +42,7 @@ describe('DsoRedirectDataService', () => {
});
requestService = jasmine.createSpyObj('requestService', {
generateRequestId: requestUUID,
configure: true
send: true
});
router = {
navigate: jasmine.createSpy('navigate')
@@ -93,18 +93,18 @@ describe('DsoRedirectDataService', () => {
expect(halService.getEndpoint).toHaveBeenCalledWith('dso');
});
it('should configure the proper FindByIDRequest for uuid', () => {
it('should send the proper FindByIDRequest for uuid', () => {
scheduler.schedule(() => service.findByIdAndIDType(dsoUUID, IdentifierType.UUID));
scheduler.flush();
expect(requestService.configure).toHaveBeenCalledWith(new FindByIDRequest(requestUUID, requestUUIDURL, dsoUUID));
expect(requestService.send).toHaveBeenCalledWith(new GetRequest(requestUUID, requestUUIDURL), true);
});
it('should configure the proper FindByIDRequest for handle', () => {
it('should send the proper FindByIDRequest for handle', () => {
scheduler.schedule(() => service.findByIdAndIDType(dsoHandle, IdentifierType.HANDLE));
scheduler.flush();
expect(requestService.configure).toHaveBeenCalledWith(new FindByIDRequest(requestUUID, requestHandleURL, dsoHandle));
expect(requestService.send).toHaveBeenCalledWith(new GetRequest(requestUUID, requestHandleURL), true);
});
it('should navigate to item route', () => {
@@ -162,7 +162,7 @@ describe('DsoRedirectDataService', () => {
it('should include nested linksToFollow 3lvl', () => {
const expected = `${requestUUIDURL}&embed=owningCollection/itemtemplate/relationships`;
const result = (service as any).getIDHref(pidLink, dsoUUID, followLink('owningCollection', undefined, true, followLink('itemtemplate', undefined, true, followLink('relationships'))));
const result = (service as any).getIDHref(pidLink, dsoUUID, followLink('owningCollection', undefined, true, true, true, followLink('itemtemplate', undefined, true, true, true, followLink('relationships'))));
expect(result).toEqual(expected);
});
});

View File

@@ -14,9 +14,10 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
import { DataService } from './data.service';
import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
import { RemoteData } from './remote-data';
import { FindByIDRequest, IdentifierType } from './request.models';
import { IdentifierType } from './request.models';
import { RequestService } from './request.service';
import { getFirstCompletedRemoteData } from '../shared/operators';
import { DSpaceObject } from '../shared/dspace-object.model';
@Injectable()
export class DsoRedirectDataService extends DataService<any> {
@@ -53,7 +54,7 @@ export class DsoRedirectDataService extends DataService<any> {
{}, [], ...linksToFollow);
}
findByIdAndIDType(id: string, identifierType = IdentifierType.UUID): Observable<RemoteData<FindByIDRequest>> {
findByIdAndIDType(id: string, identifierType = IdentifierType.UUID): Observable<RemoteData<DSpaceObject>> {
this.setLinkPath(identifierType);
return this.findById(id).pipe(
getFirstCompletedRemoteData(),

View File

@@ -3,7 +3,7 @@ import { TestScheduler } from 'rxjs/testing';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { DSpaceObject } from '../shared/dspace-object.model';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { FindByIDRequest } from './request.models';
import { GetRequest } from './request.models';
import { RequestService } from './request.service';
import { DSpaceObjectDataService } from './dspace-object-data.service';
import { ObjectCacheService } from '../cache/object-cache.service';
@@ -32,7 +32,7 @@ describe('DSpaceObjectDataService', () => {
});
requestService = jasmine.createSpyObj('requestService', {
generateRequestId: requestUUID,
configure: true
send: true
});
rdbService = jasmine.createSpyObj('rdbService', {
buildSingle: cold('a', {
@@ -65,11 +65,11 @@ describe('DSpaceObjectDataService', () => {
expect(halService.getEndpoint).toHaveBeenCalledWith('dso');
});
it('should configure the proper FindByIDRequest', () => {
it('should send the proper FindByIDRequest', () => {
scheduler.schedule(() => service.findById(testObject.uuid));
scheduler.flush();
expect(requestService.configure).toHaveBeenCalledWith(new FindByIDRequest(requestUUID, requestURL, testObject.uuid));
expect(requestService.send).toHaveBeenCalledWith(new GetRequest(requestUUID, requestURL), true);
});
it('should return a RemoteData<DSpaceObject> for the object with the given ID', () => {

View File

@@ -61,37 +61,46 @@ export class DSpaceObjectDataService {
* Returns an observable of {@link RemoteData} of an object, based on its ID, with a list of {@link FollowLinkConfig},
* to automatically resolve {@link HALLink}s of the object
* @param id ID of object we want to retrieve
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
* the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
* @param reRequestOnStale Whether or not the request should automatically be re-
* requested after the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
* {@link HALLink}s should be automatically resolved
*/
findById(id: string, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<DSpaceObject>[]): Observable<RemoteData<DSpaceObject>> {
return this.dataService.findById(id, reRequestOnStale, ...linksToFollow);
findById(id: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<DSpaceObject>[]): Observable<RemoteData<DSpaceObject>> {
return this.dataService.findById(id, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
}
/**
* Returns an observable of {@link RemoteData} of an object, based on an href, with a list of {@link FollowLinkConfig},
* to automatically resolve {@link HALLink}s of the object
* @param href The url of object we want to retrieve
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
* the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
* @param href The url of object we want to retrieve
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
* @param reRequestOnStale Whether or not the request should automatically be re-
* requested after the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
* {@link HALLink}s should be automatically resolved
*/
findByHref(href: string, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<DSpaceObject>[]): Observable<RemoteData<DSpaceObject>> {
return this.dataService.findByHref(href, reRequestOnStale, ...linksToFollow);
findByHref(href: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<DSpaceObject>[]): Observable<RemoteData<DSpaceObject>> {
return this.dataService.findByHref(href, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
}
/**
* Returns a list of observables of {@link RemoteData} of objects, based on an href, with a list of {@link FollowLinkConfig},
* to automatically resolve {@link HALLink}s of the object
* @param href The url of object we want to retrieve
* @param findListOptions Find list options object
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
* the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
* @param href The url of object we want to retrieve
* @param findListOptions Find list options object
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
* @param reRequestOnStale Whether or not the request should automatically be re-
* requested after the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
* {@link HALLink}s should be automatically resolved
*/
findAllByHref(href: string, findListOptions: FindListOptions = {}, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<DSpaceObject>[]): Observable<RemoteData<PaginatedList<DSpaceObject>>> {
return this.dataService.findAllByHref(href, findListOptions, reRequestOnStale, ...linksToFollow);
findAllByHref(href: string, findListOptions: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<DSpaceObject>[]): Observable<RemoteData<PaginatedList<DSpaceObject>>> {
return this.dataService.findAllByHref(href, findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
}
}

View File

@@ -1,89 +0,0 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
import { dataService } from '../cache/builders/build-decorators';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { ObjectCacheService } from '../cache/object-cache.service';
import { CoreState } from '../core.reducers';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { ItemType } from '../shared/item-relationships/item-type.model';
import { ITEM_TYPE } from '../shared/item-relationships/item-type.resource-type';
import { DataService } from './data.service';
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
import { PaginatedList } from './paginated-list.model';
import { RemoteData } from './remote-data';
import { FindListOptions } from './request.models';
import { RequestService } from './request.service';
/* tslint:disable:max-classes-per-file */
/**
* A private DataService implementation to delegate specific methods to.
*/
class DataServiceImpl extends DataService<ItemType> {
protected linkPath = 'entitytypes';
constructor(
protected requestService: RequestService,
protected rdbService: RemoteDataBuildService,
protected store: Store<CoreState>,
protected objectCache: ObjectCacheService,
protected halService: HALEndpointService,
protected notificationsService: NotificationsService,
protected http: HttpClient,
protected comparator: DefaultChangeAnalyzer<ItemType>) {
super();
}
}
/**
* A service to retrieve {@link ItemType}s from the REST API.
*/
@Injectable()
@dataService(ITEM_TYPE)
export class ItemTypeDataService {
/**
* A private DataService instance to delegate specific methods to.
*/
private dataService: DataServiceImpl;
constructor(
protected requestService: RequestService,
protected rdbService: RemoteDataBuildService,
protected store: Store<CoreState>,
protected objectCache: ObjectCacheService,
protected halService: HALEndpointService,
protected notificationsService: NotificationsService,
protected http: HttpClient,
protected comparator: DefaultChangeAnalyzer<ItemType>) {
this.dataService = new DataServiceImpl(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparator);
}
/**
* Returns an observable of {@link RemoteData} of an {@link ItemType}, based on an href, with a list of {@link FollowLinkConfig},
* to automatically resolve {@link HALLink}s of the {@link ItemType}
* @param href The url of {@link ItemType} we want to retrieve
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
* the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
*/
findByHref(href: string, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<ItemType>[]): Observable<RemoteData<ItemType>> {
return this.dataService.findByHref(href, reRequestOnStale, ...linksToFollow);
}
/**
* Returns a list of observables of {@link RemoteData} of {@link ItemType}s, based on an href, with a list of {@link FollowLinkConfig},
* to automatically resolve {@link HALLink}s of the {@link ItemType}
* @param href The url of the {@link ItemType} we want to retrieve
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
* the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
*/
findByAllHref(href: string, reRequestOnStale = true, findListOptions: FindListOptions = {}, ...linksToFollow: FollowLinkConfig<ItemType>[]): Observable<RemoteData<PaginatedList<ItemType>>> {
return this.dataService.findAllByHref(href, findListOptions, reRequestOnStale, ...linksToFollow);
}
}
/* tslint:enable:max-classes-per-file */

View File

@@ -10,14 +10,14 @@ import { NotificationsService } from '../../shared/notifications/notifications.s
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';
import { switchMap, take, map } from 'rxjs/operators';
import { RemoteData } from './remote-data';
import { RelationshipType } from '../shared/item-relationships/relationship-type.model';
import {PaginatedList} from './paginated-list.model';
import { PaginatedList } from './paginated-list.model';
import { ItemType } from '../shared/item-relationships/item-type.model';
import {getRemoteDataPayload, getFirstSucceededRemoteData} from '../shared/operators';
import { getRemoteDataPayload, getFirstSucceededRemoteData } from '../shared/operators';
import { RelationshipTypeService } from './relationship-type.service';
/**
* Service handling all ItemType requests
@@ -33,6 +33,7 @@ export class EntityTypeService extends DataService<ItemType> {
protected halService: HALEndpointService,
protected objectCache: ObjectCacheService,
protected notificationsService: NotificationsService,
protected relationshipTypeService: RelationshipTypeService,
protected http: HttpClient,
protected comparator: DefaultChangeAnalyzer<ItemType>) {
super();
@@ -69,18 +70,16 @@ export class EntityTypeService extends DataService<ItemType> {
/**
* Get the allowed relationship types for an entity type
* @param entityTypeId
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
* @param reRequestOnStale Whether or not the request should automatically be re-
* requested after the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
* {@link HALLink}s should be automatically resolved
*/
getEntityTypeRelationships(entityTypeId: string, ...linksToFollow: FollowLinkConfig<RelationshipType>[]): Observable<RemoteData<PaginatedList<RelationshipType>>> {
getEntityTypeRelationships(entityTypeId: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<RelationshipType>[]): Observable<RemoteData<PaginatedList<RelationshipType>>> {
const href$ = this.getRelationshipTypesEndpoint(entityTypeId);
href$.pipe(take(1)).subscribe((href) => {
const request = new GetRequest(this.requestService.generateRequestId(), href);
this.requestService.configure(request);
});
return this.rdbService.buildList(href$, ...linksToFollow);
return this.relationshipTypeService.findAllByHref(href$, undefined, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
}
/**

View File

@@ -31,7 +31,7 @@ describe('EpersonRegistrationService', () => {
requestService = jasmine.createSpyObj('requestService', {
generateRequestId: 'request-id',
configure: {},
send: {},
getByUUID: cold('a',
{ a: Object.assign(new RequestEntry(), { response: new RestResponse(true, 200, 'Success') }) })
});
@@ -70,7 +70,7 @@ describe('EpersonRegistrationService', () => {
const expected = service.registerEmail('test@mail.org');
expect(requestService.configure).toHaveBeenCalledWith(new PostRequest('request-id', 'rest-url/registrations', registration));
expect(requestService.send).toHaveBeenCalledWith(new PostRequest('request-id', 'rest-url/registrations', registration));
expect(expected).toBeObservable(cold('(a|)', { a: rd }));
});
});

View File

@@ -66,7 +66,7 @@ export class EpersonRegistrationService {
find((href: string) => hasValue(href)),
map((href: string) => {
const request = new PostRequest(requestId, href, registration);
this.requestService.configure(request);
this.requestService.send(request);
})
).subscribe();
@@ -93,7 +93,7 @@ export class EpersonRegistrationService {
return RegistrationResponseParsingService;
}
});
this.requestService.configure(request);
this.requestService.send(request, true);
})
).subscribe();

View File

@@ -42,7 +42,7 @@ describe('ExternalSourceService', () => {
function init() {
requestService = jasmine.createSpyObj('requestService', {
generateRequestId: 'request-uuid',
configure: {}
send: {}
});
rdbService = jasmine.createSpyObj('rdbService', {
buildList: createSuccessfulRemoteDataObject$(createPaginatedList(entries))
@@ -64,8 +64,8 @@ describe('ExternalSourceService', () => {
result = service.getExternalSourceEntries('test');
});
it('should configure a GetRequest', () => {
expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(GetRequest));
it('should send a GetRequest', () => {
expect(requestService.send).toHaveBeenCalledWith(jasmine.any(GetRequest), true);
});
it('should return the entries', () => {

View File

@@ -9,16 +9,16 @@ import { ObjectCacheService } from '../cache/object-cache.service';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { HttpClient } from '@angular/common/http';
import { FindListOptions, GetRequest } from './request.models';
import { FindListOptions } from './request.models';
import { Observable } from 'rxjs';
import { distinctUntilChanged, map, switchMap } from 'rxjs/operators';
import { distinctUntilChanged, map, switchMap, take } from 'rxjs/operators';
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
import { hasValue, isNotEmptyOperator } from '../../shared/empty.util';
import { configureRequest } from '../shared/operators';
import { RemoteData } from './remote-data';
import { PaginatedList } from './paginated-list.model';
import { ExternalSourceEntry } from '../shared/external-source-entry.model';
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
/**
* A service handling all external source requests
@@ -61,23 +61,24 @@ export class ExternalSourceService extends DataService<ExternalSource> {
/**
* Get the entries for an external source
* @param externalSourceId The id of the external source to fetch entries for
* @param searchOptions The search options to limit results to
* @param externalSourceId The id of the external source to fetch entries for
* @param searchOptions The search options to limit results to
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
* @param reRequestOnStale Whether or not the request should automatically be re-
* requested after the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
* {@link HALLink}s should be automatically resolved
*/
getExternalSourceEntries(externalSourceId: string, searchOptions?: PaginatedSearchOptions): Observable<RemoteData<PaginatedList<ExternalSourceEntry>>> {
const requestUuid = this.requestService.generateRequestId();
getExternalSourceEntries(externalSourceId: string, searchOptions?: PaginatedSearchOptions, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<ExternalSourceEntry>[]): Observable<RemoteData<PaginatedList<ExternalSourceEntry>>> {
const href$ = this.getEntriesEndpoint(externalSourceId).pipe(
isNotEmptyOperator(),
distinctUntilChanged(),
map((endpoint: string) => hasValue(searchOptions) ? searchOptions.toRestUrl(endpoint) : endpoint)
map((endpoint: string) => hasValue(searchOptions) ? searchOptions.toRestUrl(endpoint) : endpoint),
take(1)
);
href$.pipe(
map((endpoint: string) => new GetRequest(requestUuid, endpoint)),
configureRequest(this.requestService)
).subscribe();
return this.rdbService.buildList(href$);
// TODO create a dedicated ExternalSourceEntryDataService and move this entire method to it. Then the "as any"s won't be necessary
return this.findAllByHref(href$, undefined, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow as any) as any;
}
}

View File

@@ -68,7 +68,7 @@ describe('AuthorizationDataService', () => {
});
it('should call searchBy with the site\'s url', () => {
expect(service.searchBy).toHaveBeenCalledWith('object', createExpected(site.self), true);
expect(service.searchBy).toHaveBeenCalledWith('object', createExpected(site.self), true, true);
});
});
@@ -78,7 +78,7 @@ describe('AuthorizationDataService', () => {
});
it('should call searchBy with the site\'s url and the feature', () => {
expect(service.searchBy).toHaveBeenCalledWith('object', createExpected(site.self, null, FeatureID.LoginOnBehalfOf), true);
expect(service.searchBy).toHaveBeenCalledWith('object', createExpected(site.self, null, FeatureID.LoginOnBehalfOf), true, true);
});
});
@@ -88,7 +88,7 @@ describe('AuthorizationDataService', () => {
});
it('should call searchBy with the object\'s url and the feature', () => {
expect(service.searchBy).toHaveBeenCalledWith('object', createExpected(objectUrl, null, FeatureID.LoginOnBehalfOf), true);
expect(service.searchBy).toHaveBeenCalledWith('object', createExpected(objectUrl, null, FeatureID.LoginOnBehalfOf), true, true);
});
});
@@ -98,7 +98,7 @@ describe('AuthorizationDataService', () => {
});
it('should call searchBy with the object\'s url, user\'s uuid and the feature', () => {
expect(service.searchBy).toHaveBeenCalledWith('object', createExpected(objectUrl, ePersonUuid, FeatureID.LoginOnBehalfOf), true);
expect(service.searchBy).toHaveBeenCalledWith('object', createExpected(objectUrl, ePersonUuid, FeatureID.LoginOnBehalfOf), true, true);
});
});
});

View File

@@ -60,7 +60,7 @@ export class AuthorizationDataService extends DataService<Authorization> {
* @param featureId ID of the {@link Feature} to check {@link Authorization} for
*/
isAuthorized(featureId?: FeatureID, objectUrl?: string, ePersonUuid?: string): Observable<boolean> {
return this.searchByObject(featureId, objectUrl, ePersonUuid, {}, followLink('feature')).pipe(
return this.searchByObject(featureId, objectUrl, ePersonUuid, {}, true, true, followLink('feature')).pipe(
getFirstCompletedRemoteData(),
map((authorizationRD) => {
if (authorizationRD.statusCode !== 401 && hasValue(authorizationRD.payload) && isNotEmpty(authorizationRD.payload.page)) {
@@ -77,19 +77,24 @@ export class AuthorizationDataService extends DataService<Authorization> {
/**
* Search for a list of {@link Authorization}s using the "object" search endpoint and providing optional object url,
* {@link EPerson} uuid and/or {@link Feature} id
* @param objectUrl URL to the object to search {@link Authorization}s for.
* If not provided, the repository's {@link Site} will be used.
* @param ePersonUuid UUID of the {@link EPerson} to search {@link Authorization}s for.
* If not provided, the UUID of the currently authenticated {@link EPerson} will be used.
* @param featureId ID of the {@link Feature} to search {@link Authorization}s for
* @param options {@link FindListOptions} to provide pagination and/or additional arguments
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
* @param objectUrl URL to the object to search {@link Authorization}s for.
* If not provided, the repository's {@link Site} will be used.
* @param ePersonUuid UUID of the {@link EPerson} to search {@link Authorization}s for.
* If not provided, the UUID of the currently authenticated {@link EPerson} will be used.
* @param featureId ID of the {@link Feature} to search {@link Authorization}s for
* @param options {@link FindListOptions} to provide pagination and/or additional arguments
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
* @param reRequestOnStale Whether or not the request should automatically be re-
* requested after the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
* {@link HALLink}s should be automatically resolved
*/
searchByObject(featureId?: FeatureID, objectUrl?: string, ePersonUuid?: string, options: FindListOptions = {}, ...linksToFollow: FollowLinkConfig<Authorization>[]): Observable<RemoteData<PaginatedList<Authorization>>> {
searchByObject(featureId?: FeatureID, objectUrl?: string, ePersonUuid?: string, options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Authorization>[]): Observable<RemoteData<PaginatedList<Authorization>>> {
return observableOf(new AuthorizationSearchParams(objectUrl, ePersonUuid, featureId)).pipe(
addSiteObjectUrlIfEmpty(this.siteService),
switchMap((params: AuthorizationSearchParams) => {
return this.searchBy(this.searchByObjectPath, this.createSearchOptions(params.objectUrl, options, params.ePersonUuid, params.featureId), true, ...linksToFollow);
return this.searchBy(this.searchByObjectPath, this.createSearchOptions(params.objectUrl, options, params.ePersonUuid, params.featureId), useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
})
);
}

View File

@@ -0,0 +1,82 @@
import { HrefOnlyDataService } from './href-only-data.service';
import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
import { FindListOptions } from './request.models';
import { DataService } from './data.service';
describe(`HrefOnlyDataService`, () => {
let service: HrefOnlyDataService;
let href: string;
let spy: jasmine.Spy;
let followLinks: FollowLinkConfig<any>[];
let findListOptions: FindListOptions;
beforeEach(() => {
href = 'https://rest.api/server/api/core/items/de7fa215-4a25-43a7-a4d7-17534a09fdfc';
followLinks = [ followLink('link1'), followLink('link2') ];
findListOptions = new FindListOptions();
service = new HrefOnlyDataService(null, null, null, null, null, null, null, null);
});
it(`should instantiate a private DataService`, () => {
expect((service as any).dataService).toBeDefined();
expect((service as any).dataService).toBeInstanceOf(DataService);
});
describe(`findByHref`, () => {
beforeEach(() => {
spy = spyOn((service as any).dataService, 'findByHref').and.returnValue(createSuccessfulRemoteDataObject$(null));
});
it(`should delegate to findByHref on the internal DataService`, () => {
service.findByHref(href, false, false, ...followLinks);
expect(spy).toHaveBeenCalledWith(href, false, false, ...followLinks);
});
describe(`when useCachedVersionIfAvailable is omitted`, () => {
it(`should call findByHref on the internal DataService with useCachedVersionIfAvailable = true`, () => {
service.findByHref(href);
expect(spy).toHaveBeenCalledWith(jasmine.anything(), true, jasmine.anything());
});
});
describe(`when reRequestOnStale is omitted`, () => {
it(`should call findByHref on the internal DataService with reRequestOnStale = true`, () => {
service.findByHref(href);
expect(spy).toHaveBeenCalledWith(jasmine.anything(), jasmine.anything(), true);
});
});
});
describe(`findAllByHref`, () => {
beforeEach(() => {
spy = spyOn((service as any).dataService, 'findAllByHref').and.returnValue(createSuccessfulRemoteDataObject$(null));
});
it(`should delegate to findAllByHref on the internal DataService`, () => {
service.findAllByHref(href, findListOptions, false, false, ...followLinks);
expect(spy).toHaveBeenCalledWith(href, findListOptions, false, false, ...followLinks);
});
describe(`when findListOptions is omitted`, () => {
it(`should call findAllByHref on the internal DataService with findListOptions = {}`, () => {
service.findAllByHref(href);
expect(spy).toHaveBeenCalledWith(jasmine.anything(), {}, jasmine.anything(), jasmine.anything());
});
});
describe(`when useCachedVersionIfAvailable is omitted`, () => {
it(`should call findAllByHref on the internal DataService with useCachedVersionIfAvailable = true`, () => {
service.findAllByHref(href);
expect(spy).toHaveBeenCalledWith(jasmine.anything(), jasmine.anything(), true, jasmine.anything());
});
});
describe(`when reRequestOnStale is omitted`, () => {
it(`should call findAllByHref on the internal DataService with reRequestOnStale = true`, () => {
service.findAllByHref(href);
expect(spy).toHaveBeenCalledWith(jasmine.anything(), jasmine.anything(), jasmine.anything(), true);
});
});
});
});

View File

@@ -0,0 +1,99 @@
import { DataService } from './data.service';
import { RequestService } from './request.service';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { Store } from '@ngrx/store';
import { CoreState } from '../core.reducers';
import { ObjectCacheService } from '../cache/object-cache.service';
import { HALEndpointService } from '../shared/hal-endpoint.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 { VOCABULARY_ENTRY } from '../submission/vocabularies/models/vocabularies.resource-type';
import { FindListOptions } from './request.models';
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
import { dataService } from '../cache/builders/build-decorators';
import { RemoteData } from './remote-data';
import { Observable } from 'rxjs/internal/Observable';
import { PaginatedList } from './paginated-list.model';
import { ITEM_TYPE } from '../shared/item-relationships/item-type.resource-type';
import { LICENSE } from '../shared/license.resource-type';
import { CacheableObject } from '../cache/object-cache.reducer';
/* tslint:disable:max-classes-per-file */
class DataServiceImpl extends DataService<any> {
// linkPath isn't used if we're only searching by href.
protected linkPath = undefined;
constructor(
protected requestService: RequestService,
protected rdbService: RemoteDataBuildService,
protected store: Store<CoreState>,
protected objectCache: ObjectCacheService,
protected halService: HALEndpointService,
protected notificationsService: NotificationsService,
protected http: HttpClient,
protected comparator: DefaultChangeAnalyzer<any>) {
super();
}
}
/**
* A DataService with only findByHref methods. Its purpose is to be used for resources that don't
* need to be retrieved by ID, or have any way to update them, but require a DataService in order
* for their links to be resolved by the LinkService.
*
* an @dataService annotation can be added for any number of these resource types
*/
@Injectable({
providedIn: 'root'
})
@dataService(VOCABULARY_ENTRY)
@dataService(ITEM_TYPE)
@dataService(LICENSE)
export class HrefOnlyDataService {
private dataService: DataServiceImpl;
constructor(
protected requestService: RequestService,
protected rdbService: RemoteDataBuildService,
protected store: Store<CoreState>,
protected objectCache: ObjectCacheService,
protected halService: HALEndpointService,
protected notificationsService: NotificationsService,
protected http: HttpClient,
protected comparator: DefaultChangeAnalyzer<any>) {
this.dataService = new DataServiceImpl(requestService, rdbService, store, objectCache, halService, notificationsService, http, comparator);
}
/**
* Returns an observable of {@link RemoteData} of an object, based on an href, with a list of {@link FollowLinkConfig},
* to automatically resolve {@link HALLink}s of the object
* @param href The url of object we want to retrieve
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
* @param reRequestOnStale Whether or not the request should automatically be re-
* requested after the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
* {@link HALLink}s should be automatically resolved
*/
findByHref<T extends CacheableObject>(href: string | Observable<string>, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<T>> {
return this.dataService.findByHref(href, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
}
/**
* Returns a list of observables of {@link RemoteData} of objects, based on an href, with a list of {@link FollowLinkConfig},
* to automatically resolve {@link HALLink}s of the object
* @param href The url of object we want to retrieve
* @param findListOptions Find list options object
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
* @param reRequestOnStale Whether or not the request should automatically be re-
* requested after the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
* {@link HALLink}s should be automatically resolved
*/
findAllByHref<T extends CacheableObject>(href: string | Observable<string>, findListOptions: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<PaginatedList<T>>> {
return this.dataService.findAllByHref(href, findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
}
}

View File

@@ -126,8 +126,8 @@ describe('ItemDataService', () => {
result = service.removeMappingFromCollection('item-id', 'collection-id');
});
it('should configure a DELETE request', () => {
result.subscribe(() => expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(DeleteRequest)));
it('should send a DELETE request', () => {
result.subscribe(() => expect(requestService.send).toHaveBeenCalledWith(jasmine.any(DeleteRequest)));
});
});
@@ -139,8 +139,8 @@ describe('ItemDataService', () => {
result = service.mapToCollection('item-id', 'collection-href');
});
it('should configure a POST request', () => {
result.subscribe(() => expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(PostRequest)));
it('should send a POST request', () => {
result.subscribe(() => expect(requestService.send).toHaveBeenCalledWith(jasmine.any(PostRequest)));
});
});
@@ -158,9 +158,9 @@ describe('ItemDataService', () => {
result = service.importExternalSourceEntry(externalSourceEntry, 'collection-id');
});
it('should configure a POST request', (done) => {
it('should send a POST request', (done) => {
result.subscribe(() => {
expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(PostRequest));
expect(requestService.send).toHaveBeenCalledWith(jasmine.any(PostRequest));
done();
});
});
@@ -176,9 +176,9 @@ describe('ItemDataService', () => {
result = service.createBundle(itemId, bundleName);
});
it('should configure a POST request', (done) => {
it('should send a POST request', (done) => {
result.subscribe(() => {
expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(PostRequest));
expect(requestService.send).toHaveBeenCalledWith(jasmine.any(PostRequest));
done();
});
});

View File

@@ -16,7 +16,7 @@ import { ExternalSourceEntry } from '../shared/external-source-entry.model';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { Item } from '../shared/item.model';
import { ITEM } from '../shared/item.resource-type';
import { configureRequest } from '../shared/operators';
import { sendRequest } from '../shared/operators';
import { URLCombiner } from '../url-combiner/url-combiner';
import { DataService } from './data.service';
@@ -99,7 +99,7 @@ export class ItemDataService extends DataService<Item> {
isNotEmptyOperator(),
distinctUntilChanged(),
map((endpointURL: string) => new DeleteRequest(this.requestService.generateRequestId(), endpointURL)),
configureRequest(this.requestService),
sendRequest(this.requestService),
switchMap((request: RestRequest) => this.rdbService.buildFromRequestUUID(request.uuid)),
);
}
@@ -120,29 +120,11 @@ export class ItemDataService extends DataService<Item> {
options.headers = headers;
return new PostRequest(this.requestService.generateRequestId(), endpointURL, collectionHref, options);
}),
configureRequest(this.requestService),
sendRequest(this.requestService),
switchMap((request: RestRequest) => this.rdbService.buildFromRequestUUID(request.uuid))
);
}
/**
* Fetches all collections the item is mapped to
* @param itemId The item's id
*/
public getMappedCollections(itemId: string): Observable<RemoteData<PaginatedList<Collection>>> {
const href$ = this.getMappedCollectionsEndpoint(itemId).pipe(
isNotEmptyOperator(),
take(1)
);
href$.subscribe((href: string) => {
const request = new GetRequest(this.requestService.generateRequestId(), href);
this.requestService.configure(request);
});
return this.rdbService.buildList(href$);
}
/**
* Set the isWithdrawn state of an item to a specified state
* @param item
@@ -196,7 +178,7 @@ export class ItemDataService extends DataService<Item> {
take(1)
).subscribe((href) => {
const request = new GetRequest(this.requestService.generateRequestId(), href);
this.requestService.configure(request);
this.requestService.send(request);
});
return this.rdbService.buildList<Bundle>(hrefObs);
@@ -225,7 +207,7 @@ export class ItemDataService extends DataService<Item> {
headers = headers.append('Content-Type', 'application/json');
options.headers = headers;
const request = new PostRequest(requestId, href, JSON.stringify(bundleJson), options);
this.requestService.configure(request);
this.requestService.send(request);
});
return this.rdbService.buildFromRequestUUID(requestId);
@@ -260,7 +242,7 @@ export class ItemDataService extends DataService<Item> {
find((href: string) => hasValue(href)),
map((href: string) => {
const request = new PutRequest(requestId, href, collection._links.self.href, options);
this.requestService.configure(request);
this.requestService.send(request);
})
).subscribe();
@@ -285,7 +267,7 @@ export class ItemDataService extends DataService<Item> {
find((href: string) => hasValue(href)),
map((href: string) => {
const request = new PostRequest(requestId, href, externalSourceEntry._links.self.href, options);
this.requestService.configure(request);
this.requestService.send(request);
})
).subscribe();

View File

@@ -28,7 +28,7 @@ describe('ItemTemplateDataService', () => {
generateRequestId(): string {
return scopeID;
},
configure(request: RestRequest) {
send(request: RestRequest) {
// Do nothing
},
getByHref(requestHref: string) {

View File

@@ -100,13 +100,16 @@ class DataServiceImpl extends ItemDataService {
/**
* Set the collection ID and send a find by ID request
* @param collectionID
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
* the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
* @param reRequestOnStale Whether or not the request should automatically be re-
* requested after the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
* {@link HALLink}s should be automatically resolved
*/
findByCollectionID(collectionID: string, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Item>[]): Observable<RemoteData<Item>> {
findByCollectionID(collectionID: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Item>[]): Observable<RemoteData<Item>> {
this.setCollectionEndpoint(collectionID);
return super.findById(collectionID, reRequestOnStale, ...linksToFollow);
return super.findById(collectionID, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
}
/**
@@ -178,12 +181,15 @@ export class ItemTemplateDataService implements UpdateDataService<Item> {
/**
* Find an item template by collection ID
* @param collectionID
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
* the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
* @param reRequestOnStale Whether or not the request should automatically be re-
* requested after the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
* {@link HALLink}s should be automatically resolved
*/
findByCollectionID(collectionID: string, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Item>[]): Observable<RemoteData<Item>> {
return this.dataService.findByCollectionID(collectionID, reRequestOnStale, ...linksToFollow);
findByCollectionID(collectionID: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Item>[]): Observable<RemoteData<Item>> {
return this.dataService.findByCollectionID(collectionID, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
}
/**

View File

@@ -1,89 +0,0 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
import { dataService } from '../cache/builders/build-decorators';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { ObjectCacheService } from '../cache/object-cache.service';
import { CoreState } from '../core.reducers';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { License } from '../shared/license.model';
import { LICENSE } from '../shared/license.resource-type';
import { DataService } from './data.service';
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
import { PaginatedList } from './paginated-list.model';
import { RemoteData } from './remote-data';
import { FindListOptions } from './request.models';
import { RequestService } from './request.service';
/* tslint:disable:max-classes-per-file */
/**
* A private DataService implementation to delegate specific methods to.
*/
class DataServiceImpl extends DataService<License> {
protected linkPath = '';
constructor(
protected requestService: RequestService,
protected rdbService: RemoteDataBuildService,
protected store: Store<CoreState>,
protected objectCache: ObjectCacheService,
protected halService: HALEndpointService,
protected notificationsService: NotificationsService,
protected http: HttpClient,
protected comparator: DefaultChangeAnalyzer<License>) {
super();
}
}
/**
* A service to retrieve {@link License}s from the REST API.
*/
@Injectable()
@dataService(LICENSE)
export class LicenseDataService {
/**
* A private DataService instance to delegate specific methods to.
*/
private dataService: DataServiceImpl;
constructor(
protected requestService: RequestService,
protected rdbService: RemoteDataBuildService,
protected store: Store<CoreState>,
protected objectCache: ObjectCacheService,
protected halService: HALEndpointService,
protected notificationsService: NotificationsService,
protected http: HttpClient,
protected comparator: DefaultChangeAnalyzer<License>) {
this.dataService = new DataServiceImpl(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparator);
}
/**
* Returns an observable of {@link RemoteData} of a {@link License}, based on an href, with a list of {@link FollowLinkConfig},
* to automatically resolve {@link HALLink}s of the {@link License}
* @param href The URL of object we want to retrieve
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
* the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
*/
findByHref(href: string, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<License>[]): Observable<RemoteData<License>> {
return this.dataService.findByHref(href, reRequestOnStale, ...linksToFollow);
}
/**
* Returns a list of observables of {@link RemoteData} of {@link License}s, based on an href, with a list of {@link FollowLinkConfig},
* to automatically resolve {@link HALLink}s of the {@link License}
* @param href The URL of object we want to retrieve
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
* the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
*/
findByAllHref(href: string, reRequestOnStale = true, findListOptions: FindListOptions = {}, ...linksToFollow: FollowLinkConfig<License>[]): Observable<RemoteData<PaginatedList<License>>> {
return this.dataService.findAllByHref(href, findListOptions, reRequestOnStale, ...linksToFollow);
}
}
/* tslint:enable:max-classes-per-file */

View File

@@ -31,7 +31,7 @@ describe('MetadataFieldDataService', () => {
});
requestService = jasmine.createSpyObj('requestService', {
generateRequestId: '34cfed7c-f597-49ef-9cbe-ea351f0023c2',
configure: {},
send: {},
getByUUID: observableOf({ response: new RestResponse(true, 200, 'OK') }),
removeByHrefSubstring: {}
});
@@ -59,7 +59,7 @@ describe('MetadataFieldDataService', () => {
const expectedOptions = Object.assign(new FindListOptions(), {
searchParams: [new RequestParam('schema', schema.prefix)]
});
expect(metadataFieldService.searchBy).toHaveBeenCalledWith('bySchema', expectedOptions, true);
expect(metadataFieldService.searchBy).toHaveBeenCalledWith('bySchema', expectedOptions, true, true);
});
});

View File

@@ -46,17 +46,20 @@ export class MetadataFieldDataService extends DataService<MetadataField> {
/**
* Find metadata fields belonging to a metadata schema
* @param schema The metadata schema to list fields for
* @param options The options info used to retrieve the fields
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
* the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
* @param schema The metadata schema to list fields for
* @param options The options info used to retrieve the fields
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
* @param reRequestOnStale Whether or not the request should automatically be re-
* requested after the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
* {@link HALLink}s should be automatically resolved
*/
findBySchema(schema: MetadataSchema, options: FindListOptions = {}, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<MetadataField>[]) {
findBySchema(schema: MetadataSchema, options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<MetadataField>[]) {
const optionsWithSchema = Object.assign(new FindListOptions(), options, {
searchParams: [new RequestParam('schema', schema.prefix)]
});
return this.searchBy(this.searchBySchemaLinkPath, optionsWithSchema, reRequestOnStale, ...linksToFollow);
return this.searchBy(this.searchBySchemaLinkPath, optionsWithSchema, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
}
/**
@@ -71,10 +74,11 @@ export class MetadataFieldDataService extends DataService<MetadataField> {
* schema.element if no qualifier exists (e.g. "dc.title", "dc.contributor.author"). It will only return one value
* if there's an exact match
* @param options The options info used to retrieve the fields
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's no valid cached version. Defaults to true
* @param reRequestOnStale Whether or not the request should automatically be re-requested after the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
*/
searchByFieldNameParams(schema: string, element: string, qualifier: string, query: string, exactName: string, options: FindListOptions = {}, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<MetadataField>[]): Observable<RemoteData<PaginatedList<MetadataField>>> {
searchByFieldNameParams(schema: string, element: string, qualifier: string, query: string, exactName: string, options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<MetadataField>[]): Observable<RemoteData<PaginatedList<MetadataField>>> {
const optionParams = Object.assign(new FindListOptions(), options, {
searchParams: [
new RequestParam('schema', hasValue(schema) ? schema : ''),
@@ -84,7 +88,7 @@ export class MetadataFieldDataService extends DataService<MetadataField> {
new RequestParam('exactName', hasValue(exactName) ? exactName : '')
]
});
return this.searchBy(this.searchByFieldNameLinkPath, optionParams, reRequestOnStale, ...linksToFollow);
return this.searchBy(this.searchByFieldNameLinkPath, optionParams, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
}
/**

View File

@@ -22,7 +22,7 @@ describe('MetadataSchemaDataService', () => {
function init() {
requestService = jasmine.createSpyObj('requestService', {
generateRequestId: '34cfed7c-f597-49ef-9cbe-ea351f0023c2',
configure: {},
send: {},
getByUUID: observableOf({ response: new RestResponse(true, 200, 'OK') }),
removeByHrefSubstring: {}
});
@@ -54,7 +54,7 @@ describe('MetadataSchemaDataService', () => {
describe('called with a new metadata schema', () => {
it('should send a CreateRequest', (done) => {
metadataSchemaService.createOrUpdateMetadataSchema(schema).subscribe(() => {
expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(CreateRequest));
expect(requestService.send).toHaveBeenCalledWith(jasmine.any(CreateRequest));
done();
});
});
@@ -69,7 +69,7 @@ describe('MetadataSchemaDataService', () => {
it('should send a PutRequest', (done) => {
metadataSchemaService.createOrUpdateMetadataSchema(schema).subscribe(() => {
expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(PutRequest));
expect(requestService.send).toHaveBeenCalledWith(jasmine.any(PutRequest));
done();
});
});

View File

@@ -13,12 +13,11 @@ import { Process } from '../../../process-page/processes/process.model';
import { dataService } from '../../cache/builders/build-decorators';
import { PROCESS } from '../../../process-page/processes/process.resource-type';
import { Observable } from 'rxjs';
import { switchMap, take } from 'rxjs/operators';
import { GetRequest } from '../request.models';
import { switchMap } from 'rxjs/operators';
import { PaginatedList } from '../paginated-list.model';
import { Bitstream } from '../../shared/bitstream.model';
import { RemoteData } from '../remote-data';
import { isNotEmptyOperator } from '../../../shared/empty.util';
import { BitstreamDataService } from '../bitstream-data.service';
@Injectable()
@dataService(PROCESS)
@@ -32,6 +31,7 @@ export class ProcessDataService extends DataService<Process> {
protected objectCache: ObjectCacheService,
protected halService: HALEndpointService,
protected notificationsService: NotificationsService,
protected bitstreamDataService: BitstreamDataService,
protected http: HttpClient,
protected comparator: DefaultChangeAnalyzer<Process>) {
super();
@@ -48,20 +48,11 @@ export class ProcessDataService extends DataService<Process> {
}
/**
* Get a process his output files
* Get a process' output files
* @param processId The ID of the process
*/
getFiles(processId: string): Observable<RemoteData<PaginatedList<Bitstream>>> {
const href$ = this.getFilesEndpoint(processId).pipe(
isNotEmptyOperator(),
take(1)
);
href$.subscribe((href: string) => {
const request = new GetRequest(this.requestService.generateRequestId(), href);
this.requestService.configure(request);
});
return this.rdbService.buildList(href$);
const href$ = this.getFilesEndpoint(processId);
return this.bitstreamDataService.findAllByHref(href$);
}
}

View File

@@ -49,7 +49,7 @@ export class ScriptDataService extends DataService<Script> {
const body = this.getInvocationFormData(parameters, files);
return new MultipartPostRequest(requestId, endpoint, body);
})
).subscribe((request: RestRequest) => this.requestService.configure(request));
).subscribe((request: RestRequest) => this.requestService.send(request));
return this.rdbService.buildFromRequestUUID<Process>(requestId);
}

View File

@@ -2,7 +2,10 @@ import { of as observableOf } from 'rxjs';
import { getMockRemoteDataBuildService } from '../../shared/mocks/remote-data-build.service.mock';
import { getMockRequestService } from '../../shared/mocks/request.service.mock';
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service.stub';
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
import {
createSuccessfulRemoteDataObject,
createSuccessfulRemoteDataObject$
} from '../../shared/remote-data.utils';
import { ObjectCacheService } from '../cache/object-cache.service';
import { ItemType } from '../shared/item-relationships/item-type.model';
import { RelationshipType } from '../shared/item-relationships/relationship-type.model';
@@ -94,17 +97,6 @@ describe('RelationshipTypeService', () => {
service = initTestService();
});
describe('getAllRelationshipTypes', () => {
it('should return all relationshipTypes', (done) => {
const expected = service.getAllRelationshipTypes({});
expected.subscribe((e) => {
expect(e).toBe(buildList);
done();
});
});
});
describe('getRelationshipTypeByLabelAndTypes', () => {
it('should return the type filtered by label and type strings', (done) => {

View File

@@ -15,13 +15,12 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
import { ItemType } from '../shared/item-relationships/item-type.model';
import { RelationshipType } from '../shared/item-relationships/relationship-type.model';
import { RELATIONSHIP_TYPE } from '../shared/item-relationships/relationship-type.resource-type';
import { configureRequest, getFirstSucceededRemoteData } from '../shared/operators';
import { getFirstSucceededRemoteData } from '../shared/operators';
import { DataService } from './data.service';
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
import { ItemDataService } from './item-data.service';
import { PaginatedList } from './paginated-list.model';
import { RemoteData } from './remote-data';
import { FindListOptions, FindListRequest } from './request.models';
import { RequestService } from './request.service';
/**
@@ -45,32 +44,12 @@ export class RelationshipTypeService extends DataService<RelationshipType> {
super();
}
/**
* Get the endpoint for a relationship type by ID
* @param id
*/
getRelationshipTypeEndpoint(id: number) {
return this.halService.getEndpoint(this.linkPath).pipe(
map((href: string) => `${href}/${id}`)
);
}
getAllRelationshipTypes(options: FindListOptions): Observable<RemoteData<PaginatedList<RelationshipType>>> {
const link$ = this.halService.getEndpoint(this.linkPath);
return link$
.pipe(
map((endpointURL: string) => new FindListRequest(this.requestService.generateRequestId(), endpointURL, options)),
configureRequest(this.requestService),
switchMap(() => this.rdbService.buildList<RelationshipType>(link$, followLink('leftType'), followLink('rightType')))
) as Observable<RemoteData<PaginatedList<RelationshipType>>>;
}
/**
* Get the RelationshipType for a relationship type by label
* @param label
*/
getRelationshipTypeByLabelAndTypes(label: string, firstType: string, secondType: string): Observable<RelationshipType> {
return this.getAllRelationshipTypes({ currentPage: 1, elementsPerPage: Number.MAX_VALUE })
return this.findAll({ currentPage: 1, elementsPerPage: 9999 }, true, true, followLink('leftType'), followLink('rightType'))
.pipe(
getFirstSucceededRemoteData(),
/* Flatten the page so we can treat it like an observable */

View File

@@ -156,7 +156,7 @@ xdescribe('RelationshipService', () => {
it('should send a DeleteRequest', () => {
const expected = new DeleteRequest(requestService.generateRequestId(), relationshipsEndpointURL + '/' + relationship1.uuid + '?copyVirtualMetadata=right');
expect(requestService.configure).toHaveBeenCalledWith(expected);
expect(requestService.send).toHaveBeenCalledWith(expected);
});
it('should clear the cache of the related items', () => {
@@ -210,6 +210,7 @@ xdescribe('RelationshipService', () => {
mockLabel,
mockOptions,
true,
true,
followLink('leftItem'),
followLink('rightItem'),
followLink('relationshipType')

View File

@@ -30,7 +30,7 @@ import { Relationship } from '../shared/item-relationships/relationship.model';
import { RELATIONSHIP } from '../shared/item-relationships/relationship.resource-type';
import { Item } from '../shared/item.model';
import {
configureRequest,
sendRequest,
getFirstCompletedRemoteData,
getFirstSucceededRemoteData,
getFirstSucceededRemoteDataPayload,
@@ -113,7 +113,7 @@ export class RelationshipService extends DataService<Relationship> {
map((endpointURL: string) =>
new DeleteRequest(this.requestService.generateRequestId(), endpointURL + '?copyVirtualMetadata=' + copyVirtualMetadata)
),
configureRequest(this.requestService),
sendRequest(this.requestService),
switchMap((restRequest: RestRequest) => this.rdbService.buildFromRequestUUID(restRequest.uuid)),
getFirstCompletedRemoteData(),
tap(() => this.refreshRelationshipItemsInCacheByRelationship(id)),
@@ -140,7 +140,7 @@ export class RelationshipService extends DataService<Relationship> {
map((endpointUrl: string) => isNotEmpty(leftwardValue) ? `${endpointUrl}&leftwardValue=${leftwardValue}` : endpointUrl),
map((endpointUrl: string) => isNotEmpty(rightwardValue) ? `${endpointUrl}&rightwardValue=${rightwardValue}` : endpointUrl),
map((endpointURL: string) => new PostRequest(this.requestService.generateRequestId(), endpointURL, `${item1.self} \n ${item2.self}`, options)),
configureRequest(this.requestService),
sendRequest(this.requestService),
switchMap((restRequest: RestRequest) => this.rdbService.buildFromRequestUUID(restRequest.uuid)),
getFirstCompletedRemoteData(),
tap(() => this.refreshRelationshipItemsInCache(item1)),
@@ -153,7 +153,7 @@ export class RelationshipService extends DataService<Relationship> {
* @param relationshipId The identifier of the relationship
*/
private refreshRelationshipItemsInCacheByRelationship(relationshipId: string) {
this.findById(relationshipId, false, followLink('leftItem'), followLink('rightItem')).pipe(
this.findById(relationshipId, true, false, followLink('leftItem'), followLink('rightItem')).pipe(
getFirstSucceededRemoteData(),
getRemoteDataPayload(),
switchMap((rel: Relationship) => observableCombineLatest(
@@ -181,8 +181,7 @@ export class RelationshipService extends DataService<Relationship> {
]).pipe(
filter(([existsInOC, existsInRC]) => !existsInOC && !existsInRC),
take(1),
switchMap(() => this.itemService.findByHref(item._links.self.href).pipe(take(1)))
).subscribe();
).subscribe(() => this.itemService.findByHref(item._links.self.href, false));
}
/**
@@ -193,7 +192,7 @@ export class RelationshipService extends DataService<Relationship> {
* should be automatically resolved
*/
getItemRelationshipsArray(item: Item, ...linksToFollow: FollowLinkConfig<Relationship>[]): Observable<Relationship[]> {
return this.findAllByHref(item._links.relationships.href, undefined, false, ...linksToFollow).pipe(
return this.findAllByHref(item._links.relationships.href, undefined, true, false, ...linksToFollow).pipe(
getFirstSucceededRemoteData(),
getRemoteDataPayload(),
map((rels: PaginatedList<Relationship>) => rels.page),
@@ -255,7 +254,7 @@ export class RelationshipService extends DataService<Relationship> {
* @param options
*/
getRelatedItemsByLabel(item: Item, label: string, options?: FindListOptions): Observable<RemoteData<PaginatedList<Item>>> {
return this.getItemRelationshipsByLabel(item, label, options, true, followLink('leftItem'), followLink('rightItem'), followLink('relationshipType')).pipe(paginatedRelationsToItems(item.uuid));
return this.getItemRelationshipsByLabel(item, label, options, true, true, followLink('leftItem'), followLink('rightItem'), followLink('relationshipType')).pipe(paginatedRelationsToItems(item.uuid));
}
/**
@@ -265,12 +264,14 @@ export class RelationshipService extends DataService<Relationship> {
* @param item
* @param label
* @param options
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
* the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s
* should be automatically resolved
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
* @param reRequestOnStale Whether or not the request should automatically be re-
* requested after the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
* {@link HALLink}s should be automatically resolved
*/
getItemRelationshipsByLabel(item: Item, label: string, options?: FindListOptions, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Relationship>[]): Observable<RemoteData<PaginatedList<Relationship>>> {
getItemRelationshipsByLabel(item: Item, label: string, options?: FindListOptions, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Relationship>[]): Observable<RemoteData<PaginatedList<Relationship>>> {
let findListOptions = new FindListOptions();
if (options) {
findListOptions = Object.assign(new FindListOptions(), options);
@@ -281,7 +282,7 @@ export class RelationshipService extends DataService<Relationship> {
} else {
findListOptions.searchParams = searchParams;
}
return this.searchBy('byLabel', findListOptions, reRequestOnStale, ...linksToFollow);
return this.searchBy('byLabel', findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
}
/**
@@ -326,6 +327,7 @@ export class RelationshipService extends DataService<Relationship> {
item1,
label,
options,
true,
false,
followLink('relationshipType'),
followLink('leftItem'),

View File

@@ -21,7 +21,6 @@ export enum IdentifierType {
export abstract class RestRequest {
public responseMsToLive = environment.cache.msToLive.default;
public forceBypassCache = false;
public isMultipart = false;
constructor(
@@ -130,16 +129,6 @@ export class PatchRequest extends RestRequest {
}
}
export class FindByIDRequest extends GetRequest {
constructor(
uuid: string,
href: string,
public resourceID: string
) {
super(uuid, href);
}
}
export class FindListOptions {
scopeID?: string;
elementsPerPage?: number;
@@ -169,7 +158,6 @@ export class EndpointMapRequest extends GetRequest {
* Class representing a submission HTTP GET request object
*/
export class SubmissionRequest extends GetRequest {
forceBypassCache = true;
constructor(uuid: string, href: string) {
super(uuid, href);
}

View File

@@ -271,12 +271,16 @@ function expireRequest(storeState: RequestState, action: RequestStaleAction): Re
return storeState;
} else {
const prevEntry = storeState[action.payload.uuid];
return Object.assign({}, storeState, {
[action.payload.uuid]: Object.assign({}, prevEntry, {
state: hasSucceeded(prevEntry.state) ? RequestEntryState.SuccessStale : RequestEntryState.ErrorStale,
lastUpdated: action.lastUpdated
})
});
if (isStale(prevEntry.state)) {
return storeState;
} else {
return Object.assign({}, storeState, {
[action.payload.uuid]: Object.assign({}, prevEntry, {
state: hasSucceeded(prevEntry.state) ? RequestEntryState.SuccessStale : RequestEntryState.ErrorStale,
lastUpdated: action.lastUpdated
})
});
}
}
}

View File

@@ -24,6 +24,7 @@ import { RequestService } from './request.service';
import { TestBed, waitForAsync } from '@angular/core/testing';
import { storeModuleConfig } from '../../app.reducer';
import { MockStore, provideMockStore } from '@ngrx/store/testing';
import { createRequestEntry$ } from '../../shared/testing/utils.test';
describe('RequestService', () => {
let scheduler: TestScheduler;
@@ -209,46 +210,6 @@ describe('RequestService', () => {
});
});
describe(`if the request with the specified UUID wasn't sent, because it was already cached`, () => {
let entry;
beforeEach(() => {
entry = {
state: RequestEntryState.Success,
response: {
timeCompleted: new Date().getTime()
},
request: new GetRequest('request-uuid', 'request-href')
};
// No direct hit in the request cache with that UUID
// A hit in the index, which returns the uuid of the cached request
// the call to retrieve the cached request using the UUID from the index
const state = Object.assign({}, initialState, {
core: Object.assign({}, initialState.core, {
'data/request': {
'otherRequestUUID': entry
},
'index': {
'get-request/configured-to-cache-uuid': {
'5f2a0d2a-effa-4d54-bd54-5663b960f9eb': 'otherRequestUUID'
}
}
})
});
mockStore.setState(state);
});
it(`it should return the cached request`, () => {
const result = service.getByUUID(testUUID);
const expected = cold('c', {
c: entry
});
expect(result).toBeObservable(expected);
});
});
});
describe('getByHref', () => {
@@ -309,7 +270,7 @@ describe('RequestService', () => {
});
});
describe('configure', () => {
describe('send', () => {
beforeEach(() => {
spyOn(serviceAsAny, 'dispatchRequest');
});
@@ -324,7 +285,7 @@ describe('RequestService', () => {
it('should track it on it\'s way to the store', () => {
spyOn(serviceAsAny, 'trackRequestsOnTheirWayToTheStore');
spyOn(serviceAsAny, 'isCachedOrPending').and.returnValue(false);
service.configure(request);
service.send(request);
expect(serviceAsAny.trackRequestsOnTheirWayToTheStore).toHaveBeenCalledWith(request);
});
describe('and it isn\'t cached or pending', () => {
@@ -333,7 +294,7 @@ describe('RequestService', () => {
});
it('should dispatch the request', () => {
scheduler.schedule(() => service.configure(request));
scheduler.schedule(() => service.send(request, true));
scheduler.flush();
expect(serviceAsAny.dispatchRequest).toHaveBeenCalledWith(request);
});
@@ -344,7 +305,7 @@ describe('RequestService', () => {
});
it('shouldn\'t dispatch the request', () => {
service.configure(request);
service.send(request, true);
expect(serviceAsAny.dispatchRequest).not.toHaveBeenCalled();
});
});
@@ -352,22 +313,22 @@ describe('RequestService', () => {
describe('when the request isn\'t a GET request', () => {
it('should dispatch the request', () => {
service.configure(testPostRequest);
service.send(testPostRequest);
expect(serviceAsAny.dispatchRequest).toHaveBeenCalledWith(testPostRequest);
service.configure(testPutRequest);
service.send(testPutRequest);
expect(serviceAsAny.dispatchRequest).toHaveBeenCalledWith(testPutRequest);
service.configure(testDeleteRequest);
service.send(testDeleteRequest);
expect(serviceAsAny.dispatchRequest).toHaveBeenCalledWith(testDeleteRequest);
service.configure(testOptionsRequest);
service.send(testOptionsRequest);
expect(serviceAsAny.dispatchRequest).toHaveBeenCalledWith(testOptionsRequest);
service.configure(testHeadRequest);
service.send(testHeadRequest);
expect(serviceAsAny.dispatchRequest).toHaveBeenCalledWith(testHeadRequest);
service.configure(testPatchRequest);
service.send(testPatchRequest);
expect(serviceAsAny.dispatchRequest).toHaveBeenCalledWith(testPatchRequest);
});
});
@@ -435,20 +396,25 @@ describe('RequestService', () => {
});
describe('dispatchRequest', () => {
let dispatchSpy: jasmine.Spy;
beforeEach(() => {
spyOn(store, 'dispatch');
dispatchSpy = spyOn(store, 'dispatch');
});
it('should dispatch a RequestConfigureAction', () => {
const request = testGetRequest;
serviceAsAny.dispatchRequest(request);
expect(store.dispatch).toHaveBeenCalledWith(new RequestConfigureAction(request));
const firstAction = dispatchSpy.calls.argsFor(0)[0];
expect(firstAction).toBeInstanceOf(RequestConfigureAction);
expect(firstAction.payload).toEqual(request);
});
it('should dispatch a RequestExecuteAction', () => {
const request = testGetRequest;
serviceAsAny.dispatchRequest(request);
expect(store.dispatch).toHaveBeenCalledWith(new RequestExecuteAction(request.uuid));
const secondAction = dispatchSpy.calls.argsFor(1)[0];
expect(secondAction).toBeInstanceOf(RequestExecuteAction);
expect(secondAction.payload).toEqual(request.uuid);
});
describe('when it\'s not a GET request', () => {
@@ -501,7 +467,7 @@ describe('RequestService', () => {
describe('when the request is added to the store', () => {
it('should stop tracking the request', () => {
spyOn(serviceAsAny, 'getByUUID').and.returnValue(observableOf(entry));
spyOn(serviceAsAny, 'getByHref').and.returnValue(observableOf(entry));
serviceAsAny.trackRequestsOnTheirWayToTheStore(request);
expect(serviceAsAny.requestsOnTheirWayToTheStore.includes(request.href)).toBeFalsy();
});

View File

@@ -2,20 +2,15 @@ import { Injectable } from '@angular/core';
import { HttpHeaders } from '@angular/common/http';
import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store';
import { Observable, combineLatest as observableCombineLatest } from 'rxjs';
import { filter, map, mergeMap, take, switchMap, tap } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { filter, map, take, tap } from 'rxjs/operators';
import { cloneDeep } from 'lodash';
import { hasValue, isEmpty, isNotEmpty, hasNoValue } from '../../shared/empty.util';
import { CacheableObject, ObjectCacheEntry } from '../cache/object-cache.reducer';
import { ObjectCacheEntry } from '../cache/object-cache.reducer';
import { ObjectCacheService } from '../cache/object-cache.service';
import { CoreState } from '../core.reducers';
import { IndexName, IndexState, MetaIndexState } from '../index/index.reducer';
import {
originalRequestUUIDFromRequestUUIDSelector,
requestIndexSelector,
uuidFromHrefSelector,
getUrlWithoutEmbedParams
} from '../index/index.selectors';
import { IndexState, MetaIndexState } from '../index/index.reducer';
import { requestIndexSelector, getUrlWithoutEmbedParams } from '../index/index.selectors';
import { UUIDService } from '../shared/uuid.service';
import {
RequestConfigureAction,
@@ -23,10 +18,9 @@ import {
RequestStaleAction
} from './request.actions';
import { GetRequest, RestRequest } from './request.models';
import { RequestEntry, RequestState, hasCompleted, isStale, isLoading } from './request.reducer';
import { RequestEntry, RequestState, isStale, isLoading } from './request.reducer';
import { CommitSSBAction } from '../cache/server-sync-buffer.actions';
import { RestRequestMethod } from './rest-request-method';
import { AddToIndexAction } from '../index/index.actions';
import { coreSelector } from '../core.selectors';
/**
@@ -38,7 +32,7 @@ const requestCacheSelector = createSelector(
);
/**
* Selector function to select a request entry by uuid from the cache
* Selector function to select a request entry by uuid from the store
* @param uuid The uuid of the request
*/
const entryFromUUIDSelector = (uuid: string): MemoizedSelector<CoreState, RequestEntry> => createSelector(
@@ -48,6 +42,29 @@ const entryFromUUIDSelector = (uuid: string): MemoizedSelector<CoreState, Reques
}
);
/**
* Selector function to select a request entry by href from the store
* @param href The href of the request
*/
const entryFromHrefSelector = (href: string): MemoizedSelector<CoreState, RequestEntry> => createSelector(
requestIndexSelector,
requestCacheSelector,
(indexState: IndexState, requestState: RequestState) => {
let uuid: any;
if (hasValue(indexState)) {
uuid = indexState[getUrlWithoutEmbedParams(href)];
} else {
return undefined;
}
if (hasValue(requestState)) {
return requestState[uuid];
} else {
return undefined;
}
}
);
/**
* Create a selector that fetches a list of request UUIDs from a given index substate of which the request href
* contains a given substring
@@ -138,7 +155,7 @@ export class RequestService {
this.getByHref(request.href).pipe(
take(1))
.subscribe((re: RequestEntry) => {
isPending = (hasValue(re) && !hasCompleted(re.state));
isPending = (hasValue(re) && isLoading(re.state));
});
return isPending;
}
@@ -147,72 +164,82 @@ export class RequestService {
* Retrieve a RequestEntry based on their uuid
*/
getByUUID(uuid: string): Observable<RequestEntry> {
return observableCombineLatest([
this.store.pipe(
select(entryFromUUIDSelector(uuid))
),
this.store.pipe(
select(originalRequestUUIDFromRequestUUIDSelector(uuid)),
switchMap((originalUUID) => {
return this.store.pipe(select(entryFromUUIDSelector(originalUUID)));
},
),
),
]).pipe(
map((entries: RequestEntry[]) => entries.find((entry: RequestEntry) => hasValue(entry))),
map((entry: RequestEntry) => {
// Headers break after being retrieved from the store (because of lazy initialization)
// Combining them with a new object fixes this issue
if (hasValue(entry) && hasValue(entry.request) && hasValue(entry.request.options) && hasValue(entry.request.options.headers)) {
entry = cloneDeep(entry);
entry.request.options.headers = Object.assign(new HttpHeaders(), entry.request.options.headers);
}
return entry;
}),
tap((entry: RequestEntry) => {
if (hasValue(entry) && !isStale(entry.state) && !isValid(entry)) {
this.store.dispatch(new RequestStaleAction(uuid));
}
})
return this.store.pipe(
select(entryFromUUIDSelector(uuid)),
this.fixRequestHeaders(),
this.checkStale()
);
}
/**
* Retrieve a RequestEntry based on their href
* Operator that turns the request headers back in to an HttpHeaders instance after an entry has
* been retrieved from the ngrx store
* @private
*/
private fixRequestHeaders() {
return (source: Observable<RequestEntry>): Observable<RequestEntry> => {
return source.pipe(map((entry: RequestEntry) => {
// Headers break after being retrieved from the store (because of lazy initialization)
// Combining them with a new object fixes this issue
if (hasValue(entry) && hasValue(entry.request) && hasValue(entry.request.options) && hasValue(entry.request.options.headers)) {
entry = cloneDeep(entry);
entry.request.options.headers = Object.assign(new HttpHeaders(), entry.request.options.headers);
}
return entry;
})
);
};
}
/**
* Operator that will check if an entry should be stale, and will dispatch an action to set it to
* stale if it should
* @private
*/
private checkStale() {
return (source: Observable<RequestEntry>): Observable<RequestEntry> => {
return source.pipe(
tap((entry: RequestEntry) => {
if (hasValue(entry) && hasValue(entry.request) && !isStale(entry.state) && !isValid(entry)) {
this.store.dispatch(new RequestStaleAction(entry.request.uuid));
}
})
);
};
}
/**
* Retrieve a RequestEntry based on its href
*/
getByHref(href: string): Observable<RequestEntry> {
return this.store.pipe(
select(uuidFromHrefSelector(href)),
mergeMap((uuid: string) => {
if (isNotEmpty(uuid)) {
return this.getByUUID(uuid);
} else {
return [undefined];
}
})
select(entryFromHrefSelector(href)),
this.fixRequestHeaders(),
this.checkStale()
);
}
/**
* Configure a certain request
* Used to make sure a request is in the cache
* @param {RestRequest} request The request to send out
* Add the given request to the ngrx store, and send it to the rest api
*
* @param request The request to send out
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to false
* @returns true if the request was sent, false otherwise
*/
configure<T extends CacheableObject>(request: RestRequest): void {
const isGetRequest = request.method === RestRequestMethod.GET;
if (!isGetRequest || request.forceBypassCache || !this.isCachedOrPending(request)) {
send(request: RestRequest, useCachedVersionIfAvailable = false): boolean {
if (useCachedVersionIfAvailable && request.method !== RestRequestMethod.GET) {
console.warn(`${JSON.stringify(request, null, 2)} is not a GET request. In general only GET requests should reuse cached data.`);
}
if (!useCachedVersionIfAvailable || !this.isCachedOrPending(request)) {
this.dispatchRequest(request);
if (isGetRequest) {
if (request.method === RestRequestMethod.GET) {
this.trackRequestsOnTheirWayToTheStore(request);
}
return true;
} else {
this.getByHref(request.href).pipe(
filter((entry) => hasValue(entry)),
take(1)
).subscribe((entry) => {
return this.store.dispatch(new AddToIndexAction(IndexName.UUID_MAPPING, request.uuid, entry.request.uuid));
}
);
return false;
}
}
@@ -308,15 +335,15 @@ export class RequestService {
/**
* ngrx action dispatches are asynchronous. But this.isPending needs to return true as soon as the
* configure method for a GET request has been executed, otherwise certain requests will happen multiple times.
* send method for a GET request has been executed, otherwise certain requests will happen multiple times.
*
* This method will store the href of every GET request that gets configured in a local variable, and
* remove it as soon as it can be found in the store.
*/
private trackRequestsOnTheirWayToTheStore(request: GetRequest) {
this.requestsOnTheirWayToTheStore = [...this.requestsOnTheirWayToTheStore, request.href];
this.getByUUID(request.uuid).pipe(
filter((re: RequestEntry) => hasValue(re)),
this.getByHref(request.href).pipe(
filter((re: RequestEntry) => hasValue(re) && hasValue(re.request) && re.request.uuid === request.uuid),
take(1)
).subscribe((re: RequestEntry) => {
this.requestsOnTheirWayToTheStore = this.requestsOnTheirWayToTheStore.filter((pendingHref: string) => pendingHref !== request.href);

View File

@@ -39,7 +39,7 @@ describe('SiteDataService', () => {
});
requestService = jasmine.createSpyObj('requestService', {
generateRequestId: requestUUID,
configure: true,
send: true,
});
rdbService = jasmine.createSpyObj('rdbService', {
buildList: cold('a', {

View File

@@ -5,7 +5,7 @@ import { VersionHistoryDataService } from './version-history-data.service';
import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub';
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service.stub';
import { getMockRequestService } from '../../shared/mocks/request.service.mock';
import { GetRequest } from './request.models';
import { VersionDataService } from './version-data.service';
const url = 'fake-url';
@@ -16,6 +16,7 @@ describe('VersionHistoryDataService', () => {
let notificationsService: any;
let rdbService: RemoteDataBuildService;
let objectCache: ObjectCacheService;
let versionService: VersionDataService;
let halService: any;
beforeEach(() => {
@@ -29,8 +30,8 @@ describe('VersionHistoryDataService', () => {
result = service.getVersions('1');
});
it('should configure a GET request', () => {
expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(GetRequest));
it('should call versionService.findAllByHref', () => {
expect(versionService.findAllByHref).toHaveBeenCalled();
});
});
@@ -46,9 +47,12 @@ describe('VersionHistoryDataService', () => {
objectCache = jasmine.createSpyObj('objectCache', {
remove: jasmine.createSpy('remove')
});
versionService = jasmine.createSpyObj('objectCache', {
findAllByHref: jasmine.createSpy('findAllByHref')
});
halService = new HALEndpointServiceStub(url);
notificationsService = new NotificationsServiceStub();
service = new VersionHistoryDataService(requestService, rdbService, null, objectCache, halService, notificationsService, null, null);
service = new VersionHistoryDataService(requestService, rdbService, null, objectCache, halService, notificationsService, versionService, null, null);
}
});

View File

@@ -10,16 +10,17 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { HttpClient } from '@angular/common/http';
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
import { FindListOptions, GetRequest } from './request.models';
import { FindListOptions } from './request.models';
import { Observable } from 'rxjs';
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
import { RemoteData } from './remote-data';
import { PaginatedList } from './paginated-list.model';
import { Version } from '../shared/version.model';
import { map, switchMap, take } from 'rxjs/operators';
import { map, switchMap } from 'rxjs/operators';
import { dataService } from '../cache/builders/build-decorators';
import { VERSION_HISTORY } from '../shared/version-history.resource-type';
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
import { VersionDataService } from './version-data.service';
/**
* Service responsible for handling requests related to the VersionHistory object
@@ -37,6 +38,7 @@ export class VersionHistoryDataService extends DataService<VersionHistory> {
protected objectCache: ObjectCacheService,
protected halService: HALEndpointService,
protected notificationsService: NotificationsService,
protected versionDataService: VersionDataService,
protected http: HttpClient,
protected comparator: DefaultChangeAnalyzer<VersionHistory>) {
super();
@@ -61,21 +63,20 @@ export class VersionHistoryDataService extends DataService<VersionHistory> {
/**
* Get a version history's versions using paginated search options
* @param versionHistoryId The version history's ID
* @param searchOptions The search options to use
* @param linksToFollow HAL Links to follow on the Versions
* @param versionHistoryId The version history's ID
* @param searchOptions The search options to use
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
* @param reRequestOnStale Whether or not the request should automatically be re-
* requested after the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
* {@link HALLink}s should be automatically resolved
*/
getVersions(versionHistoryId: string, searchOptions?: PaginatedSearchOptions, ...linksToFollow: FollowLinkConfig<Version>[]): Observable<RemoteData<PaginatedList<Version>>> {
getVersions(versionHistoryId: string, searchOptions?: PaginatedSearchOptions, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Version>[]): Observable<RemoteData<PaginatedList<Version>>> {
const hrefObs = this.getVersionsEndpoint(versionHistoryId).pipe(
map((href) => searchOptions ? searchOptions.toRestUrl(href) : href)
);
hrefObs.pipe(
take(1)
).subscribe((href) => {
const request = new GetRequest(this.requestService.generateRequestId(), href);
this.requestService.configure(request);
});
return this.rdbService.buildList<Version>(hrefObs, ...linksToFollow);
return this.versionDataService.findAllByHref(hrefObs, undefined, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
}
}

View File

@@ -96,33 +96,47 @@ describe('EPersonDataService', () => {
it('search by default scope (byMetadata) and no query', () => {
service.searchByScope(null, '');
const options = Object.assign(new FindListOptions(), {
searchParams: [Object.assign(new RequestParam('query', ''))]
searchParams: [Object.assign(new RequestParam('query', encodeURIComponent('')))]
});
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options, true);
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options, true, true);
});
it('search metadata scope and no query', () => {
service.searchByScope('metadata', '');
const options = Object.assign(new FindListOptions(), {
searchParams: [Object.assign(new RequestParam('query', ''))]
searchParams: [Object.assign(new RequestParam('query', encodeURIComponent('')))]
});
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options, true);
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options, true, true);
});
it('search metadata scope and with query', () => {
service.searchByScope('metadata', 'test');
const options = Object.assign(new FindListOptions(), {
searchParams: [Object.assign(new RequestParam('query', 'test'))]
searchParams: [Object.assign(new RequestParam('query', encodeURIComponent('test')))]
});
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options, true);
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options, true, true);
});
it('search email scope and no query', () => {
spyOn(service, 'getSearchByHref').and.returnValue(epersonsEndpoint);
spyOn(service, 'findByHref').and.returnValue(createSuccessfulRemoteDataObject$(null));
service.searchByScope('email', '');
const options = Object.assign(new FindListOptions(), {
searchParams: [Object.assign(new RequestParam('email', ''))]
searchParams: [Object.assign(new RequestParam('email', encodeURIComponent('')))]
});
expect(service.searchBy).toHaveBeenCalledWith('byEmail', options, true);
expect(service.getSearchByHref).toHaveBeenCalledWith('byEmail', options);
expect(service.findByHref).toHaveBeenCalledWith(epersonsEndpoint, true, true);
});
it('search email scope with a query', () => {
spyOn(service, 'getSearchByHref').and.returnValue(epersonsEndpoint);
spyOn(service, 'findByHref').and.returnValue(createSuccessfulRemoteDataObject$(EPersonMock));
service.searchByScope('email', EPersonMock.email);
const options = Object.assign(new FindListOptions(), {
searchParams: [Object.assign(new RequestParam('email', encodeURIComponent(EPersonMock.email)))]
});
expect(service.getSearchByHref).toHaveBeenCalledWith('byEmail', options);
expect(service.findByHref).toHaveBeenCalledWith(epersonsEndpoint, true, true);
});
});
@@ -147,7 +161,7 @@ describe('EPersonDataService', () => {
it('should send PatchRequest with replace email operation', () => {
const operations = [{ op: 'replace', path: '/email', value: newEmail }];
const expected = new PatchRequest(requestService.generateRequestId(), epersonsEndpoint + '/' + EPersonMock.uuid, operations);
expect(requestService.configure).toHaveBeenCalledWith(expected);
expect(requestService.send).toHaveBeenCalledWith(expected);
});
});
@@ -166,7 +180,7 @@ describe('EPersonDataService', () => {
it('should send PatchRequest with replace certificate operation', () => {
const operations = [{ op: 'replace', path: '/certificate', value: !EPersonMock.requireCertificate }];
const expected = new PatchRequest(requestService.generateRequestId(), epersonsEndpoint + '/' + EPersonMock.uuid, operations);
expect(requestService.configure).toHaveBeenCalledWith(expected);
expect(requestService.send).toHaveBeenCalledWith(expected);
});
});
@@ -185,7 +199,7 @@ describe('EPersonDataService', () => {
it('should send PatchRequest with replace canLogIn operation', () => {
const operations = [{ op: 'replace', path: '/canLogIn', value: !EPersonMock.canLogIn }];
const expected = new PatchRequest(requestService.generateRequestId(), epersonsEndpoint + '/' + EPersonMock.uuid, operations);
expect(requestService.configure).toHaveBeenCalledWith(expected);
expect(requestService.send).toHaveBeenCalledWith(expected);
});
});
@@ -219,7 +233,7 @@ describe('EPersonDataService', () => {
{ op: 'replace', path: '/eperson.lastname/0/value', value: newLastName },
{ op: 'replace', path: '/eperson.firstname/0/value', value: newFirstName }];
const expected = new PatchRequest(requestService.generateRequestId(), epersonsEndpoint + '/' + EPersonMock.uuid, operations);
expect(requestService.configure).toHaveBeenCalledWith(expected);
expect(requestService.send).toHaveBeenCalledWith(expected);
});
});
});
@@ -278,7 +292,7 @@ describe('EPersonDataService', () => {
it('should send DeleteRequest', () => {
const expected = new DeleteRequest(requestService.generateRequestId(), epersonsEndpoint + '/' + EPersonMock.uuid);
expect(requestService.configure).toHaveBeenCalledWith(expected);
expect(requestService.send).toHaveBeenCalledWith(expected);
});
});
@@ -287,7 +301,7 @@ describe('EPersonDataService', () => {
service.createEPersonForToken(EPersonMock, 'test-token');
const expected = new PostRequest(requestService.generateRequestId(), epersonsEndpoint + '?token=test-token', EPersonMock);
expect(requestService.configure).toHaveBeenCalledWith(expected);
expect(requestService.send).toHaveBeenCalledWith(expected);
});
});
describe('patchPasswordWithToken', () => {
@@ -297,7 +311,7 @@ describe('EPersonDataService', () => {
const operation = Object.assign({ op: 'add', path: '/password', value: 'test-password' });
const expected = new PatchRequest(requestService.generateRequestId(), epersonsEndpoint + '/test-uuid?token=test-token', [operation]);
expect(requestService.configure).toHaveBeenCalledWith(expected);
expect(requestService.send).toHaveBeenCalledWith(expected);
});
});

View File

@@ -3,14 +3,14 @@ import { Injectable } from '@angular/core';
import { createSelector, select, Store } from '@ngrx/store';
import { Operation } from 'fast-json-patch';
import { Observable } from 'rxjs';
import { filter, find, map, take } from 'rxjs/operators';
import { find, map, take } from 'rxjs/operators';
import {
EPeopleRegistryCancelEPersonAction,
EPeopleRegistryEditEPersonAction
} from '../../+admin/admin-access-control/epeople-registry/epeople-registry.actions';
import { EPeopleRegistryState } from '../../+admin/admin-access-control/epeople-registry/epeople-registry.reducers';
import { AppState } from '../../app.reducer';
import { hasValue } from '../../shared/empty.util';
import { hasValue, hasNoValue } from '../../shared/empty.util';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
import { dataService } from '../cache/builders/build-decorators';
@@ -19,20 +19,16 @@ import { RequestParam } from '../cache/models/request-param.model';
import { ObjectCacheService } from '../cache/object-cache.service';
import { DataService } from '../data/data.service';
import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service';
import { PaginatedList } from '../data/paginated-list.model';
import { PaginatedList, buildPaginatedList } from '../data/paginated-list.model';
import { RemoteData } from '../data/remote-data';
import {
FindListOptions,
FindListRequest,
PatchRequest,
PostRequest,
} from '../data/request.models';
import { FindListOptions, PatchRequest, PostRequest, } from '../data/request.models';
import { RequestService } from '../data/request.service';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { getRemoteDataPayload, getFirstSucceededRemoteData, } from '../shared/operators';
import { EPerson } from './models/eperson.model';
import { EPERSON } from './models/eperson.resource-type';
import { NoContent } from '../shared/NoContent.model';
import { PageInfo } from '../shared/page-info.model';
const ePeopleRegistryStateSelector = (state: AppState) => state.epeopleRegistry;
const editEPersonSelector = createSelector(ePeopleRegistryStateSelector, (ePeopleRegistryState: EPeopleRegistryState) => ePeopleRegistryState.editEPerson);
@@ -61,23 +57,6 @@ export class EPersonDataService extends DataService<EPerson> {
super();
}
/**
* Retrieves all EPeople
* @param options The options info used to retrieve the EPeople
*/
public getEPeople(options: FindListOptions = {}): Observable<RemoteData<PaginatedList<EPerson>>> {
const hrefObs = this.getFindAllHref(options, this.linkPath);
hrefObs.pipe(
filter((href: string) => hasValue(href)),
take(1))
.subscribe((href: string) => {
const request = new FindListRequest(this.requestService.generateRequestId(), href, options);
this.requestService.configure(request);
});
return this.rdbService.buildList<EPerson>(hrefObs) as Observable<RemoteData<PaginatedList<EPerson>>>;
}
/**
* Search the EPeople with a given scope and query
* @param scope Scope of the EPeople search, default byMetadata
@@ -89,51 +68,90 @@ export class EPersonDataService extends DataService<EPerson> {
case 'metadata':
return this.getEpeopleByMetadata(query.trim(), options);
case 'email':
return this.getEpeopleByEmail(query.trim(), options);
return this.getEPersonByEmail(query.trim()).pipe(
map((rd: RemoteData<EPerson | NoContent>) => {
if (rd.hasSucceeded) {
// Turn the single EPerson or NoContent in to a PaginatedList<EPerson>
let page;
if (rd.statusCode === 204 || hasNoValue(rd.payload)) {
page = [];
} else {
page = [rd.payload];
}
return new RemoteData<PaginatedList<EPerson>>(
rd.timeCompleted,
rd.msToLive,
rd.lastUpdated,
rd.state,
rd.errorMessage,
buildPaginatedList(new PageInfo({
elementsPerPage: options.elementsPerPage,
totalElements: page.length,
totalPages: page.length,
currentPage: 1
}), page),
rd.statusCode
);
} else {
// If it hasn't succeeded, there can be no payload, so we can re-cast the existing
// RemoteData object
return rd as RemoteData<PaginatedList<EPerson>>;
}
})
);
default:
return this.getEpeopleByMetadata(query.trim(), options);
}
}
/**
* Returns a search result list of EPeople, by email query (/eperson/epersons/search/{@link searchByEmailPath}?email=<>)
* @param query email query
* @param options
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
* the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s
* should be automatically resolved
* Returns a single EPerson, by email query (/eperson/epersons/search/{@link searchByEmailPath}?email=<>). If it can be found
* NoContent otherwise
*
* @param query email query
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
* @param reRequestOnStale Whether or not the request should automatically be re-
* requested after the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
* {@link HALLink}s should be automatically resolved
*/
private getEpeopleByEmail(query: string, options?: FindListOptions, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<EPerson>[]): Observable<RemoteData<PaginatedList<EPerson>>> {
const searchParams = [new RequestParam('email', query)];
return this.getEPeopleBy(searchParams, this.searchByEmailPath, options, reRequestOnStale, ...linksToFollow);
public getEPersonByEmail(query: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<EPerson>[]): Observable<RemoteData<EPerson | NoContent>> {
const findListOptions = new FindListOptions();
findListOptions.searchParams = [new RequestParam('email', encodeURIComponent(query))];
const href$ = this.getSearchByHref(this.searchByEmailPath, findListOptions, ...linksToFollow);
return this.findByHref(href$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
}
/**
* Returns a search result list of EPeople, by metadata query (/eperson/epersons/search/{@link searchByMetadataPath}?query=<>)
* @param query metadata query
* @param query metadata query
* @param options
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
* the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s
* should be automatically resolved
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
* @param reRequestOnStale Whether or not the request should automatically be re-
* requested after the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
* {@link HALLink}s should be automatically resolved
*/
private getEpeopleByMetadata(query: string, options?: FindListOptions, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<EPerson>[]): Observable<RemoteData<PaginatedList<EPerson>>> {
const searchParams = [new RequestParam('query', query)];
return this.getEPeopleBy(searchParams, this.searchByMetadataPath, options, reRequestOnStale, ...linksToFollow);
private getEpeopleByMetadata(query: string, options?: FindListOptions, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<EPerson>[]): Observable<RemoteData<PaginatedList<EPerson>>> {
const searchParams = [new RequestParam('query', encodeURIComponent(query))];
return this.getEPeopleBy(searchParams, this.searchByMetadataPath, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
}
/**
* Returns a search result list of EPeople in a given searchMethod, with given searchParams
* @param searchParams query parameters in the search
* @param searchMethod searchBy path
* @param searchParams query parameters in the search
* @param searchMethod searchBy path
* @param options
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
* the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s
* should be automatically resolved
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
* @param reRequestOnStale Whether or not the request should automatically be re-
* requested after the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
* {@link HALLink}s should be automatically resolved
*/
private getEPeopleBy(searchParams: RequestParam[], searchMethod: string, options?: FindListOptions, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<EPerson>[]): Observable<RemoteData<PaginatedList<EPerson>>> {
private getEPeopleBy(searchParams: RequestParam[], searchMethod: string, options?: FindListOptions, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<EPerson>[]): Observable<RemoteData<PaginatedList<EPerson>>> {
let findListOptions = new FindListOptions();
if (options) {
findListOptions = Object.assign(new FindListOptions(), options);
@@ -143,7 +161,7 @@ export class EPersonDataService extends DataService<EPerson> {
} else {
findListOptions.searchParams = searchParams;
}
return this.searchBy(searchMethod, findListOptions, reRequestOnStale, ...linksToFollow);
return this.searchBy(searchMethod, findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
}
/**
@@ -153,17 +171,16 @@ export class EPersonDataService extends DataService<EPerson> {
*/
public updateEPerson(ePerson: EPerson): Observable<RemoteData<EPerson>> {
const requestId = this.requestService.generateRequestId();
const oldVersion$ = this.findByHref(ePerson._links.self.href);
const oldVersion$ = this.findByHref(ePerson._links.self.href, true, false);
oldVersion$.pipe(
getFirstSucceededRemoteData(),
getRemoteDataPayload(),
map((oldEPerson: EPerson) => {
const operations = this.generateOperations(oldEPerson, ePerson);
const patchRequest = new PatchRequest(requestId, ePerson._links.self.href, operations);
return this.requestService.configure(patchRequest);
}),
take(1)
).subscribe();
).subscribe((oldEPerson: EPerson) => {
const operations = this.generateOperations(oldEPerson, ePerson);
const patchRequest = new PatchRequest(requestId, ePerson._links.self.href, operations);
return this.requestService.send(patchRequest);
});
return this.rdbService.buildFromRequestUUID(requestId);
}
@@ -274,11 +291,10 @@ export class EPersonDataService extends DataService<EPerson> {
map((href: string) => `${href}?token=${token}`));
hrefObs.pipe(
find((href: string) => hasValue(href)),
map((href: string) => {
const request = new PostRequest(requestId, href, eperson);
this.requestService.configure(request);
})
).subscribe();
).subscribe((href: string) => {
const request = new PostRequest(requestId, href, eperson);
this.requestService.send(request);
});
return this.rdbService.buildFromRequestUUID(requestId);
@@ -301,11 +317,10 @@ export class EPersonDataService extends DataService<EPerson> {
hrefObs.pipe(
find((href: string) => hasValue(href)),
map((href: string) => {
const request = new PatchRequest(requestId, href, [operation]);
this.requestService.configure(request);
})
).subscribe();
).subscribe((href: string) => {
const request = new PatchRequest(requestId, href, [operation]);
this.requestService.send(request);
});
return this.rdbService.buildFromRequestUUID(requestId);
}

View File

@@ -94,7 +94,7 @@ describe('GroupDataService', () => {
const options = Object.assign(new FindListOptions(), {
searchParams: [Object.assign(new RequestParam('query', ''))]
});
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options, true);
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options, true, true);
});
it('search with query', () => {
@@ -102,7 +102,7 @@ describe('GroupDataService', () => {
const options = Object.assign(new FindListOptions(), {
searchParams: [Object.assign(new RequestParam('query', 'test'))]
});
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options, true);
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options, true, true);
});
});
@@ -116,7 +116,7 @@ describe('GroupDataService', () => {
headers = headers.append('Content-Type', 'text/uri-list');
options.headers = headers;
const expected = new PostRequest(requestService.generateRequestId(), GroupMock.self + '/' + service.subgroupsEndpoint, GroupMock2.self, options);
expect(requestService.configure).toHaveBeenCalledWith(expected);
expect(requestService.send).toHaveBeenCalledWith(expected);
});
});
@@ -126,7 +126,7 @@ describe('GroupDataService', () => {
});
it('should send DeleteRequest to eperson/groups/group-id/subgroups/group-id endpoint', () => {
const expected = new DeleteRequest(requestService.generateRequestId(), GroupMock.self + '/' + service.subgroupsEndpoint + '/' + GroupMock2.id);
expect(requestService.configure).toHaveBeenCalledWith(expected);
expect(requestService.send).toHaveBeenCalledWith(expected);
});
});
@@ -140,7 +140,7 @@ describe('GroupDataService', () => {
headers = headers.append('Content-Type', 'text/uri-list');
options.headers = headers;
const expected = new PostRequest(requestService.generateRequestId(), GroupMock.self + '/' + service.ePersonsEndpoint, EPersonMock2.self, options);
expect(requestService.configure).toHaveBeenCalledWith(expected);
expect(requestService.send).toHaveBeenCalledWith(expected);
});
});
@@ -150,7 +150,7 @@ describe('GroupDataService', () => {
});
it('should send DeleteRequest to eperson/groups/group-id/epersons/eperson-id endpoint', () => {
const expected = new DeleteRequest(requestService.generateRequestId(), GroupMock.self + '/' + service.ePersonsEndpoint + '/' + EPersonMock.id);
expect(requestService.configure).toHaveBeenCalledWith(expected);
expect(requestService.send).toHaveBeenCalledWith(expected);
});
});

View File

@@ -10,7 +10,6 @@ import {
} from '../../+admin/admin-access-control/group-registry/group-registry.actions';
import { GroupRegistryState } from '../../+admin/admin-access-control/group-registry/group-registry.reducers';
import { AppState } from '../../app.reducer';
import { hasValue } from '../../shared/empty.util';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
@@ -20,13 +19,7 @@ import { DataService } from '../data/data.service';
import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service';
import { PaginatedList } from '../data/paginated-list.model';
import { RemoteData } from '../data/remote-data';
import {
CreateRequest,
DeleteRequest,
FindListOptions,
FindListRequest,
PostRequest
} from '../data/request.models';
import { CreateRequest, DeleteRequest, FindListOptions, PostRequest } from '../data/request.models';
import { RequestService } from '../data/request.service';
import { HttpOptions } from '../dspace-rest/dspace-rest.service';
@@ -71,34 +64,19 @@ export class GroupDataService extends DataService<Group> {
super();
}
/**
* Retrieves all groups
* @param pagination The pagination info used to retrieve the groups
*/
public getGroups(options: FindListOptions = {}, ...linksToFollow: FollowLinkConfig<Group>[]): Observable<RemoteData<PaginatedList<Group>>> {
const hrefObs = this.getFindAllHref(options, this.linkPath, ...linksToFollow);
hrefObs.pipe(
filter((href: string) => hasValue(href)),
take(1))
.subscribe((href: string) => {
const request = new FindListRequest(this.requestService.generateRequestId(), href, options);
this.requestService.configure(request);
});
return this.rdbService.buildList<Group>(hrefObs) as Observable<RemoteData<PaginatedList<Group>>>;
}
/**
* Returns a search result list of groups, with certain query (searches in group name and by exact uuid)
* Endpoint used: /eperson/groups/search/byMetadata?query=<:name>
* @param query search query param
* @param query search query param
* @param options
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
* the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s
* should be automatically resolved
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
* @param reRequestOnStale Whether or not the request should automatically be re-
* requested after the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
* {@link HALLink}s should be automatically resolved
*/
public searchGroups(query: string, options?: FindListOptions, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Group>[]): Observable<RemoteData<PaginatedList<Group>>> {
public searchGroups(query: string, options?: FindListOptions, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Group>[]): Observable<RemoteData<PaginatedList<Group>>> {
const searchParams = [new RequestParam('query', query)];
let findListOptions = new FindListOptions();
if (options) {
@@ -109,7 +87,7 @@ export class GroupDataService extends DataService<Group> {
} else {
findListOptions.searchParams = searchParams;
}
return this.searchBy('byMetadata', findListOptions, reRequestOnStale, ...linksToFollow);
return this.searchBy('byMetadata', findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
}
/**
@@ -144,7 +122,7 @@ export class GroupDataService extends DataService<Group> {
headers = headers.append('Content-Type', 'text/uri-list');
options.headers = headers;
const postRequest = new PostRequest(requestId, activeGroup.self + '/' + this.subgroupsEndpoint, subgroup.self, options);
this.requestService.configure(postRequest);
this.requestService.send(postRequest);
return this.rdbService.buildFromRequestUUID(requestId);
}
@@ -157,7 +135,7 @@ export class GroupDataService extends DataService<Group> {
deleteSubGroupFromGroup(activeGroup: Group, subgroup: Group): Observable<RemoteData<Group>> {
const requestId = this.requestService.generateRequestId();
const deleteRequest = new DeleteRequest(requestId, activeGroup.self + '/' + this.subgroupsEndpoint + '/' + subgroup.id);
this.requestService.configure(deleteRequest);
this.requestService.send(deleteRequest);
return this.rdbService.buildFromRequestUUID(requestId);
}
@@ -174,7 +152,7 @@ export class GroupDataService extends DataService<Group> {
headers = headers.append('Content-Type', 'text/uri-list');
options.headers = headers;
const postRequest = new PostRequest(requestId, activeGroup.self + '/' + this.ePersonsEndpoint, ePerson.self, options);
this.requestService.configure(postRequest);
this.requestService.send(postRequest);
return this.rdbService.buildFromRequestUUID(requestId);
}
@@ -187,7 +165,7 @@ export class GroupDataService extends DataService<Group> {
deleteMemberFromGroup(activeGroup: Group, ePerson: EPerson): Observable<RemoteData<Group>> {
const requestId = this.requestService.generateRequestId();
const deleteRequest = new DeleteRequest(requestId, activeGroup.self + '/' + this.ePersonsEndpoint + '/' + ePerson.id);
this.requestService.configure(deleteRequest);
this.requestService.send(deleteRequest);
return this.rdbService.buildFromRequestUUID(requestId);
}
@@ -298,7 +276,7 @@ export class GroupDataService extends DataService<Group> {
},
});
this.requestService.configure(
this.requestService.send(
new CreateRequest(
requestId,
link,
@@ -327,7 +305,7 @@ export class GroupDataService extends DataService<Group> {
const requestId = this.requestService.generateRequestId();
this.requestService.configure(
this.requestService.send(
new DeleteRequest(
requestId,
link,

View File

@@ -7,6 +7,7 @@ import { AddToObjectCacheAction } from '../cache/object-cache.actions';
import { Item } from '../shared/item.model';
import { AddToIndexAction } from './index.actions';
import { IndexName } from './index.reducer';
import { provideMockStore } from '@ngrx/store/testing';
describe('ObjectUpdatesEffects', () => {
let indexEffects: UUIDIndexEffects;
@@ -18,6 +19,7 @@ describe('ObjectUpdatesEffects', () => {
let alternativeLink;
let selfLink;
let otherLink;
let initialState;
function init() {
selfLink = 'rest.org/items/6ca6549c-3db2-4288-8ce4-4a3bce011860';
@@ -34,6 +36,15 @@ describe('ObjectUpdatesEffects', () => {
msToLive = 90000;
requestUUID = '324e5a5c-06f7-428d-b3ba-cc322c5dde39';
alternativeLink = 'rest.org/alternative-link/1234';
initialState = {
core: {
index: {
[IndexName.REQUEST]: {
[selfLink]: requestUUID
}
}
}
};
}
beforeEach(waitForAsync(() => {
@@ -42,6 +53,7 @@ describe('ObjectUpdatesEffects', () => {
providers: [
UUIDIndexEffects,
provideMockActions(() => actions),
provideMockStore({ initialState }),
],
});
}));

View File

@@ -1,14 +1,24 @@
import { filter, map } from 'rxjs/operators';
import { filter, map, switchMap, take } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { Actions, Effect, ofType } from '@ngrx/effects';
import { AddToObjectCacheAction, ObjectCacheActionTypes, RemoveFromObjectCacheAction } from '../cache/object-cache.actions';
import { RequestActionTypes, RequestConfigureAction } from '../data/request.actions';
import {
AddToObjectCacheAction,
ObjectCacheActionTypes,
RemoveFromObjectCacheAction
} from '../cache/object-cache.actions';
import {
RequestActionTypes,
RequestConfigureAction,
RequestStaleAction
} from '../data/request.actions';
import { AddToIndexAction, RemoveFromIndexByValueAction } from './index.actions';
import { hasValue } from '../../shared/empty.util';
import { IndexName } from './index.reducer';
import { RestRequestMethod } from '../data/rest-request-method';
import { getUrlWithoutEmbedParams } from './index.selectors';
import { getUrlWithoutEmbedParams, uuidFromHrefSelector } from './index.selectors';
import { Store, select } from '@ngrx/store';
import { CoreState } from '../core.reducers';
@Injectable()
export class UUIDIndexEffects {
@@ -63,16 +73,30 @@ export class UUIDIndexEffects {
.pipe(
ofType(RequestActionTypes.CONFIGURE),
filter((action: RequestConfigureAction) => action.payload.method === RestRequestMethod.GET),
map((action: RequestConfigureAction) => {
return new AddToIndexAction(
switchMap((action: RequestConfigureAction) => {
const href = getUrlWithoutEmbedParams(action.payload.href);
return this.store.pipe(
select(uuidFromHrefSelector(href)),
take(1),
map((uuid: string) => [action, uuid])
);
}
),
switchMap(([action, uuid]: [RequestConfigureAction, string]) => {
let actions = [];
if (hasValue(uuid)) {
actions = [new RequestStaleAction(uuid)];
}
actions = [...actions, new AddToIndexAction(
IndexName.REQUEST,
getUrlWithoutEmbedParams(action.payload.href),
action.payload.uuid
);
)];
return actions;
})
);
constructor(private actions$: Actions) {
constructor(private actions$: Actions, private store: Store<CoreState>) {
}

View File

@@ -1,7 +1,11 @@
import * as deepFreeze from 'deep-freeze';
import { IndexName, indexReducer, MetaIndexState } from './index.reducer';
import { AddToIndexAction, RemoveFromIndexBySubstringAction, RemoveFromIndexByValueAction } from './index.actions';
import {
AddToIndexAction,
RemoveFromIndexBySubstringAction,
RemoveFromIndexByValueAction
} from './index.actions';
class NullAction extends AddToIndexAction {
type = null;
@@ -24,8 +28,6 @@ describe('requestReducer', () => {
[key1]: val1
}, [IndexName.REQUEST]: {
[key1]: val1
}, [IndexName.UUID_MAPPING]: {
[key1]: val1
}
};
deepFreeze(testState);

View File

@@ -16,13 +16,6 @@ export enum IndexName {
// contains all requests in the request cache indexed by UUID
REQUEST = 'get-request/href-to-uuid',
/**
* Contains the UUIDs of requests that were sent to the server and
* have their responses cached, indexed by the UUIDs of requests that
* weren't sent because the response they requested was already cached
*/
UUID_MAPPING = 'get-request/configured-to-cache-uuid',
/**
* Contains the alternative link for an objects
* Maps these link on to their matching self link in the object cache

View File

@@ -82,17 +82,6 @@ export const alternativeLinkIndexSelector: MemoizedSelector<CoreState, IndexStat
(state: MetaIndexState) => state[IndexName.ALTERNATIVE_OBJECT_LINK]
);
/**
* Return the request UUID mapping index based on the MetaIndexState
*
* @returns
* a MemoizedSelector to select the request UUID mapping
*/
export const requestUUIDIndexSelector: MemoizedSelector<CoreState, IndexState> = createSelector(
metaIndexSelector,
(state: MetaIndexState) => state[IndexName.UUID_MAPPING]
);
/**
* Return the self link of an object in the object-cache based on its UUID
*
@@ -121,21 +110,6 @@ export const uuidFromHrefSelector =
(state: IndexState) => hasValue(state) ? state[getUrlWithoutEmbedParams(href)] : undefined
);
/**
* Return the UUID of a cached request based on the UUID of a request
* that wasn't sent because the response was already cached
*
* @param uuid
* The UUID of the new request
* @returns
* a MemoizedSelector to select the UUID of the cached request
*/
export const originalRequestUUIDFromRequestUUIDSelector =
(uuid: string): MemoizedSelector<CoreState, string> => createSelector(
requestUUIDIndexSelector,
(state: IndexState) => hasValue(state) ? state[uuid] : undefined
);
/**
* Return the self link of an object based on its alternative link
*

View File

@@ -145,12 +145,12 @@ describe('JsonPatchOperationsService test suite', () => {
expect((service as any).submitJsonPatchOperations).toHaveBeenCalled();
});
it('should configure a new SubmissionPatchRequest', () => {
it('should send a new SubmissionPatchRequest', () => {
const expected = new SubmissionPatchRequest(requestService.generateRequestId(), resourceHref, patchOpBody);
scheduler.schedule(() => service.jsonPatchByResourceType(resourceEndpoint, resourceScope, testJsonPatchResourceType).subscribe());
scheduler.flush();
expect(requestService.configure).toHaveBeenCalledWith(expected);
expect(requestService.send).toHaveBeenCalledWith(expected);
});
it('should dispatch a new StartTransactionPatchOperationsAction', () => {
@@ -235,12 +235,12 @@ describe('JsonPatchOperationsService test suite', () => {
expect((service as any).submitJsonPatchOperations).toHaveBeenCalled();
});
it('should configure a new SubmissionPatchRequest', () => {
it('should send a new SubmissionPatchRequest', () => {
const expected = new SubmissionPatchRequest(requestService.generateRequestId(), resourceHref, patchOpBody);
scheduler.schedule(() => service.jsonPatchByResourceID(resourceEndpoint, resourceScope, testJsonPatchResourceType, testJsonPatchResourceId).subscribe());
scheduler.flush();
expect(requestService.configure).toHaveBeenCalledWith(expected);
expect(requestService.send).toHaveBeenCalledWith(expected);
});
it('should dispatch a new StartTransactionPatchOperationsAction', () => {

Some files were not shown because too many files have changed in this diff Show More