forked from hazza/dspace-angular
90252: Ensure calls come through if no subscribers
This commit is contained in:
66
src/app/core/cache/object-cache.service.spec.ts
vendored
66
src/app/core/cache/object-cache.service.spec.ts
vendored
@@ -211,25 +211,69 @@ describe('ObjectCacheService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('has', () => {
|
describe('hasByHref', () => {
|
||||||
|
describe('with requestUUID not specified', () => {
|
||||||
|
describe('getByHref emits an object', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(service, 'getByHref').and.returnValue(observableOf(cacheEntry));
|
||||||
|
});
|
||||||
|
|
||||||
describe('getByHref emits an object', () => {
|
it('should return true', () => {
|
||||||
beforeEach(() => {
|
expect(service.hasByHref(selfLink)).toBe(true);
|
||||||
spyOn(service, 'getByHref').and.returnValue(observableOf(cacheEntry));
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true', () => {
|
describe('getByHref emits nothing', () => {
|
||||||
expect(service.hasByHref(selfLink)).toBe(true);
|
beforeEach(() => {
|
||||||
|
spyOn(service, 'getByHref').and.returnValue(empty());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', () => {
|
||||||
|
expect(service.hasByHref(selfLink)).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getByHref emits nothing', () => {
|
describe('with requestUUID specified', () => {
|
||||||
beforeEach(() => {
|
describe('getByHref emits an object that includes the specified requestUUID', () => {
|
||||||
spyOn(service, 'getByHref').and.returnValue(empty());
|
beforeEach(() => {
|
||||||
|
spyOn(service, 'getByHref').and.returnValue(observableOf(Object.assign(cacheEntry, {
|
||||||
|
requestUUIDs: [
|
||||||
|
'something',
|
||||||
|
'something-else',
|
||||||
|
'specific-request',
|
||||||
|
]
|
||||||
|
})));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', () => {
|
||||||
|
expect(service.hasByHref(selfLink, 'specific-request')).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return false', () => {
|
describe('getByHref emits an object that doesn\'t include the specified requestUUID', () => {
|
||||||
expect(service.hasByHref(selfLink)).toBe(false);
|
beforeEach(() => {
|
||||||
|
spyOn(service, 'getByHref').and.returnValue(observableOf(Object.assign(cacheEntry, {
|
||||||
|
requestUUIDs: [
|
||||||
|
'something',
|
||||||
|
'something-else',
|
||||||
|
]
|
||||||
|
})));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', () => {
|
||||||
|
expect(service.hasByHref(selfLink, 'specific-request')).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getByHref emits nothing', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(service, 'getByHref').and.returnValue(empty());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false', () => {
|
||||||
|
expect(service.hasByHref(selfLink, 'specific-request')).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
2
src/app/core/cache/object-cache.service.ts
vendored
2
src/app/core/cache/object-cache.service.ts
vendored
@@ -282,7 +282,7 @@ export class ObjectCacheService {
|
|||||||
let result = false;
|
let result = false;
|
||||||
this.getByHref(href).subscribe((entry: ObjectCacheEntry) => {
|
this.getByHref(href).subscribe((entry: ObjectCacheEntry) => {
|
||||||
if (isNotEmpty(requestUUID)) {
|
if (isNotEmpty(requestUUID)) {
|
||||||
result = entry.requestUUIDs[0] === requestUUID; // todo: may make more sense to do entry.requestUUIDs.includes(requestUUID) instead
|
result = entry.requestUUIDs.includes(requestUUID);
|
||||||
} else {
|
} else {
|
||||||
result = true;
|
result = true;
|
||||||
}
|
}
|
||||||
|
@@ -864,6 +864,16 @@ describe('DataService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should call setStaleByUUID even if not subscribing to returned Observable', fakeAsync(() => {
|
||||||
|
service.invalidateByHref('some-href');
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(getByHrefSpy).toHaveBeenCalledWith('some-href');
|
||||||
|
expect(requestService.setStaleByUUID).toHaveBeenCalledWith('request1');
|
||||||
|
expect(requestService.setStaleByUUID).toHaveBeenCalledWith('request2');
|
||||||
|
expect(requestService.setStaleByUUID).toHaveBeenCalledWith('request3');
|
||||||
|
}));
|
||||||
|
|
||||||
it('should return an Observable that only emits true once all requests are stale', () => {
|
it('should return an Observable that only emits true once all requests are stale', () => {
|
||||||
testScheduler.run(({ cold, expectObservable }) => {
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
requestService.setStaleByUUID.and.callFake((uuid) => {
|
requestService.setStaleByUUID.and.callFake((uuid) => {
|
||||||
@@ -906,11 +916,11 @@ describe('DataService', () => {
|
|||||||
|
|
||||||
it('should retrieve href by ID and call deleteByHref', () => {
|
it('should retrieve href by ID and call deleteByHref', () => {
|
||||||
getIDHrefObsSpy.and.returnValue(observableOf('some-href'));
|
getIDHrefObsSpy.and.returnValue(observableOf('some-href'));
|
||||||
buildFromRequestUUIDSpy.and.returnValue(null);
|
buildFromRequestUUIDSpy.and.returnValue(createSuccessfulRemoteDataObject$({}));
|
||||||
|
|
||||||
service.delete('some-id').subscribe(rd => {
|
service.delete('some-id', ['a', 'b', 'c']).subscribe(rd => {
|
||||||
expect(getIDHrefObsSpy).toHaveBeenCalledWith('some-id');
|
expect(getIDHrefObsSpy).toHaveBeenCalledWith('some-id');
|
||||||
expect(deleteByHrefSpy).toHaveBeenCalledWith('some-href');
|
expect(deleteByHrefSpy).toHaveBeenCalledWith('some-href', ['a', 'b', 'c']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -925,6 +935,15 @@ describe('DataService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should call invalidateByHref even if not subscribing to returned Observable', fakeAsync(() => {
|
||||||
|
buildFromRequestUUIDSpy.and.returnValue(observableOf(MOCK_SUCCEEDED_RD));
|
||||||
|
|
||||||
|
service.deleteByHref('some-href');
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(invalidateByHrefSpy).toHaveBeenCalled();
|
||||||
|
}));
|
||||||
|
|
||||||
it('should not call invalidateByHref if the DELETE request fails', (done) => {
|
it('should not call invalidateByHref if the DELETE request fails', (done) => {
|
||||||
buildFromRequestUUIDSpy.and.returnValue(observableOf(MOCK_FAILED_RD));
|
buildFromRequestUUIDSpy.and.returnValue(observableOf(MOCK_FAILED_RD));
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { Operation } from 'fast-json-patch';
|
import { Operation } from 'fast-json-patch';
|
||||||
import { combineLatest, from, Observable, of as observableOf } from 'rxjs';
|
import { AsyncSubject, combineLatest, from as observableFrom, Observable, of as observableOf } from 'rxjs';
|
||||||
import {
|
import {
|
||||||
distinctUntilChanged,
|
distinctUntilChanged,
|
||||||
filter,
|
filter,
|
||||||
@@ -21,6 +21,7 @@ import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
|||||||
import { getClassForType } from '../cache/builders/build-decorators';
|
import { getClassForType } from '../cache/builders/build-decorators';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
import { RequestParam } from '../cache/models/request-param.model';
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
|
import { ObjectCacheEntry } from '../cache/object-cache.reducer';
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
import { DSpaceSerializer } from '../dspace-rest/dspace.serializer';
|
import { DSpaceSerializer } from '../dspace-rest/dspace.serializer';
|
||||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||||
@@ -596,18 +597,22 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
|
|||||||
* @return An Observable that will emit `true` once all requests are stale
|
* @return An Observable that will emit `true` once all requests are stale
|
||||||
*/
|
*/
|
||||||
invalidateByHref(href: string): Observable<boolean> {
|
invalidateByHref(href: string): Observable<boolean> {
|
||||||
return this.objectCache.getByHref(href).pipe(
|
const done$ = new AsyncSubject<boolean>();
|
||||||
map(oce => oce.requestUUIDs),
|
|
||||||
switchMap(requestUUIDs => {
|
this.objectCache.getByHref(href).pipe(
|
||||||
return from(requestUUIDs).pipe(
|
switchMap((oce: ObjectCacheEntry) => observableFrom(oce.requestUUIDs)),
|
||||||
mergeMap(requestUUID => this.requestService.setStaleByUUID(requestUUID)),
|
mergeMap((requestUUID: string) => this.requestService.setStaleByUUID(requestUUID)),
|
||||||
toArray(),
|
toArray(),
|
||||||
);
|
map((areRequestsStale: boolean[]) => areRequestsStale.every(Boolean)),
|
||||||
}),
|
|
||||||
map(areRequestsStale => areRequestsStale.every(Boolean)),
|
|
||||||
distinctUntilChanged(),
|
distinctUntilChanged(),
|
||||||
takeWhile(allStale => allStale === false, true),
|
).subscribe((done: boolean) => {
|
||||||
);
|
if (done) {
|
||||||
|
done$.next(true);
|
||||||
|
done$.complete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return done$;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -652,22 +657,25 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
|
|||||||
|
|
||||||
const response$ = this.rdbService.buildFromRequestUUID(requestId);
|
const response$ = this.rdbService.buildFromRequestUUID(requestId);
|
||||||
|
|
||||||
const invalidated$ = response$.pipe(
|
const invalidated$ = new AsyncSubject<boolean>();
|
||||||
|
response$.pipe(
|
||||||
getFirstCompletedRemoteData(),
|
getFirstCompletedRemoteData(),
|
||||||
switchMap(rd => {
|
switchMap((rd: RemoteData<NoContent>) => {
|
||||||
if (rd.hasSucceeded) {
|
if (rd.hasSucceeded) {
|
||||||
return this.invalidateByHref(href);
|
return this.invalidateByHref(href);
|
||||||
} else {
|
} else {
|
||||||
return [true];
|
return [true];
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
).subscribe((invalidated: boolean) => {
|
||||||
|
if (invalidated) {
|
||||||
|
invalidated$.next(true);
|
||||||
|
invalidated$.complete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return combineLatest([response$, invalidated$]).pipe(
|
return combineLatest([response$, invalidated$]).pipe(
|
||||||
filter(([_, invalidated]) => invalidated),
|
filter(([_, invalidated]) => invalidated),
|
||||||
tap(() => {
|
|
||||||
console.log(`DataService.deleteByHref() href=${href} done.`);
|
|
||||||
}),
|
|
||||||
map(([response, _]) => response),
|
map(([response, _]) => response),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -320,8 +320,8 @@ export class RequestService {
|
|||||||
this.store.dispatch(new RequestStaleAction(uuid));
|
this.store.dispatch(new RequestStaleAction(uuid));
|
||||||
|
|
||||||
return this.getByUUID(uuid).pipe(
|
return this.getByUUID(uuid).pipe(
|
||||||
map(request => isStale(request.state)),
|
map((request: RequestEntry) => isStale(request.state)),
|
||||||
filter(stale => stale === true),
|
filter((stale: boolean) => stale),
|
||||||
take(1),
|
take(1),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user