mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-08 10:34:15 +00:00
Switched to storing requests based on UUID, generalized UUIDIndexReducer to work with any type of index
This commit is contained in:
@@ -107,6 +107,7 @@
|
|||||||
"reflect-metadata": "0.1.10",
|
"reflect-metadata": "0.1.10",
|
||||||
"rxjs": "5.4.3",
|
"rxjs": "5.4.3",
|
||||||
"ts-md5": "1.2.2",
|
"ts-md5": "1.2.2",
|
||||||
|
"uuid": "^3.1.0",
|
||||||
"webfontloader": "1.6.28",
|
"webfontloader": "1.6.28",
|
||||||
"zone.js": "0.8.18"
|
"zone.js": "0.8.18"
|
||||||
},
|
},
|
||||||
@@ -126,6 +127,7 @@
|
|||||||
"@types/node": "8.0.34",
|
"@types/node": "8.0.34",
|
||||||
"@types/serve-static": "1.7.32",
|
"@types/serve-static": "1.7.32",
|
||||||
"@types/source-map": "0.5.1",
|
"@types/source-map": "0.5.1",
|
||||||
|
"@types/uuid": "^3.4.3",
|
||||||
"@types/webfontloader": "1.6.29",
|
"@types/webfontloader": "1.6.29",
|
||||||
"ajv": "5.2.3",
|
"ajv": "5.2.3",
|
||||||
"ajv-keywords": "2.1.0",
|
"ajv-keywords": "2.1.0",
|
||||||
|
@@ -29,7 +29,7 @@
|
|||||||
| translate}}
|
| translate}}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<ds-search-results [searchResults]="(resultsRDObs | async)?.page"
|
<ds-search-results [searchResults]="(resultsRDObs | async)"
|
||||||
[searchConfig]="searchOptions"></ds-search-results>
|
[searchConfig]="searchOptions"></ds-search-results>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import { initMockRequestService } from '../../shared/mocks/mock-request.service';
|
||||||
import { BrowseService } from './browse.service';
|
import { BrowseService } from './browse.service';
|
||||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||||
import { RequestService } from '../data/request.service';
|
import { RequestService } from '../data/request.service';
|
||||||
@@ -85,10 +86,6 @@ describe('BrowseService', () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function initMockRequestService() {
|
|
||||||
return jasmine.createSpyObj('requestService', ['configure']);
|
|
||||||
}
|
|
||||||
|
|
||||||
function initTestService() {
|
function initTestService() {
|
||||||
return new BrowseService(
|
return new BrowseService(
|
||||||
responseCache,
|
responseCache,
|
||||||
@@ -157,7 +154,7 @@ describe('BrowseService', () => {
|
|||||||
it('should configure a new BrowseEndpointRequest', () => {
|
it('should configure a new BrowseEndpointRequest', () => {
|
||||||
const metadatumKey = 'dc.date.issued';
|
const metadatumKey = 'dc.date.issued';
|
||||||
const linkName = 'items';
|
const linkName = 'items';
|
||||||
const expected = new BrowseEndpointRequest(browsesEndpointURL);
|
const expected = new BrowseEndpointRequest(requestService.generateRequestId(), browsesEndpointURL);
|
||||||
|
|
||||||
scheduler.schedule(() => service.getBrowseURLFor(metadatumKey, linkName).subscribe());
|
scheduler.schedule(() => service.getBrowseURLFor(metadatumKey, linkName).subscribe());
|
||||||
scheduler.flush();
|
scheduler.flush();
|
||||||
|
@@ -40,7 +40,7 @@ export class BrowseService extends HALEndpointService {
|
|||||||
return this.getEndpoint()
|
return this.getEndpoint()
|
||||||
.filter((href: string) => isNotEmpty(href))
|
.filter((href: string) => isNotEmpty(href))
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.map((endpointURL: string) => new BrowseEndpointRequest(endpointURL))
|
.map((endpointURL: string) => new BrowseEndpointRequest(this.requestService.generateRequestId(), endpointURL))
|
||||||
.do((request: RestRequest) => this.requestService.configure(request))
|
.do((request: RestRequest) => this.requestService.configure(request))
|
||||||
.flatMap((request: RestRequest) => {
|
.flatMap((request: RestRequest) => {
|
||||||
const [successResponse, errorResponse] = this.responseCache.get(request.href)
|
const [successResponse, errorResponse] = this.responseCache.get(request.href)
|
||||||
|
@@ -39,10 +39,10 @@ export class RemoteDataBuildService {
|
|||||||
this.objectCache.getRequestHrefBySelfLink(href));
|
this.objectCache.getRequestHrefBySelfLink(href));
|
||||||
|
|
||||||
const requestObs = Observable.race(
|
const requestObs = Observable.race(
|
||||||
hrefObs.flatMap((href: string) => this.requestService.get(href))
|
hrefObs.flatMap((href: string) => this.requestService.getByHref(href))
|
||||||
.filter((entry) => hasValue(entry)),
|
.filter((entry) => hasValue(entry)),
|
||||||
requestHrefObs.flatMap((requestHref) =>
|
requestHrefObs.flatMap((requestHref) =>
|
||||||
this.requestService.get(requestHref)).filter((entry) => hasValue(entry))
|
this.requestService.getByHref(requestHref)).filter((entry) => hasValue(entry))
|
||||||
);
|
);
|
||||||
|
|
||||||
const responseCacheObs = Observable.race(
|
const responseCacheObs = Observable.race(
|
||||||
@@ -115,7 +115,7 @@ export class RemoteDataBuildService {
|
|||||||
hrefObs = Observable.of(hrefObs);
|
hrefObs = Observable.of(hrefObs);
|
||||||
}
|
}
|
||||||
|
|
||||||
const requestObs = hrefObs.flatMap((href: string) => this.requestService.get(href))
|
const requestObs = hrefObs.flatMap((href: string) => this.requestService.getByHref(href))
|
||||||
.filter((entry) => hasValue(entry));
|
.filter((entry) => hasValue(entry));
|
||||||
const responseCacheObs = hrefObs.flatMap((href: string) => this.responseCache.get(href))
|
const responseCacheObs = hrefObs.flatMap((href: string) => this.responseCache.get(href))
|
||||||
.filter((entry) => hasValue(entry));
|
.filter((entry) => hasValue(entry));
|
||||||
@@ -169,7 +169,7 @@ export class RemoteDataBuildService {
|
|||||||
const resourceConstructor = NormalizedObjectFactory.getConstructor(resourceType);
|
const resourceConstructor = NormalizedObjectFactory.getConstructor(resourceType);
|
||||||
if (Array.isArray(normalized[relationship])) {
|
if (Array.isArray(normalized[relationship])) {
|
||||||
normalized[relationship].forEach((href: string) => {
|
normalized[relationship].forEach((href: string) => {
|
||||||
this.requestService.configure(new RestRequest(href))
|
this.requestService.configure(new RestRequest(this.requestService.generateRequestId(), href))
|
||||||
});
|
});
|
||||||
|
|
||||||
const rdArr = [];
|
const rdArr = [];
|
||||||
@@ -183,7 +183,7 @@ export class RemoteDataBuildService {
|
|||||||
links[relationship] = rdArr[0];
|
links[relationship] = rdArr[0];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.requestService.configure(new RestRequest(normalized[relationship]));
|
this.requestService.configure(new RestRequest(this.requestService.generateRequestId(), normalized[relationship]));
|
||||||
|
|
||||||
// The rest API can return a single URL to represent a list of resources (e.g. /items/:id/bitstreams)
|
// The rest API can return a single URL to represent a list of resources (e.g. /items/:id/bitstreams)
|
||||||
// in that case only 1 href will be stored in the normalized obj (so the isArray above fails),
|
// in that case only 1 href will be stored in the normalized obj (so the isArray above fails),
|
||||||
|
11
src/app/core/cache/object-cache.service.ts
vendored
11
src/app/core/cache/object-cache.service.ts
vendored
@@ -2,20 +2,21 @@ import { Injectable } from '@angular/core';
|
|||||||
import { MemoizedSelector, Store } from '@ngrx/store';
|
import { MemoizedSelector, Store } from '@ngrx/store';
|
||||||
|
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { IndexName } from '../index/index.reducer';
|
||||||
|
|
||||||
import { ObjectCacheEntry, CacheableObject } from './object-cache.reducer';
|
import { ObjectCacheEntry, CacheableObject } from './object-cache.reducer';
|
||||||
import { AddToObjectCacheAction, RemoveFromObjectCacheAction } from './object-cache.actions';
|
import { AddToObjectCacheAction, RemoveFromObjectCacheAction } from './object-cache.actions';
|
||||||
import { hasNoValue } from '../../shared/empty.util';
|
import { hasNoValue } from '../../shared/empty.util';
|
||||||
import { GenericConstructor } from '../shared/generic-constructor';
|
import { GenericConstructor } from '../shared/generic-constructor';
|
||||||
import { CoreState } from '../core.reducers';
|
import { coreSelector, CoreState } from '../core.reducers';
|
||||||
import { keySelector } from '../shared/selectors';
|
import { pathSelector } from '../shared/selectors';
|
||||||
|
|
||||||
function selfLinkFromUuidSelector(uuid: string): MemoizedSelector<CoreState, string> {
|
function selfLinkFromUuidSelector(uuid: string): MemoizedSelector<CoreState, string> {
|
||||||
return keySelector<string>('index/uuid', uuid);
|
return pathSelector<CoreState, string>(coreSelector, 'index', IndexName.OBJECT, uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
function entryFromSelfLinkSelector(selfLink: string): MemoizedSelector<CoreState, ObjectCacheEntry> {
|
function entryFromSelfLinkSelector(selfLink: string): MemoizedSelector<CoreState, ObjectCacheEntry> {
|
||||||
return keySelector<ObjectCacheEntry>('data/object', selfLink);
|
return pathSelector<CoreState, ObjectCacheEntry>(coreSelector, 'data/object', selfLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,7 +61,7 @@ export class ObjectCacheService {
|
|||||||
* the cached plain javascript object in to an instance of
|
* the cached plain javascript object in to an instance of
|
||||||
* a class.
|
* a class.
|
||||||
*
|
*
|
||||||
* e.g. get('c96588c6-72d3-425d-9d47-fa896255a695', Item)
|
* e.g. getByUUID('c96588c6-72d3-425d-9d47-fa896255a695', Item)
|
||||||
*
|
*
|
||||||
* @param uuid
|
* @param uuid
|
||||||
* The UUID of the object to get
|
* The UUID of the object to get
|
||||||
|
6
src/app/core/cache/response-cache.service.ts
vendored
6
src/app/core/cache/response-cache.service.ts
vendored
@@ -7,11 +7,11 @@ import { ResponseCacheEntry } from './response-cache.reducer';
|
|||||||
import { hasNoValue } from '../../shared/empty.util';
|
import { hasNoValue } from '../../shared/empty.util';
|
||||||
import { ResponseCacheRemoveAction, ResponseCacheAddAction } from './response-cache.actions';
|
import { ResponseCacheRemoveAction, ResponseCacheAddAction } from './response-cache.actions';
|
||||||
import { RestResponse } from './response-cache.models';
|
import { RestResponse } from './response-cache.models';
|
||||||
import { CoreState } from '../core.reducers';
|
import { coreSelector, CoreState } from '../core.reducers';
|
||||||
import { keySelector } from '../shared/selectors';
|
import { pathSelector } from '../shared/selectors';
|
||||||
|
|
||||||
function entryFromKeySelector(key: string): MemoizedSelector<CoreState, ResponseCacheEntry> {
|
function entryFromKeySelector(key: string): MemoizedSelector<CoreState, ResponseCacheEntry> {
|
||||||
return keySelector<ResponseCacheEntry>('data/response', key);
|
return pathSelector<CoreState, ResponseCacheEntry>(coreSelector, 'data/response', key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
|
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
|
||||||
import { TestScheduler } from 'rxjs/Rx';
|
import { TestScheduler } from 'rxjs/Rx';
|
||||||
import { GlobalConfig } from '../../../config';
|
import { GlobalConfig } from '../../../config';
|
||||||
|
import { initMockRequestService } from '../../shared/mocks/mock-request.service';
|
||||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||||
import { ConfigService } from './config.service';
|
import { ConfigService } from './config.service';
|
||||||
import { RequestService } from '../data/request.service';
|
import { RequestService } from '../data/request.service';
|
||||||
@@ -38,10 +39,6 @@ describe('ConfigService', () => {
|
|||||||
const scopedEndpoint = `${serviceEndpoint}/${scopeName}`;
|
const scopedEndpoint = `${serviceEndpoint}/${scopeName}`;
|
||||||
const searchEndpoint = `${serviceEndpoint}/${BROWSE}?uuid=${scopeID}`;
|
const searchEndpoint = `${serviceEndpoint}/${BROWSE}?uuid=${scopeID}`;
|
||||||
|
|
||||||
function initMockRequestService(): RequestService {
|
|
||||||
return jasmine.createSpyObj('requestService', ['configure']);
|
|
||||||
}
|
|
||||||
|
|
||||||
function initMockResponseCacheService(isSuccessful: boolean): ResponseCacheService {
|
function initMockResponseCacheService(isSuccessful: boolean): ResponseCacheService {
|
||||||
return jasmine.createSpyObj('responseCache', {
|
return jasmine.createSpyObj('responseCache', {
|
||||||
get: cold('c-', {
|
get: cold('c-', {
|
||||||
@@ -70,7 +67,7 @@ describe('ConfigService', () => {
|
|||||||
describe('getConfigByHref', () => {
|
describe('getConfigByHref', () => {
|
||||||
|
|
||||||
it('should configure a new ConfigRequest', () => {
|
it('should configure a new ConfigRequest', () => {
|
||||||
const expected = new ConfigRequest(scopedEndpoint);
|
const expected = new ConfigRequest(requestService.generateRequestId(), scopedEndpoint);
|
||||||
scheduler.schedule(() => service.getConfigByHref(scopedEndpoint).subscribe());
|
scheduler.schedule(() => service.getConfigByHref(scopedEndpoint).subscribe());
|
||||||
scheduler.flush();
|
scheduler.flush();
|
||||||
|
|
||||||
@@ -81,7 +78,7 @@ describe('ConfigService', () => {
|
|||||||
describe('getConfigByName', () => {
|
describe('getConfigByName', () => {
|
||||||
|
|
||||||
it('should configure a new ConfigRequest', () => {
|
it('should configure a new ConfigRequest', () => {
|
||||||
const expected = new ConfigRequest(scopedEndpoint);
|
const expected = new ConfigRequest(requestService.generateRequestId(), scopedEndpoint);
|
||||||
scheduler.schedule(() => service.getConfigByName(scopeName).subscribe());
|
scheduler.schedule(() => service.getConfigByName(scopeName).subscribe());
|
||||||
scheduler.flush();
|
scheduler.flush();
|
||||||
|
|
||||||
@@ -93,7 +90,7 @@ describe('ConfigService', () => {
|
|||||||
|
|
||||||
it('should configure a new ConfigRequest', () => {
|
it('should configure a new ConfigRequest', () => {
|
||||||
findOptions.scopeID = scopeID;
|
findOptions.scopeID = scopeID;
|
||||||
const expected = new ConfigRequest(searchEndpoint);
|
const expected = new ConfigRequest(requestService.generateRequestId(), searchEndpoint);
|
||||||
scheduler.schedule(() => service.getConfigBySearch(findOptions).subscribe());
|
scheduler.schedule(() => service.getConfigBySearch(findOptions).subscribe());
|
||||||
scheduler.flush();
|
scheduler.flush();
|
||||||
|
|
||||||
|
@@ -75,14 +75,14 @@ export abstract class ConfigService extends HALEndpointService {
|
|||||||
return this.getEndpoint()
|
return this.getEndpoint()
|
||||||
.filter((href: string) => isNotEmpty(href))
|
.filter((href: string) => isNotEmpty(href))
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.map((endpointURL: string) => new ConfigRequest(endpointURL))
|
.map((endpointURL: string) => new ConfigRequest(this.requestService.generateRequestId(), endpointURL))
|
||||||
.do((request: RestRequest) => this.requestService.configure(request))
|
.do((request: RestRequest) => this.requestService.configure(request))
|
||||||
.flatMap((request: RestRequest) => this.getConfig(request))
|
.flatMap((request: RestRequest) => this.getConfig(request))
|
||||||
.distinctUntilChanged();
|
.distinctUntilChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public getConfigByHref(href: string): Observable<ConfigData> {
|
public getConfigByHref(href: string): Observable<ConfigData> {
|
||||||
const request = new ConfigRequest(href);
|
const request = new ConfigRequest(this.requestService.generateRequestId(), href);
|
||||||
this.requestService.configure(request);
|
this.requestService.configure(request);
|
||||||
|
|
||||||
return this.getConfig(request);
|
return this.getConfig(request);
|
||||||
@@ -93,7 +93,7 @@ export abstract class ConfigService extends HALEndpointService {
|
|||||||
.map((endpoint: string) => this.getConfigByNameHref(endpoint, name))
|
.map((endpoint: string) => this.getConfigByNameHref(endpoint, name))
|
||||||
.filter((href: string) => isNotEmpty(href))
|
.filter((href: string) => isNotEmpty(href))
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.map((endpointURL: string) => new ConfigRequest(endpointURL))
|
.map((endpointURL: string) => new ConfigRequest(this.requestService.generateRequestId(), endpointURL))
|
||||||
.do((request: RestRequest) => this.requestService.configure(request))
|
.do((request: RestRequest) => this.requestService.configure(request))
|
||||||
.flatMap((request: RestRequest) => this.getConfig(request))
|
.flatMap((request: RestRequest) => this.getConfig(request))
|
||||||
.distinctUntilChanged();
|
.distinctUntilChanged();
|
||||||
@@ -104,7 +104,7 @@ export abstract class ConfigService extends HALEndpointService {
|
|||||||
.map((endpoint: string) => this.getConfigSearchHref(endpoint, options))
|
.map((endpoint: string) => this.getConfigSearchHref(endpoint, options))
|
||||||
.filter((href: string) => isNotEmpty(href))
|
.filter((href: string) => isNotEmpty(href))
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.map((endpointURL: string) => new ConfigRequest(endpointURL))
|
.map((endpointURL: string) => new ConfigRequest(this.requestService.generateRequestId(), endpointURL))
|
||||||
.do((request: RestRequest) => this.requestService.configure(request))
|
.do((request: RestRequest) => this.requestService.configure(request))
|
||||||
.flatMap((request: RestRequest) => this.getConfig(request))
|
.flatMap((request: RestRequest) => this.getConfig(request))
|
||||||
.distinctUntilChanged();
|
.distinctUntilChanged();
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
import { ObjectCacheEffects } from './cache/object-cache.effects';
|
import { ObjectCacheEffects } from './cache/object-cache.effects';
|
||||||
import { ResponseCacheEffects } from './cache/response-cache.effects';
|
import { ResponseCacheEffects } from './cache/response-cache.effects';
|
||||||
import { UUIDIndexEffects } from './index/uuid-index.effects';
|
import { UUIDIndexEffects } from './index/index.effects';
|
||||||
import { RequestEffects } from './data/request.effects';
|
import { RequestEffects } from './data/request.effects';
|
||||||
|
|
||||||
export const coreEffects = [
|
export const coreEffects = [
|
||||||
|
@@ -37,6 +37,7 @@ import { RouteService } from '../shared/route.service';
|
|||||||
import { SubmissionDefinitionsConfigService } from './config/submission-definitions-config.service';
|
import { SubmissionDefinitionsConfigService } from './config/submission-definitions-config.service';
|
||||||
import { SubmissionFormsConfigService } from './config/submission-forms-config.service';
|
import { SubmissionFormsConfigService } from './config/submission-forms-config.service';
|
||||||
import { SubmissionSectionsConfigService } from './config/submission-sections-config.service';
|
import { SubmissionSectionsConfigService } from './config/submission-sections-config.service';
|
||||||
|
import { UUIDService } from './shared/uuid.service';
|
||||||
|
|
||||||
const IMPORTS = [
|
const IMPORTS = [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
@@ -75,6 +76,7 @@ const PROVIDERS = [
|
|||||||
SubmissionDefinitionsConfigService,
|
SubmissionDefinitionsConfigService,
|
||||||
SubmissionFormsConfigService,
|
SubmissionFormsConfigService,
|
||||||
SubmissionSectionsConfigService,
|
SubmissionSectionsConfigService,
|
||||||
|
UUIDService,
|
||||||
{ provide: NativeWindowService, useFactory: NativeWindowFactory }
|
{ provide: NativeWindowService, useFactory: NativeWindowFactory }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -2,21 +2,21 @@ import { ActionReducerMap, createFeatureSelector } from '@ngrx/store';
|
|||||||
|
|
||||||
import { responseCacheReducer, ResponseCacheState } from './cache/response-cache.reducer';
|
import { responseCacheReducer, ResponseCacheState } from './cache/response-cache.reducer';
|
||||||
import { objectCacheReducer, ObjectCacheState } from './cache/object-cache.reducer';
|
import { objectCacheReducer, ObjectCacheState } from './cache/object-cache.reducer';
|
||||||
import { uuidIndexReducer, UUIDIndexState } from './index/uuid-index.reducer';
|
import { indexReducer, IndexState } from './index/index.reducer';
|
||||||
import { requestReducer, RequestState } from './data/request.reducer';
|
import { requestReducer, RequestState } from './data/request.reducer';
|
||||||
|
|
||||||
export interface CoreState {
|
export interface CoreState {
|
||||||
'data/object': ObjectCacheState,
|
'data/object': ObjectCacheState,
|
||||||
'data/response': ResponseCacheState,
|
'data/response': ResponseCacheState,
|
||||||
'data/request': RequestState,
|
'data/request': RequestState,
|
||||||
'index/uuid': UUIDIndexState
|
'index': IndexState
|
||||||
}
|
}
|
||||||
|
|
||||||
export const coreReducers: ActionReducerMap<CoreState> = {
|
export const coreReducers: ActionReducerMap<CoreState> = {
|
||||||
'data/object': objectCacheReducer,
|
'data/object': objectCacheReducer,
|
||||||
'data/response': responseCacheReducer,
|
'data/response': responseCacheReducer,
|
||||||
'data/request': requestReducer,
|
'data/request': requestReducer,
|
||||||
'index/uuid': uuidIndexReducer
|
'index': indexReducer
|
||||||
};
|
};
|
||||||
|
|
||||||
export const coreSelector = createFeatureSelector<CoreState>('core');
|
export const coreSelector = createFeatureSelector<CoreState>('core');
|
||||||
|
@@ -11,7 +11,7 @@ describe('BrowseResponseParsingService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('parse', () => {
|
describe('parse', () => {
|
||||||
const validRequest = new BrowseEndpointRequest('https://rest.api/discover/browses');
|
const validRequest = new BrowseEndpointRequest('clients/b186e8ce-e99c-4183-bc9a-42b4821bdb78', 'https://rest.api/discover/browses');
|
||||||
|
|
||||||
const validResponse = {
|
const validResponse = {
|
||||||
payload: {
|
payload: {
|
||||||
|
@@ -2,6 +2,7 @@ import { Store } from '@ngrx/store';
|
|||||||
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
|
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
|
||||||
import { TestScheduler } from 'rxjs/Rx';
|
import { TestScheduler } from 'rxjs/Rx';
|
||||||
import { GlobalConfig } from '../../../config';
|
import { GlobalConfig } from '../../../config';
|
||||||
|
import { initMockRequestService } from '../../shared/mocks/mock-request.service';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
import { NormalizedCommunity } from '../cache/models/normalized-community.model';
|
import { NormalizedCommunity } from '../cache/models/normalized-community.model';
|
||||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||||
@@ -62,10 +63,6 @@ describe('ComColDataService', () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function initMockRequestService(): RequestService {
|
|
||||||
return jasmine.createSpyObj('requestService', ['configure']);
|
|
||||||
}
|
|
||||||
|
|
||||||
function initMockResponseCacheService(isSuccessful: boolean): ResponseCacheService {
|
function initMockResponseCacheService(isSuccessful: boolean): ResponseCacheService {
|
||||||
return jasmine.createSpyObj('responseCache', {
|
return jasmine.createSpyObj('responseCache', {
|
||||||
get: cold('c-', {
|
get: cold('c-', {
|
||||||
@@ -110,7 +107,7 @@ describe('ComColDataService', () => {
|
|||||||
responseCache = initMockResponseCacheService(true);
|
responseCache = initMockResponseCacheService(true);
|
||||||
service = initTestService();
|
service = initTestService();
|
||||||
|
|
||||||
const expected = new FindByIDRequest(communityEndpoint, scopeID);
|
const expected = new FindByIDRequest(requestService.generateRequestId(), communityEndpoint, scopeID);
|
||||||
|
|
||||||
scheduler.schedule(() => service.getScopedEndpoint(scopeID).subscribe());
|
scheduler.schedule(() => service.getScopedEndpoint(scopeID).subscribe());
|
||||||
scheduler.flush();
|
scheduler.flush();
|
||||||
|
@@ -33,7 +33,7 @@ export abstract class ComColDataService<TNormalized extends CacheableObject, TDo
|
|||||||
.filter((href: string) => isNotEmpty(href))
|
.filter((href: string) => isNotEmpty(href))
|
||||||
.take(1)
|
.take(1)
|
||||||
.do((href: string) => {
|
.do((href: string) => {
|
||||||
const request = new FindByIDRequest(href, scopeID);
|
const request = new FindByIDRequest(this.requestService.generateRequestId(), href, scopeID);
|
||||||
this.requestService.configure(request);
|
this.requestService.configure(request);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -21,7 +21,7 @@ describe('ConfigResponseParsingService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('parse', () => {
|
describe('parse', () => {
|
||||||
const validRequest = new ConfigRequest('https://rest.api/config/submissiondefinitions/traditional');
|
const validRequest = new ConfigRequest('69f375b5-19f4-4453-8c7a-7dc5c55aafbb', 'https://rest.api/config/submissiondefinitions/traditional');
|
||||||
|
|
||||||
const validResponse = {
|
const validResponse = {
|
||||||
payload: {
|
payload: {
|
||||||
|
@@ -79,7 +79,7 @@ export abstract class DataService<TNormalized extends CacheableObject, TDomain>
|
|||||||
.filter((href: string) => hasValue(href))
|
.filter((href: string) => hasValue(href))
|
||||||
.take(1)
|
.take(1)
|
||||||
.subscribe((href: string) => {
|
.subscribe((href: string) => {
|
||||||
const request = new FindAllRequest(href, options);
|
const request = new FindAllRequest(this.requestService.generateRequestId(), href, options);
|
||||||
this.requestService.configure(request);
|
this.requestService.configure(request);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ export abstract class DataService<TNormalized extends CacheableObject, TDomain>
|
|||||||
.filter((href: string) => hasValue(href))
|
.filter((href: string) => hasValue(href))
|
||||||
.take(1)
|
.take(1)
|
||||||
.subscribe((href: string) => {
|
.subscribe((href: string) => {
|
||||||
const request = new FindByIDRequest(href, id);
|
const request = new FindByIDRequest(this.requestService.generateRequestId(), href, id);
|
||||||
this.requestService.configure(request);
|
this.requestService.configure(request);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ export abstract class DataService<TNormalized extends CacheableObject, TDomain>
|
|||||||
}
|
}
|
||||||
|
|
||||||
findByHref(href: string): Observable<RemoteData<TDomain>> {
|
findByHref(href: string): Observable<RemoteData<TDomain>> {
|
||||||
this.requestService.configure(new RestRequest(href));
|
this.requestService.configure(new RestRequest(this.requestService.generateRequestId(), href));
|
||||||
return this.rdbService.buildSingle<TNormalized, TDomain>(href, this.normalizedResourceType);
|
return this.rdbService.buildSingle<TNormalized, TDomain>(href, this.normalizedResourceType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@ export abstract class DataService<TNormalized extends CacheableObject, TDomain>
|
|||||||
.filter((href: string) => hasValue(href))
|
.filter((href: string) => hasValue(href))
|
||||||
.take(1)
|
.take(1)
|
||||||
.subscribe((href: string) => {
|
.subscribe((href: string) => {
|
||||||
const request = new RestRequest(href, RestRequestMethod.Post, dso);
|
const request = new RestRequest(this.requestService.generateRequestId(), href, RestRequestMethod.Post, dso);
|
||||||
this.requestService.configure(request);
|
this.requestService.configure(request);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -27,8 +27,14 @@ export class RequestExecuteAction implements Action {
|
|||||||
type = RequestActionTypes.EXECUTE;
|
type = RequestActionTypes.EXECUTE;
|
||||||
payload: string;
|
payload: string;
|
||||||
|
|
||||||
constructor(key: string) {
|
/**
|
||||||
this.payload = key
|
* Create a new RequestExecuteAction
|
||||||
|
*
|
||||||
|
* @param uuid
|
||||||
|
* the request's uuid
|
||||||
|
*/
|
||||||
|
constructor(uuid: string) {
|
||||||
|
this.payload = uuid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,11 +48,11 @@ export class RequestCompleteAction implements Action {
|
|||||||
/**
|
/**
|
||||||
* Create a new RequestCompleteAction
|
* Create a new RequestCompleteAction
|
||||||
*
|
*
|
||||||
* @param key
|
* @param uuid
|
||||||
* the key under which this request is stored,
|
* the request's uuid
|
||||||
*/
|
*/
|
||||||
constructor(key: string) {
|
constructor(uuid: string) {
|
||||||
this.payload = key;
|
this.payload = uuid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* tslint:enable:max-classes-per-file */
|
/* tslint:enable:max-classes-per-file */
|
||||||
|
@@ -25,7 +25,7 @@ export class RequestEffects {
|
|||||||
@Effect() execute = this.actions$
|
@Effect() execute = this.actions$
|
||||||
.ofType(RequestActionTypes.EXECUTE)
|
.ofType(RequestActionTypes.EXECUTE)
|
||||||
.flatMap((action: RequestExecuteAction) => {
|
.flatMap((action: RequestExecuteAction) => {
|
||||||
return this.requestService.get(action.payload)
|
return this.requestService.getByUUID(action.payload)
|
||||||
.take(1);
|
.take(1);
|
||||||
})
|
})
|
||||||
.map((entry: RequestEntry) => entry.request)
|
.map((entry: RequestEntry) => entry.request)
|
||||||
@@ -42,10 +42,10 @@ export class RequestEffects {
|
|||||||
.map((data: DSpaceRESTV2Response) =>
|
.map((data: DSpaceRESTV2Response) =>
|
||||||
this.injector.get(request.getResponseParser()).parse(request, data))
|
this.injector.get(request.getResponseParser()).parse(request, data))
|
||||||
.do((response: RestResponse) => this.responseCache.add(request.href, response, this.EnvConfig.cache.msToLive))
|
.do((response: RestResponse) => this.responseCache.add(request.href, response, this.EnvConfig.cache.msToLive))
|
||||||
.map((response: RestResponse) => new RequestCompleteAction(request.href))
|
.map((response: RestResponse) => new RequestCompleteAction(request.uuid))
|
||||||
.catch((error: RequestError) => Observable.of(new ErrorResponse(error))
|
.catch((error: RequestError) => Observable.of(new ErrorResponse(error))
|
||||||
.do((response: RestResponse) => this.responseCache.add(request.href, response, this.EnvConfig.cache.msToLive))
|
.do((response: RestResponse) => this.responseCache.add(request.href, response, this.EnvConfig.cache.msToLive))
|
||||||
.map((response: RestResponse) => new RequestCompleteAction(request.href)));
|
.map((response: RestResponse) => new RequestCompleteAction(request.uuid)));
|
||||||
});
|
});
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@@ -31,10 +31,12 @@ export enum RestRequestMethod {
|
|||||||
|
|
||||||
export class RestRequest {
|
export class RestRequest {
|
||||||
constructor(
|
constructor(
|
||||||
|
public uuid: string,
|
||||||
public href: string,
|
public href: string,
|
||||||
public method: RestRequestMethod = RestRequestMethod.Get,
|
public method: RestRequestMethod = RestRequestMethod.Get,
|
||||||
public body?: any
|
public body?: any
|
||||||
) { }
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
||||||
return DSOResponseParsingService;
|
return DSOResponseParsingService;
|
||||||
@@ -43,10 +45,11 @@ export class RestRequest {
|
|||||||
|
|
||||||
export class FindByIDRequest extends RestRequest {
|
export class FindByIDRequest extends RestRequest {
|
||||||
constructor(
|
constructor(
|
||||||
|
uuid: string,
|
||||||
href: string,
|
href: string,
|
||||||
public resourceID: string
|
public resourceID: string
|
||||||
) {
|
) {
|
||||||
super(href);
|
super(uuid, href);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,17 +62,18 @@ export class FindAllOptions {
|
|||||||
|
|
||||||
export class FindAllRequest extends RestRequest {
|
export class FindAllRequest extends RestRequest {
|
||||||
constructor(
|
constructor(
|
||||||
|
uuid: string,
|
||||||
href: string,
|
href: string,
|
||||||
public options?: FindAllOptions,
|
public options?: FindAllOptions,
|
||||||
) {
|
) {
|
||||||
super(href);
|
super(uuid, href);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RootEndpointRequest extends RestRequest {
|
export class RootEndpointRequest extends RestRequest {
|
||||||
constructor(EnvConfig: GlobalConfig) {
|
constructor(uuid: string, EnvConfig: GlobalConfig) {
|
||||||
const href = new RESTURLCombiner(EnvConfig, '/').toString();
|
const href = new RESTURLCombiner(EnvConfig, '/').toString();
|
||||||
super(href);
|
super(uuid, href);
|
||||||
}
|
}
|
||||||
|
|
||||||
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
||||||
@@ -78,8 +82,8 @@ export class RootEndpointRequest extends RestRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class BrowseEndpointRequest extends RestRequest {
|
export class BrowseEndpointRequest extends RestRequest {
|
||||||
constructor(href: string) {
|
constructor(uuid: string, href: string) {
|
||||||
super(href);
|
super(uuid, href);
|
||||||
}
|
}
|
||||||
|
|
||||||
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
||||||
@@ -88,8 +92,8 @@ export class BrowseEndpointRequest extends RestRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ConfigRequest extends RestRequest {
|
export class ConfigRequest extends RestRequest {
|
||||||
constructor(href: string) {
|
constructor(uuid: string, href: string) {
|
||||||
super(href);
|
super(uuid, href);
|
||||||
}
|
}
|
||||||
|
|
||||||
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
||||||
|
@@ -16,11 +16,13 @@ class NullAction extends RequestCompleteAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('requestReducer', () => {
|
describe('requestReducer', () => {
|
||||||
|
const id1 = 'clients/eca2ea1d-6a6a-4f62-8907-176d5fec5014';
|
||||||
|
const id2 = 'clients/eb7cde2e-a03f-4f0b-ac5d-888a4ef2b4eb';
|
||||||
const link1 = 'https://dspace7.4science.it/dspace-spring-rest/api/core/items/567a639f-f5ff-4126-807c-b7d0910808c8';
|
const link1 = 'https://dspace7.4science.it/dspace-spring-rest/api/core/items/567a639f-f5ff-4126-807c-b7d0910808c8';
|
||||||
const link2 = 'https://dspace7.4science.it/dspace-spring-rest/api/core/items/1911e8a4-6939-490c-b58b-a5d70f8d91fb';
|
const link2 = 'https://dspace7.4science.it/dspace-spring-rest/api/core/items/1911e8a4-6939-490c-b58b-a5d70f8d91fb';
|
||||||
const testState: RequestState = {
|
const testState: RequestState = {
|
||||||
[link1]: {
|
[id1]: {
|
||||||
request: new RestRequest(link1),
|
request: new RestRequest(id1, link1),
|
||||||
requestPending: false,
|
requestPending: false,
|
||||||
responsePending: false,
|
responsePending: false,
|
||||||
completed: false
|
completed: false
|
||||||
@@ -44,37 +46,40 @@ describe('requestReducer', () => {
|
|||||||
|
|
||||||
it('should add the new RestRequest and set \'requestPending\' to true, \'responsePending\' to false and \'completed\' to false for the given RestRequest in the state, in response to a CONFIGURE action', () => {
|
it('should add the new RestRequest and set \'requestPending\' to true, \'responsePending\' to false and \'completed\' to false for the given RestRequest in the state, in response to a CONFIGURE action', () => {
|
||||||
const state = testState;
|
const state = testState;
|
||||||
const request = new RestRequest(link2);
|
const request = new RestRequest(id2, link2);
|
||||||
|
|
||||||
const action = new RequestConfigureAction(request);
|
const action = new RequestConfigureAction(request);
|
||||||
const newState = requestReducer(state, action);
|
const newState = requestReducer(state, action);
|
||||||
|
|
||||||
expect(newState[link2].request.href).toEqual(link2);
|
expect(newState[id2].request.uuid).toEqual(id2);
|
||||||
expect(newState[link2].requestPending).toEqual(true);
|
expect(newState[id2].request.href).toEqual(link2);
|
||||||
expect(newState[link2].responsePending).toEqual(false);
|
expect(newState[id2].requestPending).toEqual(true);
|
||||||
expect(newState[link2].completed).toEqual(false);
|
expect(newState[id2].responsePending).toEqual(false);
|
||||||
|
expect(newState[id2].completed).toEqual(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set \'requestPending\' to false, \'responsePending\' to true and leave \'completed\' untouched for the given RestRequest in the state, in response to an EXECUTE action', () => {
|
it('should set \'requestPending\' to false, \'responsePending\' to true and leave \'completed\' untouched for the given RestRequest in the state, in response to an EXECUTE action', () => {
|
||||||
const state = testState;
|
const state = testState;
|
||||||
|
|
||||||
const action = new RequestExecuteAction(link1);
|
const action = new RequestExecuteAction(id1);
|
||||||
const newState = requestReducer(state, action);
|
const newState = requestReducer(state, action);
|
||||||
|
|
||||||
expect(newState[link1].request.href).toEqual(link1);
|
expect(newState[id1].request.uuid).toEqual(id1);
|
||||||
expect(newState[link1].requestPending).toEqual(false);
|
expect(newState[id1].request.href).toEqual(link1);
|
||||||
expect(newState[link1].responsePending).toEqual(true);
|
expect(newState[id1].requestPending).toEqual(false);
|
||||||
expect(newState[link1].completed).toEqual(state[link1].completed);
|
expect(newState[id1].responsePending).toEqual(true);
|
||||||
|
expect(newState[id1].completed).toEqual(state[id1].completed);
|
||||||
});
|
});
|
||||||
it('should leave \'requestPending\' untouched, set \'responsePending\' to false and \'completed\' to true for the given RestRequest in the state, in response to a COMPLETE action', () => {
|
it('should leave \'requestPending\' untouched, set \'responsePending\' to false and \'completed\' to true for the given RestRequest in the state, in response to a COMPLETE action', () => {
|
||||||
const state = testState;
|
const state = testState;
|
||||||
|
|
||||||
const action = new RequestCompleteAction(link1);
|
const action = new RequestCompleteAction(id1);
|
||||||
const newState = requestReducer(state, action);
|
const newState = requestReducer(state, action);
|
||||||
|
|
||||||
expect(newState[link1].request.href).toEqual(link1);
|
expect(newState[id1].request.uuid).toEqual(id1);
|
||||||
expect(newState[link1].requestPending).toEqual(state[link1].requestPending);
|
expect(newState[id1].request.href).toEqual(link1);
|
||||||
expect(newState[link1].responsePending).toEqual(false);
|
expect(newState[id1].requestPending).toEqual(state[id1].requestPending);
|
||||||
expect(newState[link1].completed).toEqual(true);
|
expect(newState[id1].responsePending).toEqual(false);
|
||||||
|
expect(newState[id1].completed).toEqual(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -12,7 +12,7 @@ export class RequestEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface RequestState {
|
export interface RequestState {
|
||||||
[key: string]: RequestEntry
|
[uuid: string]: RequestEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
// Object.create(null) ensures the object has no default js properties (e.g. `__proto__`)
|
// Object.create(null) ensures the object has no default js properties (e.g. `__proto__`)
|
||||||
@@ -41,7 +41,7 @@ export function requestReducer(state = initialState, action: RequestAction): Req
|
|||||||
|
|
||||||
function configureRequest(state: RequestState, action: RequestConfigureAction): RequestState {
|
function configureRequest(state: RequestState, action: RequestConfigureAction): RequestState {
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
[action.payload.href]: {
|
[action.payload.uuid]: {
|
||||||
request: action.payload,
|
request: action.payload,
|
||||||
requestPending: true,
|
requestPending: true,
|
||||||
responsePending: false,
|
responsePending: false,
|
||||||
|
@@ -10,14 +10,20 @@ import { DSOSuccessResponse, RestResponse } from '../cache/response-cache.models
|
|||||||
import { ResponseCacheEntry } from '../cache/response-cache.reducer';
|
import { ResponseCacheEntry } from '../cache/response-cache.reducer';
|
||||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||||
import { coreSelector, CoreState } from '../core.reducers';
|
import { coreSelector, CoreState } from '../core.reducers';
|
||||||
import { keySelector } from '../shared/selectors';
|
import { IndexName } from '../index/index.reducer';
|
||||||
|
import { pathSelector } from '../shared/selectors';
|
||||||
|
import { UUIDService } from '../shared/uuid.service';
|
||||||
import { RequestConfigureAction, RequestExecuteAction } from './request.actions';
|
import { RequestConfigureAction, RequestExecuteAction } from './request.actions';
|
||||||
import { RestRequest, RestRequestMethod } from './request.models';
|
import { RestRequest, RestRequestMethod } from './request.models';
|
||||||
|
|
||||||
import { RequestEntry, RequestState } from './request.reducer';
|
import { RequestEntry, RequestState } from './request.reducer';
|
||||||
|
|
||||||
function entryFromHrefSelector(href: string): MemoizedSelector<CoreState, RequestEntry> {
|
function entryFromUUIDSelector(uuid: string): MemoizedSelector<CoreState, RequestEntry> {
|
||||||
return keySelector<RequestEntry>('data/request', href);
|
return pathSelector<CoreState, RequestEntry>(coreSelector, 'data/request', uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
function uuidFromHrefSelector(href: string): MemoizedSelector<CoreState, string> {
|
||||||
|
return pathSelector<CoreState, string>(coreSelector, 'index', IndexName.REQUEST, href);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function requestStateSelector(): MemoizedSelector<CoreState, RequestState> {
|
export function requestStateSelector(): MemoizedSelector<CoreState, RequestState> {
|
||||||
@@ -32,18 +38,23 @@ export class RequestService {
|
|||||||
|
|
||||||
constructor(private objectCache: ObjectCacheService,
|
constructor(private objectCache: ObjectCacheService,
|
||||||
private responseCache: ResponseCacheService,
|
private responseCache: ResponseCacheService,
|
||||||
|
private uuidService: UUIDService,
|
||||||
private store: Store<CoreState>) {
|
private store: Store<CoreState>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
isPending(href: string): boolean {
|
generateRequestId(): string {
|
||||||
|
return `client/${this.uuidService.generate()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
isPending(uuid: string): boolean {
|
||||||
// first check requests that haven't made it to the store yet
|
// first check requests that haven't made it to the store yet
|
||||||
if (this.requestsOnTheirWayToTheStore.includes(href)) {
|
if (this.requestsOnTheirWayToTheStore.includes(uuid)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// then check the store
|
// then check the store
|
||||||
let isPending = false;
|
let isPending = false;
|
||||||
this.store.select(entryFromHrefSelector(href))
|
this.store.select(entryFromUUIDSelector(uuid))
|
||||||
.take(1)
|
.take(1)
|
||||||
.subscribe((re: RequestEntry) => {
|
.subscribe((re: RequestEntry) => {
|
||||||
isPending = (hasValue(re) && !re.completed)
|
isPending = (hasValue(re) && !re.completed)
|
||||||
@@ -52,8 +63,13 @@ export class RequestService {
|
|||||||
return isPending;
|
return isPending;
|
||||||
}
|
}
|
||||||
|
|
||||||
get(href: string): Observable<RequestEntry> {
|
getByUUID(uuid: string): Observable<RequestEntry> {
|
||||||
return this.store.select(entryFromHrefSelector(href));
|
return this.store.select(entryFromUUIDSelector(uuid));
|
||||||
|
}
|
||||||
|
|
||||||
|
getByHref(href: string): Observable<RequestEntry> {
|
||||||
|
return this.store.select(uuidFromHrefSelector(href))
|
||||||
|
.flatMap((uuid: string) => this.getByUUID(uuid));
|
||||||
}
|
}
|
||||||
|
|
||||||
configure<T extends CacheableObject>(request: RestRequest): void {
|
configure<T extends CacheableObject>(request: RestRequest): void {
|
||||||
@@ -86,15 +102,15 @@ export class RequestService {
|
|||||||
).subscribe((c) => isCached = c);
|
).subscribe((c) => isCached = c);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isPending = this.isPending(request.href);
|
const isPending = this.isPending(request.uuid);
|
||||||
|
|
||||||
return isCached || isPending;
|
return isCached || isPending;
|
||||||
}
|
}
|
||||||
|
|
||||||
private dispatchRequest(request: RestRequest) {
|
private dispatchRequest(request: RestRequest) {
|
||||||
this.store.dispatch(new RequestConfigureAction(request));
|
this.store.dispatch(new RequestConfigureAction(request));
|
||||||
this.store.dispatch(new RequestExecuteAction(request.href));
|
this.store.dispatch(new RequestExecuteAction(request.uuid));
|
||||||
this.trackRequestsOnTheirWayToTheStore(request.href);
|
this.trackRequestsOnTheirWayToTheStore(request.uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -104,13 +120,13 @@ export class RequestService {
|
|||||||
* This method will store the href of every request that gets configured in a local variable, and
|
* This method will store the href of every request that gets configured in a local variable, and
|
||||||
* remove it as soon as it can be found in the store.
|
* remove it as soon as it can be found in the store.
|
||||||
*/
|
*/
|
||||||
private trackRequestsOnTheirWayToTheStore(href: string) {
|
private trackRequestsOnTheirWayToTheStore(uuid: string) {
|
||||||
this.requestsOnTheirWayToTheStore = [...this.requestsOnTheirWayToTheStore, href];
|
this.requestsOnTheirWayToTheStore = [...this.requestsOnTheirWayToTheStore, uuid];
|
||||||
this.store.select(entryFromHrefSelector(href))
|
this.store.select(entryFromUUIDSelector(uuid))
|
||||||
.filter((re: RequestEntry) => hasValue(re))
|
.filter((re: RequestEntry) => hasValue(re))
|
||||||
.take(1)
|
.take(1)
|
||||||
.subscribe((re: RequestEntry) => {
|
.subscribe((re: RequestEntry) => {
|
||||||
this.requestsOnTheirWayToTheStore = this.requestsOnTheirWayToTheStore.filter((pendingHref: string) => pendingHref !== href)
|
this.requestsOnTheirWayToTheStore = this.requestsOnTheirWayToTheStore.filter((pendingUUID: string) => pendingUUID !== uuid)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
69
src/app/core/index/index.actions.ts
Normal file
69
src/app/core/index/index.actions.ts
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import { Action } from '@ngrx/store';
|
||||||
|
|
||||||
|
import { type } from '../../shared/ngrx/type';
|
||||||
|
import { IndexName } from './index.reducer';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of HrefIndexAction type definitions
|
||||||
|
*/
|
||||||
|
export const IndexActionTypes = {
|
||||||
|
ADD: type('dspace/core/index/ADD'),
|
||||||
|
REMOVE_BY_VALUE: type('dspace/core/index/REMOVE_BY_VALUE')
|
||||||
|
};
|
||||||
|
|
||||||
|
/* tslint:disable:max-classes-per-file */
|
||||||
|
/**
|
||||||
|
* An ngrx action to add an value to the index
|
||||||
|
*/
|
||||||
|
export class AddToIndexAction implements Action {
|
||||||
|
type = IndexActionTypes.ADD;
|
||||||
|
payload: {
|
||||||
|
name: IndexName;
|
||||||
|
value: string;
|
||||||
|
key: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new AddToIndexAction
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* the name of the index to add to
|
||||||
|
* @param key
|
||||||
|
* the key to add
|
||||||
|
* @param value
|
||||||
|
* the self link of the resource the key belongs to
|
||||||
|
*/
|
||||||
|
constructor(name: IndexName, key: string, value: string) {
|
||||||
|
this.payload = { name, key, value };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An ngrx action to remove an value from the index
|
||||||
|
*/
|
||||||
|
export class RemoveFromIndexByValueAction implements Action {
|
||||||
|
type = IndexActionTypes.REMOVE_BY_VALUE;
|
||||||
|
payload: {
|
||||||
|
name: IndexName,
|
||||||
|
value: string
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new RemoveFromIndexByValueAction
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* the name of the index to remove from
|
||||||
|
* @param value
|
||||||
|
* the value to remove the UUID for
|
||||||
|
*/
|
||||||
|
constructor(name: IndexName, value: string) {
|
||||||
|
this.payload = { name, value };
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
/* tslint:enable:max-classes-per-file */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A type to encompass all HrefIndexActions
|
||||||
|
*/
|
||||||
|
export type IndexAction = AddToIndexAction | RemoveFromIndexByValueAction;
|
61
src/app/core/index/index.effects.ts
Normal file
61
src/app/core/index/index.effects.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Effect, Actions } from '@ngrx/effects';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ObjectCacheActionTypes, AddToObjectCacheAction,
|
||||||
|
RemoveFromObjectCacheAction
|
||||||
|
} from '../cache/object-cache.actions';
|
||||||
|
import { RequestActionTypes, RequestConfigureAction } from '../data/request.actions';
|
||||||
|
import { RestRequestMethod } from '../data/request.models';
|
||||||
|
import { AddToIndexAction, RemoveFromIndexByValueAction } from './index.actions';
|
||||||
|
import { hasValue } from '../../shared/empty.util';
|
||||||
|
import { IndexName } from './index.reducer';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UUIDIndexEffects {
|
||||||
|
|
||||||
|
@Effect() addObject$ = this.actions$
|
||||||
|
.ofType(ObjectCacheActionTypes.ADD)
|
||||||
|
.filter((action: AddToObjectCacheAction) => hasValue(action.payload.objectToCache.uuid))
|
||||||
|
.map((action: AddToObjectCacheAction) => {
|
||||||
|
return new AddToIndexAction(
|
||||||
|
IndexName.OBJECT,
|
||||||
|
action.payload.objectToCache.uuid,
|
||||||
|
action.payload.objectToCache.self
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
@Effect() removeObject$ = this.actions$
|
||||||
|
.ofType(ObjectCacheActionTypes.REMOVE)
|
||||||
|
.map((action: RemoveFromObjectCacheAction) => {
|
||||||
|
return new RemoveFromIndexByValueAction(
|
||||||
|
IndexName.OBJECT,
|
||||||
|
action.payload
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
@Effect() addRequest$ = this.actions$
|
||||||
|
.ofType(RequestActionTypes.CONFIGURE)
|
||||||
|
.filter((action: RequestConfigureAction) => action.payload.method === RestRequestMethod.Get)
|
||||||
|
.map((action: RequestConfigureAction) => {
|
||||||
|
return new AddToIndexAction(
|
||||||
|
IndexName.REQUEST,
|
||||||
|
action.payload.href,
|
||||||
|
action.payload.uuid
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// @Effect() removeRequest$ = this.actions$
|
||||||
|
// .ofType(ObjectCacheActionTypes.REMOVE)
|
||||||
|
// .map((action: RemoveFromObjectCacheAction) => {
|
||||||
|
// return new RemoveFromIndexByValueAction(
|
||||||
|
// IndexName.OBJECT,
|
||||||
|
// action.payload
|
||||||
|
// );
|
||||||
|
// });
|
||||||
|
|
||||||
|
constructor(private actions$: Actions) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
58
src/app/core/index/index.reducer.spec.ts
Normal file
58
src/app/core/index/index.reducer.spec.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import * as deepFreeze from 'deep-freeze';
|
||||||
|
|
||||||
|
import { IndexName, indexReducer, IndexState } from './index.reducer';
|
||||||
|
import { AddToIndexAction, RemoveFromIndexByValueAction } from './index.actions';
|
||||||
|
|
||||||
|
class NullAction extends AddToIndexAction {
|
||||||
|
type = null;
|
||||||
|
payload = null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(null, null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('requestReducer', () => {
|
||||||
|
const key1 = '567a639f-f5ff-4126-807c-b7d0910808c8';
|
||||||
|
const key2 = '1911e8a4-6939-490c-b58b-a5d70f8d91fb';
|
||||||
|
const val1 = 'https://dspace7.4science.it/dspace-spring-rest/api/core/items/567a639f-f5ff-4126-807c-b7d0910808c8';
|
||||||
|
const val2 = 'https://dspace7.4science.it/dspace-spring-rest/api/core/items/1911e8a4-6939-490c-b58b-a5d70f8d91fb';
|
||||||
|
const testState: IndexState = {
|
||||||
|
[IndexName.OBJECT]: {
|
||||||
|
[key1]: val1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
deepFreeze(testState);
|
||||||
|
|
||||||
|
it('should return the current state when no valid actions have been made', () => {
|
||||||
|
const action = new NullAction();
|
||||||
|
const newState = indexReducer(testState, action);
|
||||||
|
|
||||||
|
expect(newState).toEqual(testState);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should start with an empty state', () => {
|
||||||
|
const action = new NullAction();
|
||||||
|
const initialState = indexReducer(undefined, action);
|
||||||
|
|
||||||
|
expect(initialState).toEqual(Object.create(null));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add the \'key\' with the corresponding \'value\' to the correct substate, in response to an ADD action', () => {
|
||||||
|
const state = testState;
|
||||||
|
|
||||||
|
const action = new AddToIndexAction(IndexName.REQUEST, key2, val2);
|
||||||
|
const newState = indexReducer(state, action);
|
||||||
|
|
||||||
|
expect(newState[IndexName.REQUEST][key2]).toEqual(val2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove the given \'value\' from its corresponding \'key\' in the correct substate, in response to a REMOVE_BY_VALUE action', () => {
|
||||||
|
const state = testState;
|
||||||
|
|
||||||
|
const action = new RemoveFromIndexByValueAction(IndexName.OBJECT, val1);
|
||||||
|
const newState = indexReducer(state, action);
|
||||||
|
|
||||||
|
expect(newState[IndexName.OBJECT][key1]).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
62
src/app/core/index/index.reducer.ts
Normal file
62
src/app/core/index/index.reducer.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import {
|
||||||
|
IndexAction,
|
||||||
|
IndexActionTypes,
|
||||||
|
AddToIndexAction,
|
||||||
|
RemoveFromIndexByValueAction
|
||||||
|
} from './index.actions';
|
||||||
|
|
||||||
|
export enum IndexName {
|
||||||
|
OBJECT = 'object/uuid-to-self-link',
|
||||||
|
REQUEST = 'get-request/href-to-uuid'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IndexState {
|
||||||
|
// TODO this should be `[name in IndexName]: {` but that's currently broken,
|
||||||
|
// see https://github.com/Microsoft/TypeScript/issues/13042
|
||||||
|
[name: string]: {
|
||||||
|
[key: string]: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Object.create(null) ensures the object has no default js properties (e.g. `__proto__`)
|
||||||
|
const initialState: IndexState = Object.create(null);
|
||||||
|
|
||||||
|
export function indexReducer(state = initialState, action: IndexAction): IndexState {
|
||||||
|
switch (action.type) {
|
||||||
|
|
||||||
|
case IndexActionTypes.ADD: {
|
||||||
|
return addToIndex(state, action as AddToIndexAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
case IndexActionTypes.REMOVE_BY_VALUE: {
|
||||||
|
return removeFromIndexByValue(state, action as RemoveFromIndexByValueAction)
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addToIndex(state: IndexState, action: AddToIndexAction): IndexState {
|
||||||
|
const subState = state[action.payload.name];
|
||||||
|
const newSubState = Object.assign({}, subState, {
|
||||||
|
[action.payload.key]: action.payload.value
|
||||||
|
});
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
[action.payload.name]: newSubState
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeFromIndexByValue(state: IndexState, action: RemoveFromIndexByValueAction): IndexState {
|
||||||
|
const subState = state[action.payload.name];
|
||||||
|
const newSubState = Object.create(null);
|
||||||
|
for (const value in subState) {
|
||||||
|
if (subState[value] !== action.payload.value) {
|
||||||
|
newSubState[value] = subState[value];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
[action.payload.name]: newSubState
|
||||||
|
});
|
||||||
|
}
|
@@ -1,60 +0,0 @@
|
|||||||
import { Action } from '@ngrx/store';
|
|
||||||
|
|
||||||
import { type } from '../../shared/ngrx/type';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The list of HrefIndexAction type definitions
|
|
||||||
*/
|
|
||||||
export const UUIDIndexActionTypes = {
|
|
||||||
ADD: type('dspace/core/index/uuid/ADD'),
|
|
||||||
REMOVE_HREF: type('dspace/core/index/uuid/REMOVE_HREF')
|
|
||||||
};
|
|
||||||
|
|
||||||
/* tslint:disable:max-classes-per-file */
|
|
||||||
/**
|
|
||||||
* An ngrx action to add an href to the index
|
|
||||||
*/
|
|
||||||
export class AddToUUIDIndexAction implements Action {
|
|
||||||
type = UUIDIndexActionTypes.ADD;
|
|
||||||
payload: {
|
|
||||||
href: string;
|
|
||||||
uuid: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new AddToUUIDIndexAction
|
|
||||||
*
|
|
||||||
* @param uuid
|
|
||||||
* the uuid to add
|
|
||||||
* @param href
|
|
||||||
* the self link of the resource the uuid belongs to
|
|
||||||
*/
|
|
||||||
constructor(uuid: string, href: string) {
|
|
||||||
this.payload = { href, uuid };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An ngrx action to remove an href from the index
|
|
||||||
*/
|
|
||||||
export class RemoveHrefFromUUIDIndexAction implements Action {
|
|
||||||
type = UUIDIndexActionTypes.REMOVE_HREF;
|
|
||||||
payload: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new RemoveHrefFromUUIDIndexAction
|
|
||||||
*
|
|
||||||
* @param href
|
|
||||||
* the href to remove the UUID for
|
|
||||||
*/
|
|
||||||
constructor(href: string) {
|
|
||||||
this.payload = href;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
/* tslint:enable:max-classes-per-file */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A type to encompass all HrefIndexActions
|
|
||||||
*/
|
|
||||||
export type UUIDIndexAction = AddToUUIDIndexAction | RemoveHrefFromUUIDIndexAction;
|
|
@@ -1,34 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { Effect, Actions } from '@ngrx/effects';
|
|
||||||
|
|
||||||
import {
|
|
||||||
ObjectCacheActionTypes, AddToObjectCacheAction,
|
|
||||||
RemoveFromObjectCacheAction
|
|
||||||
} from '../cache/object-cache.actions';
|
|
||||||
import { AddToUUIDIndexAction, RemoveHrefFromUUIDIndexAction } from './uuid-index.actions';
|
|
||||||
import { hasValue } from '../../shared/empty.util';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class UUIDIndexEffects {
|
|
||||||
|
|
||||||
@Effect() add$ = this.actions$
|
|
||||||
.ofType(ObjectCacheActionTypes.ADD)
|
|
||||||
.filter((action: AddToObjectCacheAction) => hasValue(action.payload.objectToCache.uuid))
|
|
||||||
.map((action: AddToObjectCacheAction) => {
|
|
||||||
return new AddToUUIDIndexAction(
|
|
||||||
action.payload.objectToCache.uuid,
|
|
||||||
action.payload.objectToCache.self
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
@Effect() remove$ = this.actions$
|
|
||||||
.ofType(ObjectCacheActionTypes.REMOVE)
|
|
||||||
.map((action: RemoveFromObjectCacheAction) => {
|
|
||||||
return new RemoveHrefFromUUIDIndexAction(action.payload);
|
|
||||||
});
|
|
||||||
|
|
||||||
constructor(private actions$: Actions) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,56 +0,0 @@
|
|||||||
import * as deepFreeze from 'deep-freeze';
|
|
||||||
|
|
||||||
import { uuidIndexReducer, UUIDIndexState } from './uuid-index.reducer';
|
|
||||||
import { AddToUUIDIndexAction, RemoveHrefFromUUIDIndexAction } from './uuid-index.actions';
|
|
||||||
|
|
||||||
class NullAction extends AddToUUIDIndexAction {
|
|
||||||
type = null;
|
|
||||||
payload = null;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super(null, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('requestReducer', () => {
|
|
||||||
const link1 = 'https://dspace7.4science.it/dspace-spring-rest/api/core/items/567a639f-f5ff-4126-807c-b7d0910808c8';
|
|
||||||
const link2 = 'https://dspace7.4science.it/dspace-spring-rest/api/core/items/1911e8a4-6939-490c-b58b-a5d70f8d91fb';
|
|
||||||
const uuid1 = '567a639f-f5ff-4126-807c-b7d0910808c8';
|
|
||||||
const uuid2 = '1911e8a4-6939-490c-b58b-a5d70f8d91fb';
|
|
||||||
const testState: UUIDIndexState = {
|
|
||||||
[uuid1]: link1
|
|
||||||
};
|
|
||||||
deepFreeze(testState);
|
|
||||||
|
|
||||||
it('should return the current state when no valid actions have been made', () => {
|
|
||||||
const action = new NullAction();
|
|
||||||
const newState = uuidIndexReducer(testState, action);
|
|
||||||
|
|
||||||
expect(newState).toEqual(testState);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should start with an empty state', () => {
|
|
||||||
const action = new NullAction();
|
|
||||||
const initialState = uuidIndexReducer(undefined, action);
|
|
||||||
|
|
||||||
expect(initialState).toEqual(Object.create(null));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add the \'uuid\' with the corresponding \'href\' to the state, in response to an ADD action', () => {
|
|
||||||
const state = testState;
|
|
||||||
|
|
||||||
const action = new AddToUUIDIndexAction(uuid2, link2);
|
|
||||||
const newState = uuidIndexReducer(state, action);
|
|
||||||
|
|
||||||
expect(newState[uuid2]).toEqual(link2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should remove the given \'href\' from its corresponding \'uuid\' in the state, in response to a REMOVE_HREF action', () => {
|
|
||||||
const state = testState;
|
|
||||||
|
|
||||||
const action = new RemoveHrefFromUUIDIndexAction(link1);
|
|
||||||
const newState = uuidIndexReducer(state, action);
|
|
||||||
|
|
||||||
expect(newState[uuid1]).toBeUndefined();
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,46 +0,0 @@
|
|||||||
import {
|
|
||||||
UUIDIndexAction,
|
|
||||||
UUIDIndexActionTypes,
|
|
||||||
AddToUUIDIndexAction,
|
|
||||||
RemoveHrefFromUUIDIndexAction
|
|
||||||
} from './uuid-index.actions';
|
|
||||||
|
|
||||||
export interface UUIDIndexState {
|
|
||||||
[uuid: string]: string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Object.create(null) ensures the object has no default js properties (e.g. `__proto__`)
|
|
||||||
const initialState: UUIDIndexState = Object.create(null);
|
|
||||||
|
|
||||||
export function uuidIndexReducer(state = initialState, action: UUIDIndexAction): UUIDIndexState {
|
|
||||||
switch (action.type) {
|
|
||||||
|
|
||||||
case UUIDIndexActionTypes.ADD: {
|
|
||||||
return addToUUIDIndex(state, action as AddToUUIDIndexAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
case UUIDIndexActionTypes.REMOVE_HREF: {
|
|
||||||
return removeHrefFromUUIDIndex(state, action as RemoveHrefFromUUIDIndexAction)
|
|
||||||
}
|
|
||||||
|
|
||||||
default: {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function addToUUIDIndex(state: UUIDIndexState, action: AddToUUIDIndexAction): UUIDIndexState {
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
[action.payload.uuid]: action.payload.href
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeHrefFromUUIDIndex(state: UUIDIndexState, action: RemoveHrefFromUUIDIndexAction): UUIDIndexState {
|
|
||||||
const newState = Object.create(null);
|
|
||||||
for (const uuid in state) {
|
|
||||||
if (state[uuid] !== action.payload) {
|
|
||||||
newState[uuid] = state[uuid];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newState;
|
|
||||||
}
|
|
@@ -1,5 +1,6 @@
|
|||||||
import { cold, hot } from 'jasmine-marbles';
|
import { cold, hot } from 'jasmine-marbles';
|
||||||
import { GlobalConfig } from '../../../config/global-config.interface';
|
import { GlobalConfig } from '../../../config/global-config.interface';
|
||||||
|
import { initMockRequestService } from '../../shared/mocks/mock-request.service';
|
||||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||||
import { RootEndpointRequest } from '../data/request.models';
|
import { RootEndpointRequest } from '../data/request.models';
|
||||||
import { RequestService } from '../data/request.service';
|
import { RequestService } from '../data/request.service';
|
||||||
@@ -38,7 +39,7 @@ describe('HALEndpointService', () => {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
requestService = jasmine.createSpyObj('requestService', ['configure']);
|
requestService = initMockRequestService();
|
||||||
|
|
||||||
envConfig = {
|
envConfig = {
|
||||||
rest: { baseUrl: 'https://rest.api/' }
|
rest: { baseUrl: 'https://rest.api/' }
|
||||||
@@ -53,7 +54,7 @@ describe('HALEndpointService', () => {
|
|||||||
|
|
||||||
it('should configure a new RootEndpointRequest', () => {
|
it('should configure a new RootEndpointRequest', () => {
|
||||||
(service as any).getEndpointMap();
|
(service as any).getEndpointMap();
|
||||||
const expected = new RootEndpointRequest(envConfig);
|
const expected = new RootEndpointRequest(requestService.generateRequestId(), envConfig);
|
||||||
expect(requestService.configure).toHaveBeenCalledWith(expected);
|
expect(requestService.configure).toHaveBeenCalledWith(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -14,7 +14,7 @@ export abstract class HALEndpointService {
|
|||||||
protected abstract EnvConfig: GlobalConfig;
|
protected abstract EnvConfig: GlobalConfig;
|
||||||
|
|
||||||
protected getEndpointMap(): Observable<EndpointMap> {
|
protected getEndpointMap(): Observable<EndpointMap> {
|
||||||
const request = new RootEndpointRequest(this.EnvConfig);
|
const request = new RootEndpointRequest(this.requestService.generateRequestId(), this.EnvConfig);
|
||||||
this.requestService.configure(request);
|
this.requestService.configure(request);
|
||||||
return this.responseCache.get(request.href)
|
return this.responseCache.get(request.href)
|
||||||
.map((entry: ResponseCacheEntry) => entry.response)
|
.map((entry: ResponseCacheEntry) => entry.response)
|
||||||
|
@@ -1,13 +1,17 @@
|
|||||||
import { createSelector, MemoizedSelector } from '@ngrx/store';
|
import { createSelector, MemoizedSelector } from '@ngrx/store';
|
||||||
import { coreSelector, CoreState } from '../core.reducers';
|
import { hasNoValue, isEmpty } from '../../shared/empty.util';
|
||||||
import { hasValue } from '../../shared/empty.util';
|
|
||||||
|
|
||||||
export function keySelector<T>(subState: string, key: string): MemoizedSelector<CoreState, T> {
|
export function pathSelector<From, To>(selector: MemoizedSelector<any, From>, ...path: string[]): MemoizedSelector<any, To> {
|
||||||
return createSelector(coreSelector, (state: CoreState) => {
|
return createSelector(selector, (state: any) => getSubState(state, path));
|
||||||
if (hasValue(state[subState])) {
|
}
|
||||||
return state[subState][key];
|
|
||||||
} else {
|
function getSubState(state: any, path: string[]) {
|
||||||
return undefined;
|
const current = path[0];
|
||||||
}
|
const remainingPath = path.slice(1);
|
||||||
});
|
const subState = state[current];
|
||||||
|
if (hasNoValue(subState) || isEmpty(remainingPath)) {
|
||||||
|
return subState;
|
||||||
|
} else {
|
||||||
|
return getSubState(subState, remainingPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
9
src/app/core/shared/uuid.service.ts
Normal file
9
src/app/core/shared/uuid.service.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import * as uuidv4 from 'uuid/v4';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UUIDService {
|
||||||
|
generate(): string {
|
||||||
|
return uuidv4();
|
||||||
|
}
|
||||||
|
}
|
8
src/app/shared/mocks/mock-request.service.ts
Normal file
8
src/app/shared/mocks/mock-request.service.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { RequestService } from '../../core/data/request.service';
|
||||||
|
|
||||||
|
export function initMockRequestService(): RequestService {
|
||||||
|
return jasmine.createSpyObj('requestService', {
|
||||||
|
configure: () => false,
|
||||||
|
generateRequestId: () => 'clients/b186e8ce-e99c-4183-bc9a-42b4821bdb78'
|
||||||
|
});
|
||||||
|
}
|
70
yarn.lock
70
yarn.lock
@@ -225,6 +225,12 @@
|
|||||||
version "0.5.1"
|
version "0.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/source-map/-/source-map-0.5.1.tgz#7e74db5d06ab373a712356eebfaea2fad0ea2367"
|
resolved "https://registry.yarnpkg.com/@types/source-map/-/source-map-0.5.1.tgz#7e74db5d06ab373a712356eebfaea2fad0ea2367"
|
||||||
|
|
||||||
|
"@types/key@^3.4.3":
|
||||||
|
version "3.4.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/key/-/key-3.4.3.tgz#121ace265f5569ce40f4f6d0ff78a338c732a754"
|
||||||
|
dependencies:
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/webfontloader@1.6.29":
|
"@types/webfontloader@1.6.29":
|
||||||
version "1.6.29"
|
version "1.6.29"
|
||||||
resolved "https://registry.yarnpkg.com/@types/webfontloader/-/webfontloader-1.6.29.tgz#c6b5f6eb8ca31d0aae6b02b6c1300349dd93ea8e"
|
resolved "https://registry.yarnpkg.com/@types/webfontloader/-/webfontloader-1.6.29.tgz#c6b5f6eb8ca31d0aae6b02b6c1300349dd93ea8e"
|
||||||
@@ -1009,7 +1015,7 @@ cache-base@^1.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
collection-visit "^1.0.0"
|
collection-visit "^1.0.0"
|
||||||
component-emitter "^1.2.1"
|
component-emitter "^1.2.1"
|
||||||
get-value "^2.0.6"
|
getByUUID-value "^2.0.6"
|
||||||
has-value "^1.0.0"
|
has-value "^1.0.0"
|
||||||
isobject "^3.0.1"
|
isobject "^3.0.1"
|
||||||
set-value "^2.0.0"
|
set-value "^2.0.0"
|
||||||
@@ -1751,7 +1757,7 @@ dateformat@^1.0.11, dateformat@^1.0.6:
|
|||||||
version "1.0.12"
|
version "1.0.12"
|
||||||
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-1.0.12.tgz#9f124b67594c937ff706932e4a642cca8dbbfee9"
|
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-1.0.12.tgz#9f124b67594c937ff706932e4a642cca8dbbfee9"
|
||||||
dependencies:
|
dependencies:
|
||||||
get-stdin "^4.0.1"
|
getByUUID-stdin "^4.0.1"
|
||||||
meow "^3.3.0"
|
meow "^3.3.0"
|
||||||
|
|
||||||
debug@2, debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.6, debug@^2.6.8:
|
debug@2, debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.6, debug@^2.6.8:
|
||||||
@@ -2317,7 +2323,7 @@ execa@^0.7.0:
|
|||||||
resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777"
|
resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777"
|
||||||
dependencies:
|
dependencies:
|
||||||
cross-spawn "^5.0.1"
|
cross-spawn "^5.0.1"
|
||||||
get-stream "^3.0.0"
|
getByUUID-stream "^3.0.0"
|
||||||
is-stream "^1.1.0"
|
is-stream "^1.1.0"
|
||||||
npm-run-path "^2.0.0"
|
npm-run-path "^2.0.0"
|
||||||
p-finally "^1.0.0"
|
p-finally "^1.0.0"
|
||||||
@@ -2731,25 +2737,25 @@ generate-object-property@^1.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-property "^1.0.0"
|
is-property "^1.0.0"
|
||||||
|
|
||||||
get-caller-file@^1.0.1:
|
getByUUID-caller-file@^1.0.1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5"
|
resolved "https://registry.yarnpkg.com/getByUUID-caller-file/-/getByUUID-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5"
|
||||||
|
|
||||||
get-stdin@^4.0.1:
|
getByUUID-stdin@^4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
|
resolved "https://registry.yarnpkg.com/getByUUID-stdin/-/getByUUID-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
|
||||||
|
|
||||||
get-stdin@^5.0.1:
|
getByUUID-stdin@^5.0.1:
|
||||||
version "5.0.1"
|
version "5.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398"
|
resolved "https://registry.yarnpkg.com/getByUUID-stdin/-/getByUUID-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398"
|
||||||
|
|
||||||
get-stream@^3.0.0:
|
getByUUID-stream@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
|
resolved "https://registry.yarnpkg.com/getByUUID-stream/-/getByUUID-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
|
||||||
|
|
||||||
get-value@^2.0.3, get-value@^2.0.6:
|
getByUUID-value@^2.0.3, getByUUID-value@^2.0.6:
|
||||||
version "2.0.6"
|
version "2.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
|
resolved "https://registry.yarnpkg.com/getByUUID-value/-/getByUUID-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
|
||||||
|
|
||||||
getpass@^0.1.1:
|
getpass@^0.1.1:
|
||||||
version "0.1.7"
|
version "0.1.7"
|
||||||
@@ -2842,7 +2848,7 @@ got@^6.7.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
create-error-class "^3.0.0"
|
create-error-class "^3.0.0"
|
||||||
duplexer3 "^0.1.4"
|
duplexer3 "^0.1.4"
|
||||||
get-stream "^3.0.0"
|
getByUUID-stream "^3.0.0"
|
||||||
is-redirect "^1.0.0"
|
is-redirect "^1.0.0"
|
||||||
is-retry-allowed "^1.0.0"
|
is-retry-allowed "^1.0.0"
|
||||||
is-stream "^1.0.0"
|
is-stream "^1.0.0"
|
||||||
@@ -2974,7 +2980,7 @@ has-value@^0.3.1:
|
|||||||
version "0.3.1"
|
version "0.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f"
|
resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f"
|
||||||
dependencies:
|
dependencies:
|
||||||
get-value "^2.0.3"
|
getByUUID-value "^2.0.3"
|
||||||
has-values "^0.1.4"
|
has-values "^0.1.4"
|
||||||
isobject "^2.0.0"
|
isobject "^2.0.0"
|
||||||
|
|
||||||
@@ -2982,7 +2988,7 @@ has-value@^1.0.0:
|
|||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177"
|
resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177"
|
||||||
dependencies:
|
dependencies:
|
||||||
get-value "^2.0.6"
|
getByUUID-value "^2.0.6"
|
||||||
has-values "^1.0.0"
|
has-values "^1.0.0"
|
||||||
isobject "^3.0.0"
|
isobject "^3.0.0"
|
||||||
|
|
||||||
@@ -4652,7 +4658,7 @@ node-sass@4.5.3:
|
|||||||
chalk "^1.1.1"
|
chalk "^1.1.1"
|
||||||
cross-spawn "^3.0.0"
|
cross-spawn "^3.0.0"
|
||||||
gaze "^1.0.0"
|
gaze "^1.0.0"
|
||||||
get-stdin "^4.0.1"
|
getByUUID-stdin "^4.0.1"
|
||||||
glob "^7.0.3"
|
glob "^7.0.3"
|
||||||
in-publish "^2.0.0"
|
in-publish "^2.0.0"
|
||||||
lodash.assign "^4.2.0"
|
lodash.assign "^4.2.0"
|
||||||
@@ -5223,7 +5229,7 @@ postcss-cli@4.1.1:
|
|||||||
chokidar "^1.6.1"
|
chokidar "^1.6.1"
|
||||||
dependency-graph "^0.5.0"
|
dependency-graph "^0.5.0"
|
||||||
fs-extra "^4.0.1"
|
fs-extra "^4.0.1"
|
||||||
get-stdin "^5.0.1"
|
getByUUID-stdin "^5.0.1"
|
||||||
globby "^6.1.0"
|
globby "^6.1.0"
|
||||||
ora "^1.1.0"
|
ora "^1.1.0"
|
||||||
postcss "^6.0.1"
|
postcss "^6.0.1"
|
||||||
@@ -5802,7 +5808,7 @@ protractor-istanbul-plugin@2.0.0:
|
|||||||
fs-extra "^0.22.1"
|
fs-extra "^0.22.1"
|
||||||
merge "^1.2.0"
|
merge "^1.2.0"
|
||||||
q "^1.4.1"
|
q "^1.4.1"
|
||||||
uuid "^2.0.1"
|
key "^2.0.1"
|
||||||
|
|
||||||
protractor@5.1.2:
|
protractor@5.1.2:
|
||||||
version "5.1.2"
|
version "5.1.2"
|
||||||
@@ -6224,7 +6230,7 @@ request@2, request@^2.78.0, request@^2.79.0:
|
|||||||
stringstream "~0.0.5"
|
stringstream "~0.0.5"
|
||||||
tough-cookie "~2.3.3"
|
tough-cookie "~2.3.3"
|
||||||
tunnel-agent "^0.6.0"
|
tunnel-agent "^0.6.0"
|
||||||
uuid "^3.1.0"
|
key "^3.1.0"
|
||||||
|
|
||||||
request@2.79.0:
|
request@2.79.0:
|
||||||
version "2.79.0"
|
version "2.79.0"
|
||||||
@@ -6249,7 +6255,7 @@ request@2.79.0:
|
|||||||
stringstream "~0.0.4"
|
stringstream "~0.0.4"
|
||||||
tough-cookie "~2.3.0"
|
tough-cookie "~2.3.0"
|
||||||
tunnel-agent "~0.4.1"
|
tunnel-agent "~0.4.1"
|
||||||
uuid "^3.0.0"
|
key "^3.0.0"
|
||||||
|
|
||||||
request@2.81.0, request@~2.81.0:
|
request@2.81.0, request@~2.81.0:
|
||||||
version "2.81.0"
|
version "2.81.0"
|
||||||
@@ -6276,7 +6282,7 @@ request@2.81.0, request@~2.81.0:
|
|||||||
stringstream "~0.0.4"
|
stringstream "~0.0.4"
|
||||||
tough-cookie "~2.3.0"
|
tough-cookie "~2.3.0"
|
||||||
tunnel-agent "^0.6.0"
|
tunnel-agent "^0.6.0"
|
||||||
uuid "^3.0.0"
|
key "^3.0.0"
|
||||||
|
|
||||||
require-directory@^2.1.1:
|
require-directory@^2.1.1:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
@@ -6783,7 +6789,7 @@ sockjs@0.3.18:
|
|||||||
resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.18.tgz#d9b289316ca7df77595ef299e075f0f937eb4207"
|
resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.18.tgz#d9b289316ca7df77595ef299e075f0f937eb4207"
|
||||||
dependencies:
|
dependencies:
|
||||||
faye-websocket "^0.10.0"
|
faye-websocket "^0.10.0"
|
||||||
uuid "^2.0.2"
|
key "^2.0.2"
|
||||||
|
|
||||||
sort-keys@^1.0.0:
|
sort-keys@^1.0.0:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
@@ -7056,7 +7062,7 @@ strip-indent@^1.0.1:
|
|||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2"
|
resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2"
|
||||||
dependencies:
|
dependencies:
|
||||||
get-stdin "^4.0.1"
|
getByUUID-stdin "^4.0.1"
|
||||||
|
|
||||||
strip-json-comments@^2.0.0, strip-json-comments@~2.0.1:
|
strip-json-comments@^2.0.0, strip-json-comments@~2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
@@ -7459,7 +7465,7 @@ union-value@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4"
|
resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4"
|
||||||
dependencies:
|
dependencies:
|
||||||
arr-union "^3.1.0"
|
arr-union "^3.1.0"
|
||||||
get-value "^2.0.6"
|
getByUUID-value "^2.0.6"
|
||||||
is-extendable "^0.1.1"
|
is-extendable "^0.1.1"
|
||||||
set-value "^0.4.3"
|
set-value "^0.4.3"
|
||||||
|
|
||||||
@@ -7604,13 +7610,13 @@ utils-merge@1.0.1:
|
|||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
||||||
|
|
||||||
uuid@^2.0.1, uuid@^2.0.2:
|
key@^2.0.1, key@^2.0.2:
|
||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a"
|
resolved "https://registry.yarnpkg.com/key/-/key-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a"
|
||||||
|
|
||||||
uuid@^3.0.0, uuid@^3.1.0:
|
key@^3.0.0, key@^3.1.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04"
|
resolved "https://registry.yarnpkg.com/key/-/key-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04"
|
||||||
|
|
||||||
v8flags@^3.0.0:
|
v8flags@^3.0.0:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
@@ -7999,7 +8005,7 @@ yargs@^6.6.0:
|
|||||||
camelcase "^3.0.0"
|
camelcase "^3.0.0"
|
||||||
cliui "^3.2.0"
|
cliui "^3.2.0"
|
||||||
decamelize "^1.1.1"
|
decamelize "^1.1.1"
|
||||||
get-caller-file "^1.0.1"
|
getByUUID-caller-file "^1.0.1"
|
||||||
os-locale "^1.4.0"
|
os-locale "^1.4.0"
|
||||||
read-pkg-up "^1.0.1"
|
read-pkg-up "^1.0.1"
|
||||||
require-directory "^2.1.1"
|
require-directory "^2.1.1"
|
||||||
@@ -8017,7 +8023,7 @@ yargs@^7.0.0:
|
|||||||
camelcase "^3.0.0"
|
camelcase "^3.0.0"
|
||||||
cliui "^3.2.0"
|
cliui "^3.2.0"
|
||||||
decamelize "^1.1.1"
|
decamelize "^1.1.1"
|
||||||
get-caller-file "^1.0.1"
|
getByUUID-caller-file "^1.0.1"
|
||||||
os-locale "^1.4.0"
|
os-locale "^1.4.0"
|
||||||
read-pkg-up "^1.0.1"
|
read-pkg-up "^1.0.1"
|
||||||
require-directory "^2.1.1"
|
require-directory "^2.1.1"
|
||||||
@@ -8035,7 +8041,7 @@ yargs@^8.0.1, yargs@^8.0.2:
|
|||||||
camelcase "^4.1.0"
|
camelcase "^4.1.0"
|
||||||
cliui "^3.2.0"
|
cliui "^3.2.0"
|
||||||
decamelize "^1.1.1"
|
decamelize "^1.1.1"
|
||||||
get-caller-file "^1.0.1"
|
getByUUID-caller-file "^1.0.1"
|
||||||
os-locale "^2.0.0"
|
os-locale "^2.0.0"
|
||||||
read-pkg-up "^2.0.0"
|
read-pkg-up "^2.0.0"
|
||||||
require-directory "^2.1.1"
|
require-directory "^2.1.1"
|
||||||
|
Reference in New Issue
Block a user