mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-08 10:34:15 +00:00
implemented applying of a patch
This commit is contained in:
@@ -20,7 +20,7 @@ export class AuthResponseParsingService extends BaseResponseParsingService imple
|
|||||||
protected toCache = false;
|
protected toCache = false;
|
||||||
|
|
||||||
constructor(@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
|
constructor(@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
|
||||||
protected objectCache: ObjectCacheService,) {
|
protected objectCache: ObjectCacheService) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -26,7 +26,9 @@ describe('objectCacheReducer', () => {
|
|||||||
},
|
},
|
||||||
timeAdded: new Date().getTime(),
|
timeAdded: new Date().getTime(),
|
||||||
msToLive: 900000,
|
msToLive: 900000,
|
||||||
requestHref: selfLink1
|
requestHref: selfLink1,
|
||||||
|
patches: [],
|
||||||
|
isDirty: false
|
||||||
},
|
},
|
||||||
[selfLink2]: {
|
[selfLink2]: {
|
||||||
data: {
|
data: {
|
||||||
@@ -35,7 +37,9 @@ describe('objectCacheReducer', () => {
|
|||||||
},
|
},
|
||||||
timeAdded: new Date().getTime(),
|
timeAdded: new Date().getTime(),
|
||||||
msToLive: 900000,
|
msToLive: 900000,
|
||||||
requestHref: selfLink2
|
requestHref: selfLink2,
|
||||||
|
patches: [],
|
||||||
|
isDirty: false
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
deepFreeze(testState);
|
deepFreeze(testState);
|
||||||
|
4
src/app/core/cache/object-cache.reducer.ts
vendored
4
src/app/core/cache/object-cache.reducer.ts
vendored
@@ -112,13 +112,15 @@ export function objectCacheReducer(state = initialState, action: ObjectCacheActi
|
|||||||
* the new state, with the object added, or overwritten.
|
* the new state, with the object added, or overwritten.
|
||||||
*/
|
*/
|
||||||
function addToObjectCache(state: ObjectCacheState, action: AddToObjectCacheAction): ObjectCacheState {
|
function addToObjectCache(state: ObjectCacheState, action: AddToObjectCacheAction): ObjectCacheState {
|
||||||
|
const existing = state[action.payload.objectToCache.self];
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
[action.payload.objectToCache.self]: {
|
[action.payload.objectToCache.self]: {
|
||||||
data: action.payload.objectToCache,
|
data: action.payload.objectToCache,
|
||||||
timeAdded: action.payload.timeAdded,
|
timeAdded: action.payload.timeAdded,
|
||||||
msToLive: action.payload.msToLive,
|
msToLive: action.payload.msToLive,
|
||||||
requestHref: action.payload.requestHref,
|
requestHref: action.payload.requestHref,
|
||||||
isDirty: false
|
isDirty: false,
|
||||||
|
patches: (hasValue(existing) ? existing.patches : [])
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
3
src/app/core/cache/object-cache.service.ts
vendored
3
src/app/core/cache/object-cache.service.ts
vendored
@@ -90,7 +90,6 @@ export class ObjectCacheService {
|
|||||||
getBySelfLink<T extends NormalizedObject>(selfLink: string): Observable<T> {
|
getBySelfLink<T extends NormalizedObject>(selfLink: string): Observable<T> {
|
||||||
return this.getEntry(selfLink).pipe(
|
return this.getEntry(selfLink).pipe(
|
||||||
map((entry: ObjectCacheEntry) => {
|
map((entry: ObjectCacheEntry) => {
|
||||||
// flatten two dimensional array
|
|
||||||
const flatPatch: Operation[] = [].concat(...entry.patches);
|
const flatPatch: Operation[] = [].concat(...entry.patches);
|
||||||
const patchedData = applyPatch(entry.data, flatPatch).newDocument;
|
const patchedData = applyPatch(entry.data, flatPatch).newDocument;
|
||||||
return Object.assign({}, entry, { data: patchedData });
|
return Object.assign({}, entry, { data: patchedData });
|
||||||
@@ -218,7 +217,7 @@ export class ObjectCacheService {
|
|||||||
* @param {Operation[]} patch
|
* @param {Operation[]} patch
|
||||||
* list of operations to perform
|
* list of operations to perform
|
||||||
*/
|
*/
|
||||||
private addPatch(uuid: string, patch: Operation[]) {
|
public addPatch(uuid: string, patch: Operation[]) {
|
||||||
this.store.dispatch(new AddPatchObjectCacheAction(uuid, patch));
|
this.store.dispatch(new AddPatchObjectCacheAction(uuid, patch));
|
||||||
this.store.dispatch(new AddToSSBAction(uuid, RestRequestMethod.Patch));
|
this.store.dispatch(new AddToSSBAction(uuid, RestRequestMethod.Patch));
|
||||||
}
|
}
|
||||||
|
70
src/app/core/cache/server-sync-buffer.effects.ts
vendored
70
src/app/core/cache/server-sync-buffer.effects.ts
vendored
@@ -1,4 +1,4 @@
|
|||||||
import { delay, exhaustMap, filter, first, map } from 'rxjs/operators';
|
import { delay, exhaustMap, first, map, switchMap, tap } from 'rxjs/operators';
|
||||||
import { Inject, Injectable } from '@angular/core';
|
import { Inject, Injectable } from '@angular/core';
|
||||||
import { Actions, Effect, ofType } from '@ngrx/effects';
|
import { Actions, Effect, ofType } from '@ngrx/effects';
|
||||||
import {
|
import {
|
||||||
@@ -10,43 +10,77 @@ import {
|
|||||||
import { GLOBAL_CONFIG } from '../../../config';
|
import { GLOBAL_CONFIG } from '../../../config';
|
||||||
import { GlobalConfig } from '../../../config/global-config.interface';
|
import { GlobalConfig } from '../../../config/global-config.interface';
|
||||||
import { CoreState } from '../core.reducers';
|
import { CoreState } from '../core.reducers';
|
||||||
import { select, Store } from '@ngrx/store';
|
import { Action, select, Store } from '@ngrx/store';
|
||||||
import { ServerSyncBufferEntry, ServerSyncBufferState } from './server-sync-buffer.reducer';
|
import { ServerSyncBufferEntry, ServerSyncBufferState } from './server-sync-buffer.reducer';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf, combineLatest as observableCombineLatest } from 'rxjs';
|
||||||
import { RequestService } from '../data/request.service';
|
import { RequestService } from '../data/request.service';
|
||||||
import { PutRequest } from '../data/request.models';
|
import { PutRequest, RestRequestMethod } from '../data/request.models';
|
||||||
|
import { ObjectCacheService } from './object-cache.service';
|
||||||
|
import { ApplyPatchObjectCacheAction } from './object-cache.actions';
|
||||||
|
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
|
||||||
|
import { GenericConstructor } from '../shared/generic-constructor';
|
||||||
|
import { hasValue } from '../../shared/empty.util';
|
||||||
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ObjectCacheEffects {
|
export class ObjectCacheEffects {
|
||||||
@Effect() setTimeoutForServerSync = this.actions$
|
@Effect() setTimeoutForServerSync = this.actions$
|
||||||
.pipe(ofType(ServerSyncBufferActionTypes.ADD),
|
.pipe(
|
||||||
|
ofType(ServerSyncBufferActionTypes.ADD),
|
||||||
exhaustMap((action: AddToSSBAction) => {
|
exhaustMap((action: AddToSSBAction) => {
|
||||||
const autoSyncConfig = this.EnvConfig.cache.autoSync;
|
const autoSyncConfig = this.EnvConfig.cache.autoSync;
|
||||||
const timeoutInSeconds = autoSyncConfig.timePerMethod[action.type] || autoSyncConfig.defaultTime;
|
const timeoutInSeconds = autoSyncConfig.timePerMethod[action.type] || autoSyncConfig.defaultTime;
|
||||||
return observableOf(new CommitSSBAction(action.payload.method)).pipe(delay( timeoutInSeconds * 1000))
|
return observableOf(new CommitSSBAction(action.payload.method)).pipe(delay(timeoutInSeconds * 1000))
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@Effect() commitServerSyncBuffer = this.actions$
|
@Effect() commitServerSyncBuffer = this.actions$
|
||||||
.pipe(ofType(ServerSyncBufferActionTypes.COMMIT),
|
.pipe(
|
||||||
map((action: CommitSSBAction) => {
|
ofType(ServerSyncBufferActionTypes.COMMIT),
|
||||||
this.store.pipe(
|
switchMap((action: CommitSSBAction) => {
|
||||||
|
return this.store.pipe(
|
||||||
select(serverSyncBufferSelector),
|
select(serverSyncBufferSelector),
|
||||||
first()
|
map((bufferState: ServerSyncBufferState) => {
|
||||||
).subscribe((bufferState: ServerSyncBufferState) => {
|
const actions: Array<Observable<Action>> = bufferState.buffer
|
||||||
bufferState.buffer
|
.filter((entry: ServerSyncBufferEntry) => {
|
||||||
.filter((entry: ServerSyncBufferEntry) => entry.method === action.payload)
|
/* If there's a request method, filter
|
||||||
.forEach((entry: ServerSyncBufferEntry) => {
|
If there's no filter, commit everything */
|
||||||
this.requestService.configure(new PutRequest(this.requestService.generateRequestId(), ,))
|
if (hasValue(action.payload)) {
|
||||||
})
|
return entry.method === action.payload;
|
||||||
});
|
}
|
||||||
return new EmptySSBAction(action.payload);
|
return true;
|
||||||
|
})
|
||||||
|
.map((entry: ServerSyncBufferEntry) => {
|
||||||
|
if (entry.method === RestRequestMethod.Patch) {
|
||||||
|
return this.applyPatch(entry.href);
|
||||||
|
} else {
|
||||||
|
/* TODO other request stuff */
|
||||||
|
}
|
||||||
|
});
|
||||||
|
/* Add extra action to array, to make sure the ServerSyncBuffer is emptied afterwards */
|
||||||
|
return observableCombineLatest(actions).pipe(
|
||||||
|
map((array) => array.push(new EmptySSBAction(action.payload)))
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
private applyPatch(href: string): Observable<Action> {
|
||||||
|
const patchObject = this.objectCache.getBySelfLink(href);
|
||||||
|
return patchObject.pipe(
|
||||||
|
map((object) => {
|
||||||
|
const serializedObject = new DSpaceRESTv2Serializer(object.constructor as GenericConstructor<{}>).serialize(object);
|
||||||
|
this.requestService.configure(new PutRequest(this.requestService.generateRequestId(), href, serializedObject));
|
||||||
|
return new ApplyPatchObjectCacheAction(href)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
constructor(private actions$: Actions,
|
constructor(private actions$: Actions,
|
||||||
private store: Store<CoreState>,
|
private store: Store<CoreState>,
|
||||||
private requestService: RequestService,
|
private requestService: RequestService,
|
||||||
|
private objectCache: ObjectCacheService,
|
||||||
@Inject(GLOBAL_CONFIG) private EnvConfig: GlobalConfig) {
|
@Inject(GLOBAL_CONFIG) private EnvConfig: GlobalConfig) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -25,7 +25,6 @@ export abstract class BaseResponseParsingService {
|
|||||||
protected abstract toCache: boolean;
|
protected abstract toCache: boolean;
|
||||||
|
|
||||||
protected process<ObjectDomain, ObjectType>(data: any, requestHref: string): any {
|
protected process<ObjectDomain, ObjectType>(data: any, requestHref: string): any {
|
||||||
|
|
||||||
if (isNotEmpty(data)) {
|
if (isNotEmpty(data)) {
|
||||||
if (hasNoValue(data) || (typeof data !== 'object')) {
|
if (hasNoValue(data) || (typeof data !== 'object')) {
|
||||||
return data;
|
return data;
|
||||||
|
@@ -22,8 +22,8 @@ export class CollectionDataService extends ComColDataService<NormalizedCollectio
|
|||||||
protected rdbService: RemoteDataBuildService,
|
protected rdbService: RemoteDataBuildService,
|
||||||
protected store: Store<CoreState>,
|
protected store: Store<CoreState>,
|
||||||
protected cds: CommunityDataService,
|
protected cds: CommunityDataService,
|
||||||
protected objectCache: ObjectCacheService,
|
protected halService: HALEndpointService,
|
||||||
protected halService: HALEndpointService
|
protected objectCache: ObjectCacheService
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
@@ -29,8 +29,8 @@ class TestService extends ComColDataService<NormalizedTestObject, any> {
|
|||||||
protected store: Store<CoreState>,
|
protected store: Store<CoreState>,
|
||||||
protected EnvConfig: GlobalConfig,
|
protected EnvConfig: GlobalConfig,
|
||||||
protected cds: CommunityDataService,
|
protected cds: CommunityDataService,
|
||||||
protected objectCache: ObjectCacheService,
|
|
||||||
protected halService: HALEndpointService,
|
protected halService: HALEndpointService,
|
||||||
|
protected objectCache: ObjectCacheService,
|
||||||
protected linkPath: string
|
protected linkPath: string
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
@@ -92,8 +92,8 @@ describe('ComColDataService', () => {
|
|||||||
store,
|
store,
|
||||||
EnvConfig,
|
EnvConfig,
|
||||||
cds,
|
cds,
|
||||||
objectCache,
|
|
||||||
halService,
|
halService,
|
||||||
|
objectCache,
|
||||||
LINK_NAME
|
LINK_NAME
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -29,8 +29,8 @@ export class CommunityDataService extends ComColDataService<NormalizedCommunity,
|
|||||||
protected requestService: RequestService,
|
protected requestService: RequestService,
|
||||||
protected rdbService: RemoteDataBuildService,
|
protected rdbService: RemoteDataBuildService,
|
||||||
protected store: Store<CoreState>,
|
protected store: Store<CoreState>,
|
||||||
protected objectCache: ObjectCacheService,
|
protected halService: HALEndpointService,
|
||||||
protected halService: HALEndpointService
|
protected objectCache: ObjectCacheService
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
@@ -9,6 +9,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
|
|||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { FindAllOptions } from './request.models';
|
import { FindAllOptions } from './request.models';
|
||||||
import { SortOptions, SortDirection } from '../cache/models/sort-options.model';
|
import { SortOptions, SortDirection } from '../cache/models/sort-options.model';
|
||||||
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
|
|
||||||
const LINK_NAME = 'test'
|
const LINK_NAME = 'test'
|
||||||
|
|
||||||
@@ -17,117 +18,122 @@ class NormalizedTestObject extends NormalizedObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class TestService extends DataService<NormalizedTestObject, any> {
|
class TestService extends DataService<NormalizedTestObject, any> {
|
||||||
constructor(
|
constructor(
|
||||||
protected responseCache: ResponseCacheService,
|
protected responseCache: ResponseCacheService,
|
||||||
protected requestService: RequestService,
|
protected requestService: RequestService,
|
||||||
protected rdbService: RemoteDataBuildService,
|
protected rdbService: RemoteDataBuildService,
|
||||||
protected store: Store<CoreState>,
|
protected store: Store<CoreState>,
|
||||||
protected linkPath: string,
|
protected linkPath: string,
|
||||||
protected halService: HALEndpointService
|
protected halService: HALEndpointService,
|
||||||
) {
|
protected objectCache: ObjectCacheService
|
||||||
super();
|
) {
|
||||||
}
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
public getScopedEndpoint(scope: string): Observable<string> {
|
public getScopedEndpoint(scope: string): Observable<string> {
|
||||||
throw new Error('getScopedEndpoint is abstract in DataService');
|
throw new Error('getScopedEndpoint is abstract in DataService');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('DataService', () => {
|
describe('DataService', () => {
|
||||||
let service: TestService;
|
let service: TestService;
|
||||||
let options: FindAllOptions;
|
let options: FindAllOptions;
|
||||||
const responseCache = {} as ResponseCacheService;
|
const responseCache = {} as ResponseCacheService;
|
||||||
const requestService = {} as RequestService;
|
const requestService = {} as RequestService;
|
||||||
const halService = {} as HALEndpointService;
|
const halService = {} as HALEndpointService;
|
||||||
const rdbService = {} as RemoteDataBuildService;
|
const rdbService = {} as RemoteDataBuildService;
|
||||||
const store = {} as Store<CoreState>;
|
const objectCache = {} as ObjectCacheService;
|
||||||
const endpoint = 'https://rest.api/core';
|
const store = {} as Store<CoreState>;
|
||||||
|
const endpoint = 'https://rest.api/core';
|
||||||
|
|
||||||
function initTestService(): TestService {
|
function initTestService(): TestService {
|
||||||
return new TestService(
|
return new TestService(
|
||||||
responseCache,
|
responseCache,
|
||||||
requestService,
|
requestService,
|
||||||
rdbService,
|
rdbService,
|
||||||
store,
|
store,
|
||||||
LINK_NAME,
|
LINK_NAME,
|
||||||
halService
|
halService,
|
||||||
);
|
objectCache
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
service = initTestService();
|
service = initTestService();
|
||||||
|
|
||||||
describe('getFindAllHref', () => {
|
describe('getFindAllHref', () => {
|
||||||
|
|
||||||
it('should return an observable with the endpoint', () => {
|
it('should return an observable with the endpoint', () => {
|
||||||
options = {};
|
options = {};
|
||||||
|
|
||||||
(service as any).getFindAllHref(endpoint).subscribe((value) => {
|
(service as any).getFindAllHref(endpoint).subscribe((value) => {
|
||||||
expect(value).toBe(endpoint);
|
expect(value).toBe(endpoint);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
// getScopedEndpoint is not implemented in abstract DataService
|
|
||||||
it('should throw error if scopeID provided in options', () => {
|
|
||||||
options = { scopeID: 'somevalue' };
|
|
||||||
|
|
||||||
expect(() => { (service as any).getFindAllHref(endpoint, options) })
|
|
||||||
.toThrowError('getScopedEndpoint is abstract in DataService');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should include page in href if currentPage provided in options', () => {
|
|
||||||
options = { currentPage: 2 };
|
|
||||||
const expected = `${endpoint}?page=${options.currentPage - 1}`;
|
|
||||||
|
|
||||||
(service as any).getFindAllHref(endpoint, options).subscribe((value) => {
|
|
||||||
expect(value).toBe(expected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should include size in href if elementsPerPage provided in options', () => {
|
|
||||||
options = { elementsPerPage: 5 };
|
|
||||||
const expected = `${endpoint}?size=${options.elementsPerPage}`;
|
|
||||||
|
|
||||||
(service as any).getFindAllHref(endpoint, options).subscribe((value) => {
|
|
||||||
expect(value).toBe(expected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should include sort href if SortOptions provided in options', () => {
|
|
||||||
const sortOptions = new SortOptions('field1', SortDirection.ASC);
|
|
||||||
options = { sort: sortOptions};
|
|
||||||
const expected = `${endpoint}?sort=${sortOptions.field},${sortOptions.direction}`;
|
|
||||||
|
|
||||||
(service as any).getFindAllHref(endpoint, options).subscribe((value) => {
|
|
||||||
expect(value).toBe(expected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should include startsWith in href if startsWith provided in options', () => {
|
|
||||||
options = { startsWith: 'ab' };
|
|
||||||
const expected = `${endpoint}?startsWith=${options.startsWith}`;
|
|
||||||
|
|
||||||
(service as any).getFindAllHref(endpoint, options).subscribe((value) => {
|
|
||||||
expect(value).toBe(expected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should include all provided options in href', () => {
|
|
||||||
const sortOptions = new SortOptions('field1', SortDirection.DESC)
|
|
||||||
options = {
|
|
||||||
currentPage: 6,
|
|
||||||
elementsPerPage: 10,
|
|
||||||
sort: sortOptions,
|
|
||||||
startsWith: 'ab'
|
|
||||||
}
|
|
||||||
const expected = `${endpoint}?page=${options.currentPage - 1}&size=${options.elementsPerPage}` +
|
|
||||||
`&sort=${sortOptions.field},${sortOptions.direction}&startsWith=${options.startsWith}`;
|
|
||||||
|
|
||||||
(service as any).getFindAllHref(endpoint, options).subscribe((value) => {
|
|
||||||
expect(value).toBe(expected);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// getScopedEndpoint is not implemented in abstract DataService
|
||||||
|
it('should throw error if scopeID provided in options', () => {
|
||||||
|
options = { scopeID: 'somevalue' };
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
(service as any).getFindAllHref(endpoint, options)
|
||||||
|
})
|
||||||
|
.toThrowError('getScopedEndpoint is abstract in DataService');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include page in href if currentPage provided in options', () => {
|
||||||
|
options = { currentPage: 2 };
|
||||||
|
const expected = `${endpoint}?page=${options.currentPage - 1}`;
|
||||||
|
|
||||||
|
(service as any).getFindAllHref(endpoint, options).subscribe((value) => {
|
||||||
|
expect(value).toBe(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include size in href if elementsPerPage provided in options', () => {
|
||||||
|
options = { elementsPerPage: 5 };
|
||||||
|
const expected = `${endpoint}?size=${options.elementsPerPage}`;
|
||||||
|
|
||||||
|
(service as any).getFindAllHref(endpoint, options).subscribe((value) => {
|
||||||
|
expect(value).toBe(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include sort href if SortOptions provided in options', () => {
|
||||||
|
const sortOptions = new SortOptions('field1', SortDirection.ASC);
|
||||||
|
options = { sort: sortOptions };
|
||||||
|
const expected = `${endpoint}?sort=${sortOptions.field},${sortOptions.direction}`;
|
||||||
|
|
||||||
|
(service as any).getFindAllHref(endpoint, options).subscribe((value) => {
|
||||||
|
expect(value).toBe(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include startsWith in href if startsWith provided in options', () => {
|
||||||
|
options = { startsWith: 'ab' };
|
||||||
|
const expected = `${endpoint}?startsWith=${options.startsWith}`;
|
||||||
|
|
||||||
|
(service as any).getFindAllHref(endpoint, options).subscribe((value) => {
|
||||||
|
expect(value).toBe(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include all provided options in href', () => {
|
||||||
|
const sortOptions = new SortOptions('field1', SortDirection.DESC)
|
||||||
|
options = {
|
||||||
|
currentPage: 6,
|
||||||
|
elementsPerPage: 10,
|
||||||
|
sort: sortOptions,
|
||||||
|
startsWith: 'ab'
|
||||||
|
}
|
||||||
|
const expected = `${endpoint}?page=${options.currentPage - 1}&size=${options.elementsPerPage}` +
|
||||||
|
`&sort=${sortOptions.field},${sortOptions.direction}&startsWith=${options.startsWith}`;
|
||||||
|
|
||||||
|
(service as any).getFindAllHref(endpoint, options).subscribe((value) => {
|
||||||
|
expect(value).toBe(expected);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -14,6 +14,9 @@ import { RemoteData } from './remote-data';
|
|||||||
import { FindAllOptions, FindAllRequest, FindByIDRequest, GetRequest } from './request.models';
|
import { FindAllOptions, FindAllRequest, FindByIDRequest, GetRequest } from './request.models';
|
||||||
import { RequestService } from './request.service';
|
import { RequestService } from './request.service';
|
||||||
import { NormalizedObject } from '../cache/models/normalized-object.model';
|
import { NormalizedObject } from '../cache/models/normalized-object.model';
|
||||||
|
import { compare, Operation } from 'fast-json-patch';
|
||||||
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
|
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||||
|
|
||||||
export abstract class DataService<TNormalized extends NormalizedObject, TDomain> {
|
export abstract class DataService<TNormalized extends NormalizedObject, TDomain> {
|
||||||
protected abstract responseCache: ResponseCacheService;
|
protected abstract responseCache: ResponseCacheService;
|
||||||
@@ -22,6 +25,7 @@ export abstract class DataService<TNormalized extends NormalizedObject, TDomain>
|
|||||||
protected abstract store: Store<CoreState>;
|
protected abstract store: Store<CoreState>;
|
||||||
protected abstract linkPath: string;
|
protected abstract linkPath: string;
|
||||||
protected abstract halService: HALEndpointService;
|
protected abstract halService: HALEndpointService;
|
||||||
|
protected abstract objectCache: ObjectCacheService;
|
||||||
|
|
||||||
public abstract getScopedEndpoint(scope: string): Observable<string>
|
public abstract getScopedEndpoint(scope: string): Observable<string>
|
||||||
|
|
||||||
@@ -97,6 +101,15 @@ export abstract class DataService<TNormalized extends NormalizedObject, TDomain>
|
|||||||
return this.rdbService.buildSingle<TNormalized, TDomain>(href);
|
return this.rdbService.buildSingle<TNormalized, TDomain>(href);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
patch(href: string, operations: Operation[]) {
|
||||||
|
this.objectCache.addPatch(href, operations);
|
||||||
|
}
|
||||||
|
|
||||||
|
update(object: DSpaceObject) {
|
||||||
|
const oldVersion = this.objectCache.getBySelfLink(object.self);
|
||||||
|
const operations = compare(oldVersion, object);
|
||||||
|
this.objectCache.addPatch(object.self, operations);
|
||||||
|
}
|
||||||
// TODO implement, after the structure of the REST server's POST response is finalized
|
// TODO implement, after the structure of the REST server's POST response is finalized
|
||||||
// create(dso: DSpaceObject): Observable<RemoteData<TDomain>> {
|
// create(dso: DSpaceObject): Observable<RemoteData<TDomain>> {
|
||||||
// const postHrefObs = this.getEndpoint();
|
// const postHrefObs = this.getEndpoint();
|
||||||
|
@@ -6,6 +6,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
|
|||||||
import { FindByIDRequest } from './request.models';
|
import { FindByIDRequest } from './request.models';
|
||||||
import { RequestService } from './request.service';
|
import { RequestService } from './request.service';
|
||||||
import { DSpaceObjectDataService } from './dspace-object-data.service';
|
import { DSpaceObjectDataService } from './dspace-object-data.service';
|
||||||
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
|
|
||||||
describe('DSpaceObjectDataService', () => {
|
describe('DSpaceObjectDataService', () => {
|
||||||
let scheduler: TestScheduler;
|
let scheduler: TestScheduler;
|
||||||
@@ -13,6 +14,7 @@ describe('DSpaceObjectDataService', () => {
|
|||||||
let halService: HALEndpointService;
|
let halService: HALEndpointService;
|
||||||
let requestService: RequestService;
|
let requestService: RequestService;
|
||||||
let rdbService: RemoteDataBuildService;
|
let rdbService: RemoteDataBuildService;
|
||||||
|
let objectCache: ObjectCacheService;
|
||||||
const testObject = {
|
const testObject = {
|
||||||
uuid: '9b4f22f4-164a-49db-8817-3316b6ee5746'
|
uuid: '9b4f22f4-164a-49db-8817-3316b6ee5746'
|
||||||
} as DSpaceObject;
|
} as DSpaceObject;
|
||||||
@@ -37,11 +39,13 @@ describe('DSpaceObjectDataService', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
objectCache = {} as ObjectCacheService;
|
||||||
|
|
||||||
service = new DSpaceObjectDataService(
|
service = new DSpaceObjectDataService(
|
||||||
requestService,
|
requestService,
|
||||||
rdbService,
|
rdbService,
|
||||||
halService
|
halService,
|
||||||
|
objectCache
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -10,6 +10,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
|
|||||||
import { DataService } from './data.service';
|
import { DataService } from './data.service';
|
||||||
import { RemoteData } from './remote-data';
|
import { RemoteData } from './remote-data';
|
||||||
import { RequestService } from './request.service';
|
import { RequestService } from './request.service';
|
||||||
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
|
|
||||||
/* tslint:disable:max-classes-per-file */
|
/* tslint:disable:max-classes-per-file */
|
||||||
class DataServiceImpl extends DataService<NormalizedDSpaceObject, DSpaceObject> {
|
class DataServiceImpl extends DataService<NormalizedDSpaceObject, DSpaceObject> {
|
||||||
@@ -20,7 +21,8 @@ class DataServiceImpl extends DataService<NormalizedDSpaceObject, DSpaceObject>
|
|||||||
protected requestService: RequestService,
|
protected requestService: RequestService,
|
||||||
protected rdbService: RemoteDataBuildService,
|
protected rdbService: RemoteDataBuildService,
|
||||||
protected store: Store<CoreState>,
|
protected store: Store<CoreState>,
|
||||||
protected halService: HALEndpointService) {
|
protected halService: HALEndpointService,
|
||||||
|
protected objectCache: ObjectCacheService) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,8 +43,9 @@ export class DSpaceObjectDataService {
|
|||||||
constructor(
|
constructor(
|
||||||
protected requestService: RequestService,
|
protected requestService: RequestService,
|
||||||
protected rdbService: RemoteDataBuildService,
|
protected rdbService: RemoteDataBuildService,
|
||||||
protected halService: HALEndpointService) {
|
protected halService: HALEndpointService,
|
||||||
this.dataService = new DataServiceImpl(null, requestService, rdbService, null, halService);
|
protected objectCache: ObjectCacheService) {
|
||||||
|
this.dataService = new DataServiceImpl(null, requestService, rdbService, null, halService, objectCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
findById(uuid: string): Observable<RemoteData<DSpaceObject>> {
|
findById(uuid: string): Observable<RemoteData<DSpaceObject>> {
|
||||||
|
@@ -8,6 +8,7 @@ import { CoreState } from '../core.reducers';
|
|||||||
import { ItemDataService } from './item-data.service';
|
import { ItemDataService } from './item-data.service';
|
||||||
import { RequestService } from './request.service';
|
import { RequestService } from './request.service';
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
|
|
||||||
describe('ItemDataService', () => {
|
describe('ItemDataService', () => {
|
||||||
let scheduler: TestScheduler;
|
let scheduler: TestScheduler;
|
||||||
@@ -16,6 +17,7 @@ describe('ItemDataService', () => {
|
|||||||
const requestService = {} as RequestService;
|
const requestService = {} as RequestService;
|
||||||
const responseCache = {} as ResponseCacheService;
|
const responseCache = {} as ResponseCacheService;
|
||||||
const rdbService = {} as RemoteDataBuildService;
|
const rdbService = {} as RemoteDataBuildService;
|
||||||
|
const objectCache = {} as ObjectCacheService;
|
||||||
const store = {} as Store<CoreState>;
|
const store = {} as Store<CoreState>;
|
||||||
const halEndpointService = {} as HALEndpointService;
|
const halEndpointService = {} as HALEndpointService;
|
||||||
|
|
||||||
@@ -42,7 +44,8 @@ describe('ItemDataService', () => {
|
|||||||
rdbService,
|
rdbService,
|
||||||
store,
|
store,
|
||||||
bs,
|
bs,
|
||||||
halEndpointService
|
halEndpointService,
|
||||||
|
objectCache
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -17,6 +17,7 @@ import { URLCombiner } from '../url-combiner/url-combiner';
|
|||||||
import { DataService } from './data.service';
|
import { DataService } from './data.service';
|
||||||
import { RequestService } from './request.service';
|
import { RequestService } from './request.service';
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ItemDataService extends DataService<NormalizedItem, Item> {
|
export class ItemDataService extends DataService<NormalizedItem, Item> {
|
||||||
@@ -28,7 +29,8 @@ export class ItemDataService extends DataService<NormalizedItem, Item> {
|
|||||||
protected rdbService: RemoteDataBuildService,
|
protected rdbService: RemoteDataBuildService,
|
||||||
protected store: Store<CoreState>,
|
protected store: Store<CoreState>,
|
||||||
private bs: BrowseService,
|
private bs: BrowseService,
|
||||||
protected halService: HALEndpointService) {
|
protected halService: HALEndpointService,
|
||||||
|
protected objectCache: ObjectCacheService) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -17,6 +17,7 @@ import { RequestConfigureAction, RequestExecuteAction } from './request.actions'
|
|||||||
import { GetRequest, RestRequest, RestRequestMethod } from './request.models';
|
import { GetRequest, RestRequest, RestRequestMethod } from './request.models';
|
||||||
|
|
||||||
import { RequestEntry } from './request.reducer';
|
import { RequestEntry } from './request.reducer';
|
||||||
|
import { CommitSSBAction } from '../cache/server-sync-buffer.actions';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RequestService {
|
export class RequestService {
|
||||||
@@ -123,4 +124,8 @@ export class RequestService {
|
|||||||
this.requestsOnTheirWayToTheStore = this.requestsOnTheirWayToTheStore.filter((pendingHref: string) => pendingHref !== request.href)
|
this.requestsOnTheirWayToTheStore = this.requestsOnTheirWayToTheStore.filter((pendingHref: string) => pendingHref !== request.href)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
commit(method?: RestRequestMethod) {
|
||||||
|
this.store.dispatch(new CommitSSBAction(method))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
<div [class.form-group]="(type !== 6 && asBootstrapFormGroup) || getClass('element', 'container').includes('form-group')"
|
<div [class.form-group]="(model.type !== 'GROUP' && asBootstrapFormGroup) || getClass('element', 'container').includes('form-group')"
|
||||||
[formGroup]="group"
|
[formGroup]="group"
|
||||||
[ngClass]="[getClass('element', 'container'), getClass('grid', 'container')]">
|
[ngClass]="[getClass('element', 'container'), getClass('grid', 'container')]">
|
||||||
|
|
||||||
<label *ngIf="type !== 3 && model.label"
|
<label *ngIf="model.type !== 'CHECKBOX' && model.label"
|
||||||
[for]="model.id"
|
[for]="model.id"
|
||||||
[innerHTML]="(model.required && model.label) ? (model.label | translate) + ' *' : (model.label | translate)"
|
[innerHTML]="(model.required && model.label) ? (model.label | translate) + ' *' : (model.label | translate)"
|
||||||
[ngClass]="[getClass('element', 'label'), getClass('grid', 'label')]"></label>
|
[ngClass]="[getClass('element', 'label'), getClass('grid', 'label')]"></label>
|
||||||
|
@@ -10,7 +10,12 @@ export function getMockFormService(
|
|||||||
getUniqueId: id$,
|
getUniqueId: id$,
|
||||||
resetForm: {},
|
resetForm: {},
|
||||||
validateAllFormFields: {},
|
validateAllFormFields: {},
|
||||||
getForm: errors.pipe(map((err) => { return {data: {}, valid: true, errors: err} })),
|
getForm: errors.pipe(
|
||||||
|
map((err) => {
|
||||||
|
return { data: {}, valid: true, errors: err }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
removeForm: undefined,
|
removeForm: undefined,
|
||||||
removeError: undefined,
|
removeError: undefined,
|
||||||
changeForm: undefined,
|
changeForm: undefined,
|
||||||
|
@@ -99,7 +99,7 @@ export class NumberPickerComponent implements OnInit, ControlValueAccessor {
|
|||||||
|
|
||||||
update(event) {
|
update(event) {
|
||||||
try {
|
try {
|
||||||
const i = Number.parseInt(event.target.value);
|
const i = Number.parseInt(event.target.value, 10);
|
||||||
|
|
||||||
if (i >= this.min && i <= this.max) {
|
if (i >= this.min && i <= this.max) {
|
||||||
this.value = i;
|
this.value = i;
|
||||||
|
10
tslint.json
10
tslint.json
@@ -151,10 +151,10 @@
|
|||||||
"use-input-property-decorator": true,
|
"use-input-property-decorator": true,
|
||||||
"use-life-cycle-interface": false,
|
"use-life-cycle-interface": false,
|
||||||
"use-output-property-decorator": true,
|
"use-output-property-decorator": true,
|
||||||
"use-pipe-transform-interface": true,
|
"use-pipe-transform-interface": true
|
||||||
"rxjs-collapse-imports": true,
|
// "rxjs-collapse-imports": true,
|
||||||
"rxjs-pipeable-operators-only": true,
|
// "rxjs-pipeable-operators-only": true,
|
||||||
"rxjs-no-static-observable-methods": true,
|
// "rxjs-no-static-observable-methods": true,
|
||||||
"rxjs-proper-imports": true
|
// "rxjs-proper-imports": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user