implemented applying of a patch

This commit is contained in:
lotte
2018-09-19 13:10:23 +02:00
parent 17ad62c172
commit e959542e2d
20 changed files with 226 additions and 147 deletions

View File

@@ -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();
} }

View File

@@ -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);

View File

@@ -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 : [])
} }
}); });
} }

View File

@@ -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));
} }

View File

@@ -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) {
} }

View File

@@ -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;

View File

@@ -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();
} }

View File

@@ -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
); );
} }

View File

@@ -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();
} }

View File

@@ -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);
});
})
});
}); });

View File

@@ -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();

View File

@@ -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
) )
}); });

View File

@@ -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>> {

View File

@@ -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
); );
} }

View File

@@ -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();
} }

View File

@@ -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))
}
} }

View File

@@ -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>

View File

@@ -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,

View File

@@ -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;

View File

@@ -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
} }
} }