mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-15 14:03:06 +00:00
Cache redesign part 1, and add support for alternative links
This commit is contained in:
@@ -1,20 +1,33 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { combineLatest as observableCombineLatest, Observable, of as observableOf, race as observableRace } from 'rxjs';
|
||||
import { distinctUntilChanged, map, startWith, switchMap } from 'rxjs/operators';
|
||||
import { hasValue, hasValueOperator, isEmpty, isNotEmpty, isNotUndefined } from '../../../shared/empty.util';
|
||||
import {
|
||||
combineLatest as observableCombineLatest,
|
||||
Observable,
|
||||
of as observableOf,
|
||||
race as observableRace
|
||||
} from 'rxjs';
|
||||
import { map, switchMap, filter, distinctUntilKeyChanged } from 'rxjs/operators';
|
||||
import { hasValue, isEmpty, isNotEmpty, hasNoValue, isUndefined } from '../../../shared/empty.util';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||
import { FollowLinkConfig } from '../../../shared/utils/follow-link-config.model';
|
||||
import { PaginatedList } from '../../data/paginated-list';
|
||||
import { FollowLinkConfig, followLink } from '../../../shared/utils/follow-link-config.model';
|
||||
import { PaginatedList } from '../../data/paginated-list.model';
|
||||
import { RemoteData } from '../../data/remote-data';
|
||||
import { RemoteDataError } from '../../data/remote-data-error';
|
||||
import { RequestEntry } from '../../data/request.reducer';
|
||||
import {
|
||||
RequestEntry,
|
||||
ResponseState,
|
||||
RequestEntryState,
|
||||
hasSucceeded
|
||||
} from '../../data/request.reducer';
|
||||
import { RequestService } from '../../data/request.service';
|
||||
import { filterSuccessfulResponses, getRequestFromRequestHref, getRequestFromRequestUUID, getResourceLinksFromResponse } from '../../shared/operators';
|
||||
import { PageInfo } from '../../shared/page-info.model';
|
||||
import { CacheableObject } from '../object-cache.reducer';
|
||||
import { getRequestFromRequestHref, getRequestFromRequestUUID } from '../../shared/operators';
|
||||
import { ObjectCacheService } from '../object-cache.service';
|
||||
import { DSOSuccessResponse, ErrorResponse } from '../response.models';
|
||||
import { LinkService } from './link.service';
|
||||
import { HALLink } from '../../shared/hal-link.model';
|
||||
import { GenericConstructor } from '../../shared/generic-constructor';
|
||||
import { getClassForType } from './build-decorators';
|
||||
import { HALResource } from '../../shared/hal-resource.model';
|
||||
import { PAGINATED_LIST } from '../../data/paginated-list.resource-type';
|
||||
import { getUrlWithoutEmbedParams } from '../../index/index.selectors';
|
||||
import { getResourceTypeValueFor } from '../object-cache.reducer';
|
||||
|
||||
@Injectable()
|
||||
export class RemoteDataBuildService {
|
||||
@@ -24,15 +37,174 @@ export class RemoteDataBuildService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a single {@link RemoteData} object based on the response of a request to the REST server, with a list of
|
||||
* {@link FollowLinkConfig} that indicate which embedded info should be added to the object
|
||||
* @param href$ Observable href of object we want to retrieve
|
||||
* Creates an Observable<T> with the payload for a RemoteData object
|
||||
*
|
||||
* @param requestEntry$ The {@link RequestEntry} to create a {@link RemoteData} object from
|
||||
* @param href$ The self link of the object to retrieve. If left empty, the root
|
||||
* payload link from the response will be used. These links will differ in
|
||||
* case we're retrieving an object that was embedded in the request for
|
||||
* another
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s
|
||||
* should be automatically resolved
|
||||
* @private
|
||||
*/
|
||||
private buildPayload<T>(requestEntry$: Observable<RequestEntry>, href$?: Observable<string>, ...linksToFollow: Array<FollowLinkConfig<any>>): Observable<T> {
|
||||
if (hasNoValue(href$)) {
|
||||
href$ = observableOf(undefined);
|
||||
}
|
||||
return observableCombineLatest([href$, requestEntry$]).pipe(
|
||||
switchMap(([href, entry]: [string, RequestEntry]) => {
|
||||
const hasExactMatchInObjectCache = this.hasExactMatchInObjectCache(href, entry);
|
||||
if (hasValue(entry.response) &&
|
||||
(hasExactMatchInObjectCache || this.isCacheablePayload(entry) || this.isUnCacheablePayload(entry))) {
|
||||
if (hasExactMatchInObjectCache) {
|
||||
return this.objectCache.getObjectByHref(href);
|
||||
} else if (this.isCacheablePayload(entry)) {
|
||||
return this.objectCache.getObjectByHref(entry.response.payloadLink.href);
|
||||
} else {
|
||||
return [this.plainObjectToInstance<T>(entry.response.unCacheableObject)];
|
||||
}
|
||||
} else if (hasSucceeded(entry.state)) {
|
||||
return [null];
|
||||
} else {
|
||||
return [undefined];
|
||||
}
|
||||
}),
|
||||
switchMap((obj: T) => {
|
||||
if (hasValue(obj)) {
|
||||
if (getResourceTypeValueFor((obj as any).type) === PAGINATED_LIST.value) {
|
||||
return this.buildPaginatedList<T>(obj, ...linksToFollow);
|
||||
} else if (isNotEmpty(linksToFollow)) {
|
||||
return [this.linkService.resolveLinks(obj, ...linksToFollow)];
|
||||
}
|
||||
}
|
||||
return [obj];
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* When an object is returned from the store, it's possibly a plain javascript object (in case
|
||||
* it was first instantiated on the server). This method will turn it in to an instance of the
|
||||
* class corresponding with its type property. If it doesn't have one, or we can't find a
|
||||
* constructor for that type, it will remain a plain object.
|
||||
*
|
||||
* @param obj The object to turn in to a class instance based on its type property
|
||||
*/
|
||||
private plainObjectToInstance<T>(obj: any): T {
|
||||
const type: GenericConstructor<T> = getClassForType(obj.type);
|
||||
if (typeof type === 'function') {
|
||||
return Object.assign(new type(), obj) as T
|
||||
} else {
|
||||
return Object.assign({}, obj) as T;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if there is a match for the given self link and request entry in the object cache,
|
||||
* false otherwise. The goal is to find objects that were not the root object of the request, but
|
||||
* embedded.
|
||||
*
|
||||
* @param href the self link to check
|
||||
* @param entry the request entry the object has to match
|
||||
* @private
|
||||
*/
|
||||
private hasExactMatchInObjectCache(href: string, entry: RequestEntry): boolean {
|
||||
return hasValue(entry) && hasValue(entry.request) && isNotEmpty(entry.request.uuid) &&
|
||||
hasValue(href) && this.objectCache.hasByHref(href, entry.request.uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given entry has a valid payloadLink, false otherwise
|
||||
* @param entry the RequestEntry to check
|
||||
* @private
|
||||
*/
|
||||
private isCacheablePayload(entry: RequestEntry): boolean {
|
||||
return hasValue(entry.response.payloadLink) && isNotEmpty(entry.response.payloadLink.href);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given entry has an unCacheableObject, false otherwise
|
||||
* @param entry the RequestEntry to check
|
||||
* @private
|
||||
*/
|
||||
private isUnCacheablePayload(entry: RequestEntry): boolean {
|
||||
return hasValue(entry.response.unCacheableObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a PaginatedList by creating a new PaginatedList instance from the given object, to ensure
|
||||
* it has the correct prototype (you can't be sure if it came from the ngrx store), by
|
||||
* retrieving the objects in the list and following any links.
|
||||
*
|
||||
* @param object A plain object to be turned in to a {@link PaginatedList}
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
private buildPaginatedList<T>(object: any, ...linksToFollow: Array<FollowLinkConfig<any>>): Observable<T> {
|
||||
const pageLink = linksToFollow.find((linkToFollow: FollowLinkConfig<any>) => linkToFollow.name === 'page');
|
||||
const otherLinks = linksToFollow.filter((linkToFollow: FollowLinkConfig<any>) => linkToFollow.name !== 'page');
|
||||
|
||||
const paginatedList = Object.assign(new PaginatedList(), object);
|
||||
|
||||
if (hasValue(pageLink)) {
|
||||
if (isEmpty(paginatedList.page)) {
|
||||
const pageSelfLinks = paginatedList._links.page.map((link: HALLink) => link.href);
|
||||
return this.objectCache.getList(pageSelfLinks).pipe(map((page: any[]) => {
|
||||
paginatedList.page = page
|
||||
.map((obj: any) => this.plainObjectToInstance<T>(obj))
|
||||
.map((obj: any) =>
|
||||
this.linkService.resolveLinks(obj, ...pageLink.linksToFollow)
|
||||
);
|
||||
if (isNotEmpty(otherLinks)) {
|
||||
return this.linkService.resolveLinks(paginatedList, ...otherLinks);
|
||||
}
|
||||
return paginatedList;
|
||||
}));
|
||||
} else {
|
||||
// in case the elements of the paginated list were already filled in, because they're UnCacheableObjects
|
||||
paginatedList.page = paginatedList.page
|
||||
.map((obj: any) => this.plainObjectToInstance<T>(obj))
|
||||
.map((obj: any) =>
|
||||
this.linkService.resolveLinks(obj, ...pageLink.linksToFollow)
|
||||
);
|
||||
if (isNotEmpty(otherLinks)) {
|
||||
return observableOf(this.linkService.resolveLinks(paginatedList, ...otherLinks));
|
||||
}
|
||||
}
|
||||
}
|
||||
return observableOf(paginatedList as any);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link RemoteData} object for a rest request and its response
|
||||
*
|
||||
* @param requestUUID$ The UUID of the request we want to retrieve
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
buildSingle<T extends CacheableObject>(href$: string | Observable<string>, ...linksToFollow: Array<FollowLinkConfig<T>>): Observable<RemoteData<T>> {
|
||||
buildFromRequestUUID<T>(requestUUID$: string | Observable<string>, ...linksToFollow: Array<FollowLinkConfig<any>>): Observable<RemoteData<T>> {
|
||||
if (typeof requestUUID$ === 'string') {
|
||||
requestUUID$ = observableOf(requestUUID$);
|
||||
}
|
||||
const requestEntry$ = requestUUID$.pipe(getRequestFromRequestUUID(this.requestService));
|
||||
|
||||
const payload$ = this.buildPayload<T>(requestEntry$, undefined, ...linksToFollow);
|
||||
|
||||
return this.toRemoteDataObservable<T>(requestEntry$, payload$);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link RemoteData} object for a rest request and its response
|
||||
*
|
||||
* @param href$ self link of object we want to retrieve
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
buildFromHref<T>(href$: string | Observable<string>, ...linksToFollow: Array<FollowLinkConfig<any>>): Observable<RemoteData<T>> {
|
||||
if (typeof href$ === 'string') {
|
||||
href$ = observableOf(href$);
|
||||
}
|
||||
|
||||
href$ = href$.pipe(map((href: string) => getUrlWithoutEmbedParams(href)));
|
||||
|
||||
const requestUUID$ = href$.pipe(
|
||||
switchMap((href: string) =>
|
||||
this.objectCache.getRequestUUIDBySelfLink(href)),
|
||||
@@ -41,205 +213,143 @@ export class RemoteDataBuildService {
|
||||
const requestEntry$ = observableRace(
|
||||
href$.pipe(getRequestFromRequestHref(this.requestService)),
|
||||
requestUUID$.pipe(getRequestFromRequestUUID(this.requestService)),
|
||||
).pipe(
|
||||
distinctUntilKeyChanged('lastUpdated')
|
||||
);
|
||||
// always use self link if that is cached, only if it isn't, get it via the response.
|
||||
const payload$ =
|
||||
observableCombineLatest(
|
||||
href$.pipe(
|
||||
switchMap((href: string) => this.objectCache.getObjectBySelfLink<T>(href)),
|
||||
startWith(undefined)),
|
||||
requestEntry$.pipe(
|
||||
getResourceLinksFromResponse(),
|
||||
switchMap((resourceSelfLinks: string[]) => {
|
||||
if (isNotEmpty(resourceSelfLinks)) {
|
||||
return this.objectCache.getObjectBySelfLink<T>(resourceSelfLinks[0]);
|
||||
} else {
|
||||
return observableOf(undefined);
|
||||
}
|
||||
}),
|
||||
distinctUntilChanged(),
|
||||
startWith(undefined)
|
||||
)
|
||||
).pipe(
|
||||
map(([fromSelfLink, fromResponse]) => {
|
||||
if (hasValue(fromSelfLink)) {
|
||||
return fromSelfLink;
|
||||
} else {
|
||||
return fromResponse;
|
||||
}
|
||||
}),
|
||||
hasValueOperator(),
|
||||
map((obj: T) =>
|
||||
this.linkService.resolveLinks(obj, ...linksToFollow)
|
||||
),
|
||||
startWith(undefined),
|
||||
distinctUntilChanged()
|
||||
);
|
||||
return this.toRemoteDataObservable(requestEntry$, payload$);
|
||||
|
||||
const payload$ = this.buildPayload<T>(requestEntry$, href$, ...linksToFollow);
|
||||
|
||||
return this.toRemoteDataObservable<T>(requestEntry$, payload$);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a single {@link RemoteData} object based on the response of a request to the REST server, with a list of
|
||||
* {@link FollowLinkConfig} that indicate which embedded info should be added to the object
|
||||
* @param href$ Observable href of object we want to retrieve
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
buildSingle<T>(href$: string | Observable<string>, ...linksToFollow: Array<FollowLinkConfig<any>>): Observable<RemoteData<T>> {
|
||||
return this.buildFromHref(href$, ...linksToFollow);
|
||||
}
|
||||
|
||||
toRemoteDataObservable<T>(requestEntry$: Observable<RequestEntry>, payload$: Observable<T>) {
|
||||
return observableCombineLatest(requestEntry$, payload$).pipe(
|
||||
map(([reqEntry, payload]) => {
|
||||
const requestPending = hasValue(reqEntry) && hasValue(reqEntry.requestPending) ? reqEntry.requestPending : true;
|
||||
const responsePending = hasValue(reqEntry) && hasValue(reqEntry.responsePending) ? reqEntry.responsePending : false;
|
||||
let isSuccessful: boolean;
|
||||
let error: RemoteDataError;
|
||||
const response = reqEntry ? reqEntry.response : undefined;
|
||||
if (hasValue(response)) {
|
||||
isSuccessful = response.statusCode === 204 ||
|
||||
response.statusCode >= 200 && response.statusCode < 300 && hasValue(payload);
|
||||
const errorMessage = isSuccessful === false ? (response as ErrorResponse).errorMessage : undefined;
|
||||
if (hasValue(errorMessage)) {
|
||||
error = new RemoteDataError(
|
||||
response.statusCode,
|
||||
response.statusText,
|
||||
errorMessage
|
||||
);
|
||||
}
|
||||
return observableCombineLatest([
|
||||
requestEntry$,
|
||||
payload$
|
||||
]).pipe(
|
||||
filter(([entry,payload]: [RequestEntry, T]) =>
|
||||
hasValue(entry) &&
|
||||
// filter out cases where the state is successful, but the payload isn't yet set
|
||||
!(hasSucceeded(entry.state) && isUndefined(payload))
|
||||
),
|
||||
map(([entry, payload]: [RequestEntry, T]) => {
|
||||
let response = entry.response;
|
||||
if (hasNoValue(response)) {
|
||||
response = {} as ResponseState;
|
||||
}
|
||||
return new RemoteData(
|
||||
requestPending,
|
||||
responsePending,
|
||||
isSuccessful,
|
||||
error,
|
||||
payload,
|
||||
hasValue(response) ? response.statusCode : undefined
|
||||
|
||||
);
|
||||
return new RemoteData(
|
||||
response.timeCompleted,
|
||||
entry.request.responseMsToLive,
|
||||
entry.lastUpdated,
|
||||
entry.state,
|
||||
response.errorMessage,
|
||||
payload,
|
||||
response.statusCode
|
||||
)
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a list of {@link RemoteData} objects based on the response of a request to the REST server, with a list of
|
||||
*
|
||||
* Note: T extends HALResource not CacheableObject, because a PaginatedList is a CacheableObject in and of itself
|
||||
*
|
||||
* {@link FollowLinkConfig} that indicate which embedded info should be added to the objects
|
||||
* @param href$ Observable href of objects we want to retrieve
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
buildList<T extends CacheableObject>(href$: string | Observable<string>, ...linksToFollow: Array<FollowLinkConfig<T>>): Observable<RemoteData<PaginatedList<T>>> {
|
||||
if (typeof href$ === 'string') {
|
||||
href$ = observableOf(href$);
|
||||
}
|
||||
|
||||
const requestEntry$ = href$.pipe(getRequestFromRequestHref(this.requestService));
|
||||
const tDomainList$ = requestEntry$.pipe(
|
||||
getResourceLinksFromResponse(),
|
||||
switchMap((resourceUUIDs: string[]) => {
|
||||
return this.objectCache.getList(resourceUUIDs).pipe(
|
||||
map((objs: T[]) => {
|
||||
return objs.map((obj: T) =>
|
||||
this.linkService.resolveLinks(obj, ...linksToFollow)
|
||||
);
|
||||
}));
|
||||
}),
|
||||
startWith([]),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
const pageInfo$ = requestEntry$.pipe(
|
||||
filterSuccessfulResponses(),
|
||||
map((response: DSOSuccessResponse) => {
|
||||
if (hasValue(response.pageInfo)) {
|
||||
return Object.assign(new PageInfo(), response.pageInfo);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const payload$ = observableCombineLatest([tDomainList$, pageInfo$]).pipe(
|
||||
map(([tDomainList, pageInfo]) => {
|
||||
return new PaginatedList(pageInfo, tDomainList);
|
||||
})
|
||||
);
|
||||
|
||||
return this.toRemoteDataObservable(requestEntry$, payload$);
|
||||
buildList<T extends HALResource>(href$: string | Observable<string>, ...linksToFollow: Array<FollowLinkConfig<T>>): Observable<RemoteData<PaginatedList<T>>> {
|
||||
return this.buildFromHref<PaginatedList<T>>(href$, followLink('page', undefined, false, ...linksToFollow));
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns an array of RemoteData observables in to an observable RemoteData[]
|
||||
*
|
||||
* By doing this you lose most of the info about the status of the original
|
||||
* RemoteData objects, as you have to squash them down in to one. So use
|
||||
* this only if the list you need isn't available on the REST API. If you
|
||||
* need to use it, it's likely an indication that a REST endpoint is missing
|
||||
*
|
||||
* @param input the array of RemoteData observables to start from
|
||||
*/
|
||||
aggregate<T>(input: Array<Observable<RemoteData<T>>>): Observable<RemoteData<T[]>> {
|
||||
|
||||
if (isEmpty(input)) {
|
||||
return createSuccessfulRemoteDataObject$([]);
|
||||
return createSuccessfulRemoteDataObject$([], new Date().getTime());
|
||||
}
|
||||
|
||||
return observableCombineLatest(...input).pipe(
|
||||
return observableCombineLatest(input).pipe(
|
||||
map((arr) => {
|
||||
// The request of an aggregate RD should be pending if at least one
|
||||
// of the RDs it's based on is still in the state RequestPending
|
||||
const requestPending: boolean = arr
|
||||
.map((d: RemoteData<T>) => d.isRequestPending)
|
||||
.find((b: boolean) => b === true);
|
||||
const timeCompleted = arr
|
||||
.map((d: RemoteData<T>) => d.timeCompleted)
|
||||
.reduce((max: number, current: number) => current > max ? current : max)
|
||||
|
||||
// The response of an aggregate RD should be pending if no requests
|
||||
// are still pending and at least one of the RDs it's based
|
||||
// on is still in the state ResponsePending
|
||||
const responsePending: boolean = !requestPending && arr
|
||||
.map((d: RemoteData<T>) => d.isResponsePending)
|
||||
.find((b: boolean) => b === true);
|
||||
const msToLive = arr
|
||||
.map((d: RemoteData<T>) => d.msToLive)
|
||||
.reduce((min: number, current: number) => current < min ? current : min)
|
||||
|
||||
let isSuccessful: boolean;
|
||||
// isSuccessful should be undefined until all responses have come in.
|
||||
// We can't know its state beforehand. We also can't say it's false
|
||||
// because that would imply a request failed.
|
||||
if (!(requestPending || responsePending)) {
|
||||
isSuccessful = arr
|
||||
.map((d: RemoteData<T>) => d.hasSucceeded)
|
||||
.every((b: boolean) => b === true);
|
||||
const lastUpdated = arr
|
||||
.map((d: RemoteData<T>) => d.lastUpdated)
|
||||
.reduce((max: number, current: number) => current > max ? current : max)
|
||||
|
||||
let state: RequestEntryState;
|
||||
if (arr.some((d: RemoteData<T>) => d.isRequestPending)) {
|
||||
state = RequestEntryState.RequestPending;
|
||||
} else if (arr.some((d: RemoteData<T>) => d.isResponsePending)) {
|
||||
state = RequestEntryState.ResponsePending;
|
||||
} else if (arr.some((d: RemoteData<T>) => d.isErrorStale)) {
|
||||
state = RequestEntryState.ErrorStale;
|
||||
} else if (arr.some((d: RemoteData<T>) => d.isError)) {
|
||||
state = RequestEntryState.Error;
|
||||
} else if (arr.some((d: RemoteData<T>) => d.isSuccessStale)) {
|
||||
state = RequestEntryState.SuccessStale;
|
||||
} else {
|
||||
state = RequestEntryState.Success;
|
||||
}
|
||||
|
||||
const errorMessage: string = arr
|
||||
.map((d: RemoteData<T>) => d.error)
|
||||
.map((e: RemoteDataError, idx: number) => {
|
||||
.map((d: RemoteData<T>) => d.errorMessage)
|
||||
.map((e: string, idx: number) => {
|
||||
if (hasValue(e)) {
|
||||
return `[${idx}]: ${e.message}`;
|
||||
return `[${idx}]: ${e}`;
|
||||
}
|
||||
}).filter((e: string) => hasValue(e))
|
||||
.join(', ');
|
||||
|
||||
const statusText: string = arr
|
||||
.map((d: RemoteData<T>) => d.error)
|
||||
.map((e: RemoteDataError, idx: number) => {
|
||||
if (hasValue(e)) {
|
||||
return `[${idx}]: ${e.statusText}`;
|
||||
}
|
||||
}).filter((c: string) => hasValue(c))
|
||||
.join(', ');
|
||||
const statusCodes = new Set(arr
|
||||
.map((d: RemoteData<T>) => d.statusCode));
|
||||
|
||||
const statusCode: number = arr
|
||||
.map((d: RemoteData<T>) => d.error)
|
||||
.map((e: RemoteDataError, idx: number) => {
|
||||
if (hasValue(e)) {
|
||||
return e.statusCode;
|
||||
}
|
||||
}).filter((c: number) => hasValue(c))
|
||||
.reduce((acc, status) => status, undefined);
|
||||
let statusCode: number;
|
||||
|
||||
const error = new RemoteDataError(statusCode, statusText, errorMessage);
|
||||
if (statusCodes.size === 1) {
|
||||
statusCode = statusCodes.values().next().value;
|
||||
} else if (statusCodes.size > 1) {
|
||||
statusCode = 207;
|
||||
}
|
||||
|
||||
const payload: T[] = arr.map((d: RemoteData<T>) => d.payload);
|
||||
|
||||
return new RemoteData(
|
||||
requestPending,
|
||||
responsePending,
|
||||
isSuccessful,
|
||||
error,
|
||||
payload
|
||||
timeCompleted,
|
||||
msToLive,
|
||||
lastUpdated,
|
||||
state,
|
||||
errorMessage,
|
||||
payload,
|
||||
statusCode
|
||||
);
|
||||
}))
|
||||
}
|
||||
|
||||
private toPaginatedList<T>(input: Observable<RemoteData<T[] | PaginatedList<T>>>, pageInfo: PageInfo): Observable<RemoteData<PaginatedList<T>>> {
|
||||
return input.pipe(
|
||||
map((rd: RemoteData<T[] | PaginatedList<T>>) => {
|
||||
const rdAny = rd as any;
|
||||
const newRD = new RemoteData(rdAny.requestPending, rdAny.responsePending, rdAny.isSuccessful, rd.error, undefined);
|
||||
if (Array.isArray(rd.payload)) {
|
||||
return Object.assign(newRD, { payload: new PaginatedList(pageInfo, rd.payload) })
|
||||
} else if (isNotUndefined(rd.payload)) {
|
||||
return Object.assign(newRD, { payload: new PaginatedList(pageInfo, rd.payload.page) });
|
||||
} else {
|
||||
return Object.assign(newRD, { payload: new PaginatedList(pageInfo, []) });
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user