Merge pull request #4662 from FinnStutzenstein/repoSpeed

Speedup DS and repo updates, DSUpdateSlots, lazyloading history objects
This commit is contained in:
Emanuel Schütze 2019-05-07 13:26:21 +02:00 committed by GitHub
commit d218a86f69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 524 additions and 235 deletions

View File

@ -3,7 +3,7 @@ import { Injectable, Injector } from '@angular/core';
import { plugins } from '../../../plugins'; import { plugins } from '../../../plugins';
import { CommonAppConfig } from '../../site/common/common.config'; import { CommonAppConfig } from '../../site/common/common.config';
import { AppConfig, SearchableModelEntry, ModelEntry } from '../app-config'; import { AppConfig, SearchableModelEntry, ModelEntry } from '../app-config';
import { CollectionStringMapperService } from './collectionStringMapper.service'; import { CollectionStringMapperService } from './collection-string-mapper.service';
import { MediafileAppConfig } from '../../site/mediafiles/mediafile.config'; import { MediafileAppConfig } from '../../site/mediafiles/mediafile.config';
import { MotionsAppConfig } from '../../site/motions/motions.config'; import { MotionsAppConfig } from '../../site/motions/motions.config';
import { ConfigAppConfig } from '../../site/config/config.config'; import { ConfigAppConfig } from '../../site/config/config.config';

View File

@ -1,9 +1,10 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { WebsocketService } from './websocket.service'; import { WebsocketService } from './websocket.service';
import { CollectionStringMapperService } from './collectionStringMapper.service'; import { CollectionStringMapperService } from './collection-string-mapper.service';
import { DataStoreService } from './data-store.service'; import { DataStoreService } from './data-store.service';
import { BaseModel } from '../../shared/models/base/base-model'; import { BaseModel } from '../../shared/models/base/base-model';
import { DataStoreUpdateManagerService } from './data-store-update-manager.service';
interface AutoupdateFormat { interface AutoupdateFormat {
/** /**
@ -54,7 +55,8 @@ export class AutoupdateService {
public constructor( public constructor(
private websocketService: WebsocketService, private websocketService: WebsocketService,
private DS: DataStoreService, private DS: DataStoreService,
private modelMapper: CollectionStringMapperService private modelMapper: CollectionStringMapperService,
private DSUpdateManager: DataStoreUpdateManagerService
) { ) {
this.websocketService.getOberservable<AutoupdateFormat>('autoupdate').subscribe(response => { this.websocketService.getOberservable<AutoupdateFormat>('autoupdate').subscribe(response => {
this.storeResponse(response); this.storeResponse(response);
@ -105,6 +107,8 @@ export class AutoupdateService {
// Normal autoupdate // Normal autoupdate
if (autoupdate.from_change_id <= maxChangeId + 1 && autoupdate.to_change_id > maxChangeId) { if (autoupdate.from_change_id <= maxChangeId + 1 && autoupdate.to_change_id > maxChangeId) {
const updateSlot = await this.DSUpdateManager.getNewUpdateSlot(this.DS);
// Delete the removed objects from the DataStore // Delete the removed objects from the DataStore
for (const collection of Object.keys(autoupdate.deleted)) { for (const collection of Object.keys(autoupdate.deleted)) {
await this.DS.remove(collection, autoupdate.deleted[collection]); await this.DS.remove(collection, autoupdate.deleted[collection]);
@ -120,6 +124,8 @@ export class AutoupdateService {
} }
await this.DS.flushToStorage(autoupdate.to_change_id); await this.DS.flushToStorage(autoupdate.to_change_id);
this.DSUpdateManager.commit(updateSlot);
} else { } else {
// autoupdate fully in the future. we are missing something! // autoupdate fully in the future. we are missing something!
this.requestChanges(); this.requestChanges();

View File

@ -0,0 +1,17 @@
import { TestBed, inject } from '@angular/core/testing';
import { E2EImportsModule } from '../../../e2e-imports.module';
import { CollectionStringMapperService } from './collection-string-mapper.service';
describe('CollectionStringMapperService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [E2EImportsModule],
providers: [CollectionStringMapperService]
});
});
it('should be created', inject([CollectionStringMapperService], (service: CollectionStringMapperService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -17,6 +17,12 @@ interface UnifiedConstructors {
*/ */
type TypeIdentifier = UnifiedConstructors | BaseRepository<any, any> | string; type TypeIdentifier = UnifiedConstructors | BaseRepository<any, any> | string;
type CollectionStringMappedTypes = [
ModelConstructor<BaseModel>,
ViewModelConstructor<BaseViewModel>,
BaseRepository<BaseViewModel, BaseModel>
];
/** /**
* Registeres the mapping between collection strings, models constructors, view * Registeres the mapping between collection strings, models constructors, view
* model constructors and repositories. * model constructors and repositories.
@ -30,11 +36,7 @@ export class CollectionStringMapperService {
* Maps collection strings to mapping entries * Maps collection strings to mapping entries
*/ */
private collectionStringMapping: { private collectionStringMapping: {
[collectionString: string]: [ [collectionString: string]: CollectionStringMappedTypes;
ModelConstructor<BaseModel>,
ViewModelConstructor<BaseViewModel>,
BaseRepository<BaseViewModel, BaseModel>
];
} = {}; } = {};
public constructor() {} public constructor() {}
@ -65,6 +67,9 @@ export class CollectionStringMapperService {
} }
} }
/**
* @returns true, if the given collection is known by this service.
*/
public isCollectionRegistered(collectionString: string): boolean { public isCollectionRegistered(collectionString: string): boolean {
return !!this.collectionStringMapping[collectionString]; return !!this.collectionStringMapping[collectionString];
} }
@ -100,4 +105,11 @@ export class CollectionStringMapperService {
return this.collectionStringMapping[this.getCollectionString(obj)][2] as BaseRepository<V, M>; return this.collectionStringMapping[this.getCollectionString(obj)][2] as BaseRepository<V, M>;
} }
} }
/**
* @returns all registered repositories.
*/
public getAllRepositories(): BaseRepository<any, any>[] {
return Object.values(this.collectionStringMapping).map((types: CollectionStringMappedTypes) => types[2]);
}
} }

View File

@ -1,3 +0,0 @@
describe('CollectionStringModelMapperService', () => {
beforeEach(() => {});
});

View File

@ -0,0 +1,17 @@
import { TestBed, inject } from '@angular/core/testing';
import { E2EImportsModule } from '../../../e2e-imports.module';
import { DataStoreUpdateManagerService } from './data-store-update-manager.service';
describe('DataStoreUpdateManagerService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [E2EImportsModule],
providers: [DataStoreUpdateManagerService]
});
});
it('should be created', inject([DataStoreUpdateManagerService], (service: DataStoreUpdateManagerService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -0,0 +1,182 @@
import { Injectable } from '@angular/core';
import { Deferred } from '../deferred';
import { DataStoreService } from './data-store.service';
import { CollectionStringMapperService } from './collection-string-mapper.service';
export interface CollectionIds {
[collection: string]: number[];
}
/**
* Helper class for collecting data during the update phase of the DataStore.
*/
export class UpdateSlot {
/**
* Count instnaces of this class to easily compare them.
*/
private static ID_COUTNER = 1;
/**
* Mapping of changed model ids to their collection.
*/
private changedModels: CollectionIds = {};
/**
* Mapping of deleted models to their collection.
*/
private deletedModels: CollectionIds = {};
/**
* The object's id.
*/
private _id: number;
/**
* @param DS Carries the DataStore: TODO (see below `DataStoreUpdateManagerService.getNewUpdateSlot`)
*/
public constructor(public readonly DS: DataStoreService) {
this._id = UpdateSlot.ID_COUTNER++;
}
/**
* Adds changed model information
*
* @param collection The collection
* @param id The id
*/
public addChangedModel(collection: string, id: number): void {
if (!this.changedModels[collection]) {
this.changedModels[collection] = [];
}
this.changedModels[collection].push(id);
}
/**
* Adds deleted model information
*
* @param collection The collection
* @param id The id
*/
public addDeletedModel(collection: string, id: number): void {
if (!this.deletedModels[collection]) {
this.deletedModels[collection] = [];
}
this.deletedModels[collection].push(id);
}
/**
* @param collection The collection
* @returns the list of changed model ids for the collection
*/
public getChangedModelIdsForCollection(collection: string): number[] {
return this.changedModels[collection] || [];
}
/**
* @returns the mapping of all changed models
*/
public getChangedModels(): CollectionIds {
return this.changedModels;
}
/**
* @param collection The collection
* @returns the list of deleted model ids for the collection
*/
public getDeletedModelIdsForCollection(collection: string): number[] {
return this.deletedModels[collection] || [];
}
/**
* Compares this object to another update slot.
*/
public equal(other: UpdateSlot): boolean {
return this._id === other._id;
}
}
/**
* Manages updates in the DS. Collects all ids for changed and deleted models and bulk-update
* affected repositories.
*/
@Injectable({
providedIn: 'root'
})
export class DataStoreUpdateManagerService {
/**
* The current update slot
*/
private currentUpdateSlot: UpdateSlot | null = null;
/**
* Requests for getting an update slot.
*/
private updateSlotRequests: Deferred[] = [];
/**
* @param mapperService
*/
public constructor(private mapperService: CollectionStringMapperService) {}
/**
* Retrieve the current update slot.
*/
public getCurrentUpdateSlot(): UpdateSlot | null {
return this.currentUpdateSlot;
}
/**
* Get a new update slot. Returns a promise that must be awaited, if there is another
* update in progress.
*
* @param DS The DataStore. This is a hack, becuase we cannot use the DataStore
* here, because these are cyclic dependencies... --> TODO
*/
public async getNewUpdateSlot(DS: DataStoreService): Promise<UpdateSlot> {
if (this.currentUpdateSlot) {
const request = new Deferred();
this.updateSlotRequests.push(request);
await request.promise;
}
this.currentUpdateSlot = new UpdateSlot(DS);
return this.currentUpdateSlot;
}
/**
* Commits the given update slot. THis slot must be the current one. If there are requests
* for update slots queued, the next one will be served.
*
* Note: I added this param to make sure, that only the user of the slot
* can commit the update and no one else.
*
* @param slot The slot to commit
*/
public commit(slot: UpdateSlot): void {
if (!this.currentUpdateSlot || !this.currentUpdateSlot.equal(slot)) {
throw new Error('No or wrong update slot to be finished!');
}
this.currentUpdateSlot = null;
// notify repositories in two phases
const repositories = this.mapperService.getAllRepositories();
// Phase 1: deleting and creating of view models (in this order)
repositories.forEach(repo => {
repo.deleteModels(slot.getDeletedModelIdsForCollection(repo.collectionString));
repo.changedModels(slot.getChangedModelIdsForCollection(repo.collectionString));
});
// Phase 2: updating dependencies
repositories.forEach(repo => {
repo.updateDependencies(slot.getChangedModels());
});
slot.DS.triggerModifiedObservable();
// serve next slot request
if (this.updateSlotRequests.length > 0) {
const request = this.updateSlotRequests.pop();
request.resolve();
}
}
}

View File

@ -4,7 +4,8 @@ import { Observable, Subject } from 'rxjs';
import { BaseModel, ModelConstructor } from '../../shared/models/base/base-model'; import { BaseModel, ModelConstructor } from '../../shared/models/base/base-model';
import { StorageService } from './storage.service'; import { StorageService } from './storage.service';
import { CollectionStringMapperService } from './collectionStringMapper.service'; import { CollectionStringMapperService } from './collection-string-mapper.service';
import { DataStoreUpdateManagerService } from './data-store-update-manager.service';
/** /**
* Represents information about a deleted model. * Represents information about a deleted model.
@ -68,59 +69,23 @@ export class DataStoreService {
private jsonStore: JsonStorage = {}; private jsonStore: JsonStorage = {};
/** /**
* Observable subject for changed models in the datastore. * Subjects for changed elements (notified, even if there is a current update slot) for
* a specific collection.
*/ */
private readonly changedSubject: Subject<BaseModel> = new Subject<BaseModel>(); private changedSubjects: { [collection: string]: Subject<BaseModel> } = {};
/**
* This is subject notify all subscribers _before_ the `secondaryModelChangeSubject`.
* It's the same subject as the changedSubject.
*/
public readonly primaryModelChangeSubject = new Subject<BaseModel>();
/**
* This is subject notify all subscribers _after_ the `primaryModelChangeSubject`.
* It's the same subject as the changedSubject.
*/
public readonly secondaryModelChangeSubject = new Subject<BaseModel>();
/**
* Observe the datastore for changes.
*
* @return an observable for changed models
*/
public get changeObservable(): Observable<BaseModel> {
return this.changedSubject.asObservable();
}
/**
* Observable subject for changed models in the datastore.
*/
private readonly deletedSubject: Subject<DeletedInformation> = new Subject<DeletedInformation>();
/**
* Observe the datastore for deletions.
*
* @return an observable for deleted objects.
*/
public get deletedObservable(): Observable<DeletedInformation> {
return this.deletedSubject.asObservable();
}
/** /**
* Observable subject for changed or deleted models in the datastore. * Observable subject for changed or deleted models in the datastore.
*/ */
private readonly changedOrDeletedSubject: Subject<BaseModel | DeletedInformation> = new Subject< private readonly modifiedSubject: Subject<void> = new Subject<void>();
BaseModel | DeletedInformation
>();
/** /**
* Observe the datastore for changes and deletions. * Observe the datastore for changes and deletions.
* *
* @return an observable for changed and deleted objects. * @return an observable for changed and deleted objects.
*/ */
public get changedOrDeletedObservable(): Observable<BaseModel | DeletedInformation> { public get modifiedObservable(): Observable<void> {
return this.changedOrDeletedSubject.asObservable(); return this.modifiedSubject.asObservable();
} }
/** /**
@ -152,12 +117,26 @@ export class DataStoreService {
/** /**
* @param storageService use StorageService to preserve the DataStore. * @param storageService use StorageService to preserve the DataStore.
* @param modelMapper * @param modelMapper
* @param DSUpdateManager
*/ */
public constructor(private storageService: StorageService, private modelMapper: CollectionStringMapperService) { public constructor(
this.changeObservable.subscribe(model => { private storageService: StorageService,
this.primaryModelChangeSubject.next(model); private modelMapper: CollectionStringMapperService,
this.secondaryModelChangeSubject.next(model); private DSUpdateManager: DataStoreUpdateManagerService
}); ) {}
/**
* Get an model observable for models from a given collection. These observable will be notified,
* even if there is an active update slot. So use this with caution (-> only collections with less models).
*
* @param collectionType The collection
*/
public getChangeObservable<T extends BaseModel>(collectionType: ModelConstructor<T> | string): Observable<T> {
const collection = this.getCollectionString(collectionType);
if (!this.changedSubjects[collection]) {
this.changedSubjects[collection] = new Subject();
}
return this.changedSubjects[collection].asObservable() as Observable<T>;
} }
/** /**
@ -165,12 +144,15 @@ export class DataStoreService {
* @returns The max change id. * @returns The max change id.
*/ */
public async initFromStorage(): Promise<number> { public async initFromStorage(): Promise<number> {
// This promise will be resolved with the maximal change id of the cache. // This promise will be resolved with cached datastore.
const store = await this.storageService.get<JsonStorage>(DataStoreService.cachePrefix + 'DS'); const store = await this.storageService.get<JsonStorage>(DataStoreService.cachePrefix + 'DS');
if (store) { if (store) {
const updateSlot = await this.DSUpdateManager.getNewUpdateSlot(this);
// There is a store. Deserialize it // There is a store. Deserialize it
this.jsonStore = store; this.jsonStore = store;
this.modelStore = this.deserializeJsonStore(this.jsonStore); this.modelStore = this.deserializeJsonStore(this.jsonStore);
// Get the maxChangeId from the cache // Get the maxChangeId from the cache
let maxChangeId = await this.storageService.get<number>(DataStoreService.cachePrefix + 'maxChangeId'); let maxChangeId = await this.storageService.get<number>(DataStoreService.cachePrefix + 'maxChangeId');
if (!maxChangeId) { if (!maxChangeId) {
@ -184,6 +166,8 @@ export class DataStoreService {
this.publishChangedInformation(this.modelStore[collection][id]); this.publishChangedInformation(this.modelStore[collection][id]);
}); });
}); });
this.DSUpdateManager.commit(updateSlot);
} else { } else {
await this.clear(); await this.clear();
} }
@ -414,8 +398,17 @@ export class DataStoreService {
* @param model The model to publish * @param model The model to publish
*/ */
private publishChangedInformation(model: BaseModel): void { private publishChangedInformation(model: BaseModel): void {
this.changedSubject.next(model); const slot = this.DSUpdateManager.getCurrentUpdateSlot();
this.changedOrDeletedSubject.next(model); if (slot) {
slot.addChangedModel(model.collectionString, model.id);
// triggerModifiedObservable will be called by committing the update slot.
} else {
this.triggerModifiedObservable();
}
if (this.changedSubjects[model.collectionString]) {
this.changedSubjects[model.collectionString].next(model);
}
} }
/** /**
@ -424,8 +417,20 @@ export class DataStoreService {
* @param information The information about the deleted model * @param information The information about the deleted model
*/ */
private publishDeletedInformation(information: DeletedInformation): void { private publishDeletedInformation(information: DeletedInformation): void {
this.deletedSubject.next(information); const slot = this.DSUpdateManager.getCurrentUpdateSlot();
this.changedOrDeletedSubject.next(information); if (slot) {
slot.addDeletedModel(information.collection, information.id);
// triggerModifiedObservable will be called by committing the update slot.
} else {
this.triggerModifiedObservable();
}
}
/**
* Triggers the modified subject.
*/
public triggerModifiedObservable(): void {
this.modifiedSubject.next();
} }
/** /**

View File

@ -12,7 +12,7 @@ import { OpenSlidesStatusService } from './openslides-status.service';
import { ViewUser } from 'app/site/users/models/view-user'; import { ViewUser } from 'app/site/users/models/view-user';
import { OnAfterAppsLoaded } from '../onAfterAppsLoaded'; import { OnAfterAppsLoaded } from '../onAfterAppsLoaded';
import { UserRepositoryService } from '../repositories/users/user-repository.service'; import { UserRepositoryService } from '../repositories/users/user-repository.service';
import { CollectionStringMapperService } from './collectionStringMapper.service'; import { CollectionStringMapperService } from './collection-string-mapper.service';
import { StorageService } from './storage.service'; import { StorageService } from './storage.service';
import { HttpService } from './http.service'; import { HttpService } from './http.service';
import { filter, auditTime } from 'rxjs/operators'; import { filter, auditTime } from 'rxjs/operators';
@ -139,23 +139,23 @@ export class OperatorService implements OnAfterAppsLoaded {
private storageService: StorageService, private storageService: StorageService,
private OSStatus: OpenSlidesStatusService private OSStatus: OpenSlidesStatusService
) { ) {
this.DS.changeObservable.subscribe(newModel => { this.DS.getChangeObservable(User).subscribe(newModel => {
if (this._user && newModel instanceof User && this._user.id === newModel.id) { if (this._user && this._user.id === newModel.id) {
this._user = newModel; this._user = newModel;
this.updateUserInCurrentWhoAmI(); this.updateUserInCurrentWhoAmI();
} }
}); });
this.DS.changeObservable this.DS.getChangeObservable(Group)
.pipe( .pipe(
filter( filter(
model => model =>
// Any group has changed if we have an operator or // Any group has changed if we have an operator or
// group 1 (default) for anonymous changed // group 1 (default) for anonymous changed
model instanceof Group && (!!this._user || model.id === 1) !!this._user || model.id === 1
), ),
auditTime(10) auditTime(10)
) )
.subscribe(newModel => this.updatePermissions()); .subscribe(_ => this.updatePermissions());
} }
/** /**

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { environment } from 'environments/environment'; import { environment } from 'environments/environment';
import { CollectionStringMapperService } from './collectionStringMapper.service'; import { CollectionStringMapperService } from './collection-string-mapper.service';
import { History } from 'app/shared/models/core/history'; import { History } from 'app/shared/models/core/history';
import { DataStoreService } from './data-store.service'; import { DataStoreService } from './data-store.service';
import { WebsocketService } from './websocket.service'; import { WebsocketService } from './websocket.service';

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CollectionStringMapperService } from './collectionStringMapper.service'; import { CollectionStringMapperService } from './collection-string-mapper.service';
import { BaseViewModel, ViewModelConstructor } from 'app/site/base/base-view-model'; import { BaseViewModel, ViewModelConstructor } from 'app/site/base/base-view-model';
import { BaseRepository } from '../repositories/base-repository'; import { BaseRepository } from '../repositories/base-repository';

View File

@ -13,7 +13,7 @@
* // * //
* ``` * ```
*/ */
export class Deferred<T> { export class Deferred<T = void> {
/** /**
* The promise to wait for * The promise to wait for
*/ */
@ -22,7 +22,7 @@ export class Deferred<T> {
/** /**
* custom resolve function * custom resolve function
*/ */
private _resolve: () => void; private _resolve: (val?: T) => void;
/** /**
* Creates the promise and overloads the resolve function * Creates the promise and overloads the resolve function
@ -36,7 +36,7 @@ export class Deferred<T> {
/** /**
* Entry point for the resolve function * Entry point for the resolve function
*/ */
public resolve(): void { public resolve(val?: T): void {
this._resolve(); this._resolve(val);
} }
} }

View File

@ -5,7 +5,7 @@ import { Observable } from 'rxjs';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { BaseRepository } from '../base-repository'; import { BaseRepository } from '../base-repository';
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
import { ConfigService } from 'app/core/ui-services/config.service'; import { ConfigService } from 'app/core/ui-services/config.service';
import { DataSendService } from 'app/core/core-services/data-send.service'; import { DataSendService } from 'app/core/core-services/data-send.service';
import { DataStoreService } from '../../core-services/data-store.service'; import { DataStoreService } from '../../core-services/data-store.service';
@ -17,6 +17,10 @@ import { BaseAgendaViewModel } from 'app/site/base/base-agenda-view-model';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { BaseViewModel } from 'app/site/base/base-view-model'; import { BaseViewModel } from 'app/site/base/base-view-model';
import { BaseAgendaContentObjectRepository } from '../base-agenda-content-object-repository'; import { BaseAgendaContentObjectRepository } from '../base-agenda-content-object-repository';
import { Motion } from 'app/shared/models/motions/motion';
import { MotionBlock } from 'app/shared/models/motions/motion-block';
import { Topic } from 'app/shared/models/topics/topic';
import { Assignment } from 'app/shared/models/assignments/assignment';
/** /**
* Repository service for users * Repository service for users
@ -47,25 +51,18 @@ export class ItemRepositoryService extends BaseRepository<ViewItem, Item> {
private config: ConfigService, private config: ConfigService,
private treeService: TreeService private treeService: TreeService
) { ) {
super(DS, dataSend, mapperService, viewModelStoreService, translate, Item); super(DS, dataSend, mapperService, viewModelStoreService, translate, Item, [
Topic,
Assignment,
Motion,
MotionBlock
]);
} }
public getVerboseName = (plural: boolean = false) => { public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Items' : 'Item'); return this.translate.instant(plural ? 'Items' : 'Item');
}; };
protected setupDependencyObservation(): void {
this.DS.secondaryModelChangeSubject.subscribe(model => {
const viewModel = this.viewModelStoreService.get(model.collectionString, model.id);
const somethingChanged = this.getViewModelList().some(ownViewModel => {
return ownViewModel.updateDependencies(viewModel);
});
if (somethingChanged) {
this.updateAllObservables(model.id);
}
});
}
/** /**
* Creates the viewItem out of a given item * Creates the viewItem out of a given item
* *

View File

@ -3,7 +3,7 @@ import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { BaseAgendaContentObjectRepository } from '../base-agenda-content-object-repository'; import { BaseAgendaContentObjectRepository } from '../base-agenda-content-object-repository';
import { CollectionStringMapperService } from 'app/core/core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from 'app/core/core-services/collection-string-mapper.service';
import { DataStoreService } from 'app/core/core-services/data-store.service'; import { DataStoreService } from 'app/core/core-services/data-store.service';
import { DataSendService } from 'app/core/core-services/data-send.service'; import { DataSendService } from 'app/core/core-services/data-send.service';
import { Item } from 'app/shared/models/agenda/item'; import { Item } from 'app/shared/models/agenda/item';

View File

@ -5,7 +5,7 @@ import { TranslateService } from '@ngx-translate/core';
import { Assignment } from 'app/shared/models/assignments/assignment'; import { Assignment } from 'app/shared/models/assignments/assignment';
import { AssignmentRelatedUser } from 'app/shared/models/assignments/assignment-related-user'; import { AssignmentRelatedUser } from 'app/shared/models/assignments/assignment-related-user';
import { BaseAgendaContentObjectRepository } from '../base-agenda-content-object-repository'; import { BaseAgendaContentObjectRepository } from '../base-agenda-content-object-repository';
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
import { DataSendService } from 'app/core/core-services/data-send.service'; import { DataSendService } from 'app/core/core-services/data-send.service';
import { DataStoreService } from '../../core-services/data-store.service'; import { DataStoreService } from '../../core-services/data-store.service';
import { HttpService } from 'app/core/core-services/http.service'; import { HttpService } from 'app/core/core-services/http.service';

View File

@ -2,7 +2,7 @@ import { TranslateService } from '@ngx-translate/core';
import { BaseViewModel } from '../../site/base/base-view-model'; import { BaseViewModel } from '../../site/base/base-view-model';
import { BaseModel, ModelConstructor } from '../../shared/models/base/base-model'; import { BaseModel, ModelConstructor } from '../../shared/models/base/base-model';
import { CollectionStringMapperService } from '../core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from '../core-services/collection-string-mapper.service';
import { DataSendService } from '../core-services/data-send.service'; import { DataSendService } from '../core-services/data-send.service';
import { DataStoreService } from '../core-services/data-store.service'; import { DataStoreService } from '../core-services/data-store.service';
import { ViewModelStoreService } from '../core-services/view-model-store.service'; import { ViewModelStoreService } from '../core-services/view-model-store.service';

View File

@ -3,7 +3,7 @@ import { TranslateService } from '@ngx-translate/core';
import { BaseViewModel } from '../../site/base/base-view-model'; import { BaseViewModel } from '../../site/base/base-view-model';
import { BaseModel, ModelConstructor } from '../../shared/models/base/base-model'; import { BaseModel, ModelConstructor } from '../../shared/models/base/base-model';
import { CollectionStringMapperService } from '../core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from '../core-services/collection-string-mapper.service';
import { DataSendService } from '../core-services/data-send.service'; import { DataSendService } from '../core-services/data-send.service';
import { DataStoreService } from '../core-services/data-store.service'; import { DataStoreService } from '../core-services/data-store.service';
import { Identifiable } from '../../shared/models/base/identifiable'; import { Identifiable } from '../../shared/models/base/identifiable';
@ -11,6 +11,7 @@ import { auditTime } from 'rxjs/operators';
import { ViewModelStoreService } from '../core-services/view-model-store.service'; import { ViewModelStoreService } from '../core-services/view-model-store.service';
import { OnAfterAppsLoaded } from '../onAfterAppsLoaded'; import { OnAfterAppsLoaded } from '../onAfterAppsLoaded';
import { Collection } from 'app/shared/models/base/collection'; import { Collection } from 'app/shared/models/base/collection';
import { CollectionIds } from '../core-services/data-store-update-manager.service';
export abstract class BaseRepository<V extends BaseViewModel, M extends BaseModel> export abstract class BaseRepository<V extends BaseViewModel, M extends BaseModel>
implements OnAfterAppsLoaded, Collection { implements OnAfterAppsLoaded, Collection {
@ -107,6 +108,10 @@ export abstract class BaseRepository<V extends BaseViewModel, M extends BaseMode
this.updateViewModelListObservable(); this.updateViewModelListObservable();
}); });
this.loadInitialFromDS();
}
protected loadInitialFromDS(): void {
// Populate the local viewModelStore with ViewModel Objects. // Populate the local viewModelStore with ViewModel Objects.
this.DS.getAll(this.baseModelCtor).forEach((model: M) => { this.DS.getAll(this.baseModelCtor).forEach((model: M) => {
this.viewModelStore[model.id] = this.createViewModel(model); this.viewModelStore[model.id] = this.createViewModel(model);
@ -117,60 +122,71 @@ export abstract class BaseRepository<V extends BaseViewModel, M extends BaseMode
this.DS.getAll(this.baseModelCtor).forEach((model: M) => { this.DS.getAll(this.baseModelCtor).forEach((model: M) => {
this.updateViewModelObservable(model.id); this.updateViewModelObservable(model.id);
}); });
// Could be raise in error if the root injector is not known
this.DS.primaryModelChangeSubject.subscribe(model => {
if (model instanceof this.baseModelCtor) {
// Add new and updated motions to the viewModelStore
this.viewModelStore[model.id] = this.createViewModel(model as M);
this.updateAllObservables(model.id);
}
});
this.setupDependencyObservation();
// Watch the Observables for deleting
// TODO: What happens, if some related object was deleted?
// My quess: This must trigger an autoupdate also for this model, because some IDs changed, so the
// affected models will be newly created by the primaryModelChangeSubject.
this.DS.deletedObservable.subscribe(model => {
if (model.collection === this.collectionStringMapperService.getCollectionString(this.baseModelCtor)) {
delete this.viewModelStore[model.id];
this.updateAllObservables(model.id);
}
});
} }
/** /**
* Sets up the observation of dependency subjects. * Deletes all models from the repository (internally, no requests). Informs all subjects.
*/
protected setupDependencyObservation(): void {
if (this.depsModelCtors) {
this.DS.secondaryModelChangeSubject.subscribe(model => {
const dependencyChanged: boolean = this.depsModelCtors.some(ctor => {
return model instanceof ctor;
});
if (dependencyChanged) {
const viewModel = this.viewModelStoreService.get(model.collectionString, model.id);
this.updateDependency(viewModel);
}
});
}
}
/**
* Updates all models with the provided `update` which is a dependency.
* *
* @param update The dependency to update. * @param ids All model ids
*/ */
protected updateDependency(update: BaseViewModel): void { public deleteModels(ids: number[]): void {
// if an domain object we need was added or changed, update viewModelStore ids.forEach(id => {
this.getViewModelList().forEach(ownViewModel => { delete this.viewModelStore[id];
ownViewModel.updateDependencies(update); this.updateViewModelObservable(id);
});
this.updateViewModelListObservable();
}
/**
* Updates or creates all given models in the repository (internally, no requests).
* Informs all subjects.
*
* @param ids All model ids.
*/
public changedModels(ids: number[]): void {
ids.forEach(id => {
this.viewModelStore[id] = this.createViewModel(this.DS.get(this.collectionString, id));
this.updateViewModelObservable(id);
});
this.updateViewModelListObservable();
}
/**
* Updates all models in this repository with all changed models.
*
* @param changedModels A mapping of collections to ids of all changed models.
*/
public updateDependencies(changedModels: CollectionIds): void {
if (!this.depsModelCtors || this.depsModelCtors.length === 0) {
return;
}
// Get all viewModels from this repo once.
const viewModels = this.getViewModelList();
let somethingUpdated = false;
Object.keys(changedModels).forEach(collection => {
const dependencyChanged: boolean = this.depsModelCtors.some(ctor => {
return ctor.COLLECTIONSTRING === collection;
});
if (collection === this.collectionString || !dependencyChanged) {
return;
}
// Ok, we are affected by this collection. Update all viewModels from this repo.
viewModels.forEach(ownViewModel => {
changedModels[collection].forEach(id => {
ownViewModel.updateDependencies(this.viewModelStoreService.get(collection, id));
});
});
somethingUpdated = true;
});
if (somethingUpdated) {
viewModels.forEach(ownViewModel => {
this.updateViewModelObservable(ownViewModel.id); this.updateViewModelObservable(ownViewModel.id);
}); });
this.updateViewModelListObservable(); this.updateViewModelListObservable();
} }
}
/** /**
* Saves the (full) update to an existing model. So called "update"-function * Saves the (full) update to an existing model. So called "update"-function

View File

@ -10,7 +10,7 @@ import { DataStoreService } from 'app/core/core-services/data-store.service';
import { ConstantsService } from 'app/core/core-services/constants.service'; import { ConstantsService } from 'app/core/core-services/constants.service';
import { HttpService } from 'app/core/core-services/http.service'; import { HttpService } from 'app/core/core-services/http.service';
import { Identifiable } from 'app/shared/models/base/identifiable'; import { Identifiable } from 'app/shared/models/base/identifiable';
import { CollectionStringMapperService } from 'app/core/core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from 'app/core/core-services/collection-string-mapper.service';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { ViewConfig } from 'app/site/config/models/view-config'; import { ViewConfig } from 'app/site/config/models/view-config';
@ -88,6 +88,16 @@ export class ConfigRepositoryService extends BaseRepository<ViewConfig, Config>
*/ */
protected configListSubject: BehaviorSubject<ConfigGroup[]> = new BehaviorSubject<ConfigGroup[]>(null); protected configListSubject: BehaviorSubject<ConfigGroup[]> = new BehaviorSubject<ConfigGroup[]>(null);
/**
* Saves, if we got config variables (the structure) from the server.
*/
protected gotConfigsVariables = false;
/**
* Saves, if we got first configs via autoupdate or cache.
*/
protected gotFirstUpdate = false;
/** /**
* Constructor for ConfigRepositoryService. Requests the constants from the server and creates the config group structure. * Constructor for ConfigRepositoryService. Requests the constants from the server and creates the config group structure.
* *
@ -109,7 +119,9 @@ export class ConfigRepositoryService extends BaseRepository<ViewConfig, Config>
this.constantsService.get('ConfigVariables').subscribe(constant => { this.constantsService.get('ConfigVariables').subscribe(constant => {
this.createConfigStructure(constant); this.createConfigStructure(constant);
this.updateConfigStructure(true, ...Object.values(this.viewModelStore)); this.updateConfigStructure(false, ...Object.values(this.viewModelStore));
this.gotConfigsVariables = true;
this.checkConfigStructure();
this.updateConfigListObservable(); this.updateConfigListObservable();
}); });
} }
@ -146,30 +158,23 @@ export class ConfigRepositoryService extends BaseRepository<ViewConfig, Config>
throw new Error('Config variables cannot be created'); throw new Error('Config variables cannot be created');
} }
/** protected loadInitialFromDS(): void {
* Overwritten setup. Does only care about the custom list observable and inserts changed configs into the
* config group structure.
*/
public onAfterAppsLoaded(): void {
if (!this.configListSubject) {
this.configListSubject = new BehaviorSubject<ConfigGroup[]>(null);
}
this.DS.getAll(Config).forEach((config: Config) => { this.DS.getAll(Config).forEach((config: Config) => {
this.viewModelStore[config.id] = this.createViewModel(config); this.viewModelStore[config.id] = this.createViewModel(config);
this.updateConfigStructure(false, this.viewModelStore[config.id]); this.updateConfigStructure(false, this.viewModelStore[config.id]);
}); });
this.updateConfigListObservable(); this.updateConfigListObservable();
// Could be raise in error if the root injector is not known
// TODO go over repo
this.DS.changeObservable.subscribe(model => {
if (model instanceof Config) {
this.viewModelStore[model.id] = this.createViewModel(model as Config);
this.updateConfigStructure(false, this.viewModelStore[model.id]);
this.updateConfigListObservable();
} }
public changedModels(ids: number[]): void {
super.changedModels(ids);
ids.forEach(id => {
this.updateConfigStructure(false, this.viewModelStore[id]);
}); });
this.gotFirstUpdate = true;
this.checkConfigStructure();
this.updateConfigListObservable();
} }
/** /**
@ -195,6 +200,16 @@ export class ConfigRepositoryService extends BaseRepository<ViewConfig, Config>
return this.configs; return this.configs;
} }
/**
* Checks the config structure, if we got configs (first data) and the
* structure (config variables)
*/
protected checkConfigStructure(): void {
if (this.gotConfigsVariables && this.gotFirstUpdate) {
this.updateConfigStructure(true, ...Object.values(this.viewModelStore));
}
}
/** /**
* With a given (and maybe partially filled) config structure, all given view configs are put into it. * With a given (and maybe partially filled) config structure, all given view configs are put into it.
* @param check Whether to check, if all given configs are there (according to the config structure). * @param check Whether to check, if all given configs are there (according to the config structure).

View File

@ -1,13 +1,12 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CollectionStringMapperService } from 'app/core/core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from 'app/core/core-services/collection-string-mapper.service';
import { DataStoreService } from 'app/core/core-services/data-store.service'; import { DataStoreService } from 'app/core/core-services/data-store.service';
import { BaseRepository } from 'app/core/repositories/base-repository'; import { BaseRepository } from 'app/core/repositories/base-repository';
import { History } from 'app/shared/models/core/history'; import { History } from 'app/shared/models/core/history';
import { Identifiable } from 'app/shared/models/base/identifiable'; import { Identifiable } from 'app/shared/models/base/identifiable';
import { User } from 'app/shared/models/users/user';
import { HttpService } from 'app/core/core-services/http.service'; import { HttpService } from 'app/core/core-services/http.service';
import { ViewHistory } from 'app/site/history/models/view-history'; import { ViewHistory, ProxyHistory } from 'app/site/history/models/view-history';
import { TimeTravelService } from 'app/core/core-services/time-travel.service'; import { TimeTravelService } from 'app/core/core-services/time-travel.service';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { ViewUser } from 'app/site/users/models/view-user'; import { ViewUser } from 'app/site/users/models/view-user';
@ -40,7 +39,7 @@ export class HistoryRepositoryService extends BaseRepository<ViewHistory, Histor
private httpService: HttpService, private httpService: HttpService,
private timeTravel: TimeTravelService private timeTravel: TimeTravelService
) { ) {
super(DS, dataSend, mapperService, viewModelStoreService, translate, History, [User]); super(DS, dataSend, mapperService, viewModelStoreService, translate, History);
} }
public getVerboseName = (plural: boolean = false) => { public getVerboseName = (plural: boolean = false) => {
@ -54,12 +53,29 @@ export class HistoryRepositoryService extends BaseRepository<ViewHistory, Histor
* @return a new ViewHistory object * @return a new ViewHistory object
*/ */
public createViewModel(history: History): ViewHistory { public createViewModel(history: History): ViewHistory {
const user = this.viewModelStoreService.get(ViewUser, history.user_id); const viewHistory = new ViewHistory(this.createProxyHistory(history));
const viewHistory = new ViewHistory(history, user);
viewHistory.getVerboseName = this.getVerboseName; viewHistory.getVerboseName = this.getVerboseName;
return viewHistory; return viewHistory;
} }
/**
* Creates a ProxyHistory from a History by wrapping it and give access to the user.
*
* @param history The History object
* @returns the ProxyHistory
*/
private createProxyHistory(history: History): ProxyHistory {
return new Proxy(history, {
get: (instance, property) => {
if (property === 'user') {
return this.viewModelStoreService.get(ViewUser, instance.user_id);
} else {
return instance[property];
}
}
});
}
/** /**
* Overwrites the default procedure * Overwrites the default procedure
* *

View File

@ -6,7 +6,7 @@ import { Mediafile } from 'app/shared/models/mediafiles/mediafile';
import { User } from 'app/shared/models/users/user'; import { User } from 'app/shared/models/users/user';
import { DataStoreService } from '../../core-services/data-store.service'; import { DataStoreService } from '../../core-services/data-store.service';
import { Identifiable } from 'app/shared/models/base/identifiable'; import { Identifiable } from 'app/shared/models/base/identifiable';
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
import { DataSendService } from 'app/core/core-services/data-send.service'; import { DataSendService } from 'app/core/core-services/data-send.service';
import { HttpService } from 'app/core/core-services/http.service'; import { HttpService } from 'app/core/core-services/http.service';
import { HttpHeaders } from '@angular/common/http'; import { HttpHeaders } from '@angular/common/http';

View File

@ -4,7 +4,7 @@ import { TranslateService } from '@ngx-translate/core';
import { BaseRepository } from '../base-repository'; import { BaseRepository } from '../base-repository';
import { Category } from 'app/shared/models/motions/category'; import { Category } from 'app/shared/models/motions/category';
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
import { ConfigService } from 'app/core/ui-services/config.service'; import { ConfigService } from 'app/core/ui-services/config.service';
import { DataSendService } from '../../core-services/data-send.service'; import { DataSendService } from '../../core-services/data-send.service';
import { DataStoreService } from '../../core-services/data-store.service'; import { DataStoreService } from '../../core-services/data-store.service';

View File

@ -13,7 +13,7 @@ import { DataStoreService } from 'app/core/core-services/data-store.service';
import { MotionChangeRecommendation } from 'app/shared/models/motions/motion-change-reco'; import { MotionChangeRecommendation } from 'app/shared/models/motions/motion-change-reco';
import { ViewMotionChangeRecommendation } from 'app/site/motions/models/view-change-recommendation'; import { ViewMotionChangeRecommendation } from 'app/site/motions/models/view-change-recommendation';
import { Identifiable } from 'app/shared/models/base/identifiable'; import { Identifiable } from 'app/shared/models/base/identifiable';
import { CollectionStringMapperService } from 'app/core/core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from 'app/core/core-services/collection-string-mapper.service';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
/** /**

View File

@ -4,7 +4,7 @@ import { Observable } from 'rxjs';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { CollectionStringMapperService } from 'app/core/core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from 'app/core/core-services/collection-string-mapper.service';
import { DataSendService } from 'app/core/core-services/data-send.service'; import { DataSendService } from 'app/core/core-services/data-send.service';
import { DataStoreService } from 'app/core/core-services/data-store.service'; import { DataStoreService } from 'app/core/core-services/data-store.service';
import { HttpService } from 'app/core/core-services/http.service'; import { HttpService } from 'app/core/core-services/http.service';

View File

@ -6,7 +6,7 @@ import { BaseRepository } from '../base-repository';
import { ViewMotionCommentSection } from 'app/site/motions/models/view-motion-comment-section'; import { ViewMotionCommentSection } from 'app/site/motions/models/view-motion-comment-section';
import { MotionCommentSection } from 'app/shared/models/motions/motion-comment-section'; import { MotionCommentSection } from 'app/shared/models/motions/motion-comment-section';
import { Group } from 'app/shared/models/users/group'; import { Group } from 'app/shared/models/users/group';
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
import { HttpService } from 'app/core/core-services/http.service'; import { HttpService } from 'app/core/core-services/http.service';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { ViewGroup } from 'app/site/users/models/view-group'; import { ViewGroup } from 'app/site/users/models/view-group';

View File

@ -7,7 +7,7 @@ import { tap, map } from 'rxjs/operators';
import { Category } from 'app/shared/models/motions/category'; import { Category } from 'app/shared/models/motions/category';
import { ChangeRecoMode, ViewMotion } from 'app/site/motions/models/view-motion'; import { ChangeRecoMode, ViewMotion } from 'app/site/motions/models/view-motion';
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
import { ConfigService } from 'app/core/ui-services/config.service'; import { ConfigService } from 'app/core/ui-services/config.service';
import { DataSendService } from '../../core-services/data-send.service'; import { DataSendService } from '../../core-services/data-send.service';
@ -39,10 +39,10 @@ import { ViewMotionBlock } from 'app/site/motions/models/view-motion-block';
import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile'; import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
import { ViewTag } from 'app/site/tags/models/view-tag'; import { ViewTag } from 'app/site/tags/models/view-tag';
import { BaseAgendaContentObjectRepository } from '../base-agenda-content-object-repository'; import { BaseAgendaContentObjectRepository } from '../base-agenda-content-object-repository';
import { BaseViewModel } from 'app/site/base/base-view-model';
import { PersonalNote, PersonalNoteContent } from 'app/shared/models/users/personal-note'; import { PersonalNote, PersonalNoteContent } from 'app/shared/models/users/personal-note';
import { ViewPersonalNote } from 'app/site/users/models/view-personal-note'; import { ViewPersonalNote } from 'app/site/users/models/view-personal-note';
import { OperatorService } from 'app/core/core-services/operator.service'; import { OperatorService } from 'app/core/core-services/operator.service';
import { CollectionIds } from 'app/core/core-services/data-store-update-manager.service';
type SortProperty = 'callListWeight' | 'identifier'; type SortProperty = 'callListWeight' | 'identifier';
@ -261,30 +261,48 @@ export class MotionRepositoryService extends BaseAgendaContentObjectRepository<V
} }
/** /**
* * Special handling of updating personal notes.
* @param update * @override
*
* @overwrite
*/ */
protected updateDependency(update: BaseViewModel): void { public updateDependencies(changedModels: CollectionIds): void {
if (update instanceof ViewPersonalNote) { if (!this.depsModelCtors || this.depsModelCtors.length === 0) {
if (this.operator.isAnonymous || update.userId !== this.operator.user.id) {
return; return;
} }
const notes = update.notes; const viewModels = this.getViewModelList();
const collection = Motion.COLLECTIONSTRING; let somethingUpdated = false;
Object.keys(changedModels).forEach(collection => {
this.getViewModelList().forEach(ownViewModel => { const dependencyChanged: boolean = this.depsModelCtors.some(ctor => {
if (notes && notes[collection] && notes[collection][ownViewModel.id]) { return ctor.COLLECTIONSTRING === collection;
ownViewModel.personalNote = notes[collection][ownViewModel.id]; });
} else { if (collection === this.collectionString || !dependencyChanged) {
ownViewModel.personalNote = null; return;
} }
// Do not update personal notes, if the operator is anonymous
if (collection === PersonalNote.COLLECTIONSTRING && this.operator.isAnonymous) {
return;
}
viewModels.forEach(ownViewModel => {
changedModels[collection].forEach(id => {
const viewModel = this.viewModelStoreService.get(collection, id);
// Only update the personal note, if the operator is the right user.
if (
collection === PersonalNote.COLLECTIONSTRING &&
(<ViewPersonalNote>viewModel).userId !== this.operator.user.id
) {
return;
}
ownViewModel.updateDependencies(viewModel);
});
});
somethingUpdated = true;
});
if (somethingUpdated) {
viewModels.forEach(ownViewModel => {
this.updateViewModelObservable(ownViewModel.id); this.updateViewModelObservable(ownViewModel.id);
}); });
this.updateViewModelListObservable(); this.updateViewModelListObservable();
} else {
super.updateDependency(update);
} }
} }

View File

@ -5,7 +5,7 @@ import { DataStoreService } from '../../core-services/data-store.service';
import { BaseRepository } from '../base-repository'; import { BaseRepository } from '../base-repository';
import { ViewStatuteParagraph } from 'app/site/motions/models/view-statute-paragraph'; import { ViewStatuteParagraph } from 'app/site/motions/models/view-statute-paragraph';
import { StatuteParagraph } from 'app/shared/models/motions/statute-paragraph'; import { StatuteParagraph } from 'app/shared/models/motions/statute-paragraph';
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';

View File

@ -5,7 +5,7 @@ import { ViewWorkflow } from 'app/site/motions/models/view-workflow';
import { DataSendService } from '../../core-services/data-send.service'; import { DataSendService } from '../../core-services/data-send.service';
import { DataStoreService } from '../../core-services/data-store.service'; import { DataStoreService } from '../../core-services/data-store.service';
import { BaseRepository } from '../base-repository'; import { BaseRepository } from '../base-repository';
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
import { WorkflowState } from 'app/shared/models/motions/workflow-state'; import { WorkflowState } from 'app/shared/models/motions/workflow-state';
import { ViewMotion } from 'app/site/motions/models/view-motion'; import { ViewMotion } from 'app/site/motions/models/view-motion';
import { HttpService } from 'app/core/core-services/http.service'; import { HttpService } from 'app/core/core-services/http.service';

View File

@ -5,7 +5,7 @@ import { TranslateService } from '@ngx-translate/core';
import { DataSendService } from '../../core-services/data-send.service'; import { DataSendService } from '../../core-services/data-send.service';
import { DataStoreService } from '../../core-services/data-store.service'; import { DataStoreService } from '../../core-services/data-store.service';
import { BaseRepository } from '../base-repository'; import { BaseRepository } from '../base-repository';
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
import { ViewCountdown } from 'app/site/projector/models/view-countdown'; import { ViewCountdown } from 'app/site/projector/models/view-countdown';
import { Countdown } from 'app/shared/models/core/countdown'; import { Countdown } from 'app/shared/models/core/countdown';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';

View File

@ -3,7 +3,7 @@ import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { BaseRepository } from '../base-repository'; import { BaseRepository } from '../base-repository';
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
import { DataSendService } from '../../core-services/data-send.service'; import { DataSendService } from '../../core-services/data-send.service';
import { DataStoreService } from '../../core-services/data-store.service'; import { DataStoreService } from '../../core-services/data-store.service';
import { Identifiable } from 'app/shared/models/base/identifiable'; import { Identifiable } from 'app/shared/models/base/identifiable';

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { DataStoreService } from '../../core-services/data-store.service'; import { DataStoreService } from '../../core-services/data-store.service';
import { BaseRepository } from '../base-repository'; import { BaseRepository } from '../base-repository';
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
import { ProjectorMessage } from 'app/shared/models/core/projector-message'; import { ProjectorMessage } from 'app/shared/models/core/projector-message';
import { ViewProjectorMessage } from 'app/site/projector/models/view-projector-message'; import { ViewProjectorMessage } from 'app/site/projector/models/view-projector-message';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { BaseRepository } from '../base-repository'; import { BaseRepository } from '../base-repository';
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
import { DataSendService } from '../../core-services/data-send.service'; import { DataSendService } from '../../core-services/data-send.service';
import { DataStoreService } from '../../core-services/data-store.service'; import { DataStoreService } from '../../core-services/data-store.service';
import { Identifiable } from 'app/shared/models/base/identifiable'; import { Identifiable } from 'app/shared/models/base/identifiable';

View File

@ -5,7 +5,7 @@ import { ViewTag } from 'app/site/tags/models/view-tag';
import { DataSendService } from '../../core-services/data-send.service'; import { DataSendService } from '../../core-services/data-send.service';
import { DataStoreService } from '../../core-services/data-store.service'; import { DataStoreService } from '../../core-services/data-store.service';
import { BaseRepository } from '../base-repository'; import { BaseRepository } from '../base-repository';
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { BaseRepository } from '../base-repository'; import { BaseRepository } from '../base-repository';
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
import { ConstantsService } from '../../core-services/constants.service'; import { ConstantsService } from '../../core-services/constants.service';
import { DataSendService } from '../../core-services/data-send.service'; import { DataSendService } from '../../core-services/data-send.service';
import { DataStoreService } from '../../core-services/data-store.service'; import { DataStoreService } from '../../core-services/data-store.service';

View File

@ -5,7 +5,7 @@ import { TranslateService } from '@ngx-translate/core';
import { DataSendService } from 'app/core/core-services/data-send.service'; import { DataSendService } from 'app/core/core-services/data-send.service';
import { DataStoreService } from '../../core-services/data-store.service'; import { DataStoreService } from '../../core-services/data-store.service';
import { BaseRepository } from '../base-repository'; import { BaseRepository } from '../base-repository';
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
import { Identifiable } from 'app/shared/models/base/identifiable'; import { Identifiable } from 'app/shared/models/base/identifiable';
import { PersonalNote } from 'app/shared/models/users/personal-note'; import { PersonalNote } from 'app/shared/models/users/personal-note';
import { ViewPersonalNote } from 'app/site/users/models/view-personal-note'; import { ViewPersonalNote } from 'app/site/users/models/view-personal-note';

View File

@ -3,7 +3,7 @@ import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { BaseRepository } from '../base-repository'; import { BaseRepository } from '../base-repository';
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
import { ConfigService } from 'app/core/ui-services/config.service'; import { ConfigService } from 'app/core/ui-services/config.service';
import { DataSendService } from '../../core-services/data-send.service'; import { DataSendService } from '../../core-services/data-send.service';
import { DataStoreService } from '../../core-services/data-store.service'; import { DataStoreService } from '../../core-services/data-store.service';

View File

@ -33,9 +33,9 @@ export class ConfigService {
* Listen for changes of config variables. * Listen for changes of config variables.
*/ */
public constructor(private DS: DataStoreService) { public constructor(private DS: DataStoreService) {
this.DS.changeObservable.subscribe(data => { this.DS.getChangeObservable(Config).subscribe(data => {
// on changes notify the observers for specific keys. // on changes notify the observers for specific keys.
if (data instanceof Config && this.configSubjects[data.key]) { if (this.configSubjects[data.key]) {
this.configSubjects[data.key].next(data.value); this.configSubjects[data.key].next(data.value);
} }
}); });

View File

@ -24,10 +24,8 @@ export class PersonalNoteService {
*/ */
public constructor(private operator: OperatorService, private DS: DataStoreService, private http: HttpService) { public constructor(private operator: OperatorService, private DS: DataStoreService, private http: HttpService) {
operator.getUserObservable().subscribe(() => this.updatePersonalNoteObject()); operator.getUserObservable().subscribe(() => this.updatePersonalNoteObject());
this.DS.changeObservable.subscribe(model => { this.DS.getChangeObservable(PersonalNote).subscribe(_ => {
if (model instanceof PersonalNote) {
this.updatePersonalNoteObject(); this.updatePersonalNoteObject();
}
}); });
} }

View File

@ -21,7 +21,7 @@ import { UserRepositoryService } from 'app/core/repositories/users/user-reposito
import { DurationService } from 'app/core/ui-services/duration.service'; import { DurationService } from 'app/core/ui-services/duration.service';
import { CurrentAgendaItemService } from 'app/site/projector/services/current-agenda-item.service'; import { CurrentAgendaItemService } from 'app/site/projector/services/current-agenda-item.service';
import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service'; import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service';
import { CollectionStringMapperService } from 'app/core/core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from 'app/core/core-services/collection-string-mapper.service';
import { CurrentListOfSpeakersSlideService } from 'app/site/projector/services/current-list-of-of-speakers-slide.service'; import { CurrentListOfSpeakersSlideService } from 'app/site/projector/services/current-list-of-of-speakers-slide.service';
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable'; import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';

View File

@ -80,7 +80,7 @@ export class SearchComponent extends BaseViewComponent implements OnInit {
this.registeredModels = this.searchService.getRegisteredModels().map(rm => ({ ...rm, enabled: true })); this.registeredModels = this.searchService.getRegisteredModels().map(rm => ({ ...rm, enabled: true }));
this.DS.changedOrDeletedObservable.pipe(auditTime(100)).subscribe(() => this.search()); this.DS.modifiedObservable.pipe(auditTime(100)).subscribe(() => this.search());
this.quickSearchSubject.pipe(debounceTime(250)).subscribe(query => this.search(query)); this.quickSearchSubject.pipe(debounceTime(250)).subscribe(query => this.search(query));
} }

View File

@ -49,7 +49,7 @@
<!-- User --> <!-- User -->
<ng-container matColumnDef="user"> <ng-container matColumnDef="user">
<mat-header-cell *matHeaderCellDef translate>Changed by</mat-header-cell> <mat-header-cell *matHeaderCellDef translate>Changed by</mat-header-cell>
<mat-cell *matCellDef="let history">{{ history.user }}</mat-cell> <mat-cell *matCellDef="let history">{{ history.user_full_name }}</mat-cell>
</ng-container> </ng-container>
<mat-header-row *matHeaderRowDef="getRowDef()"></mat-header-row> <mat-header-row *matHeaderRowDef="getRowDef()"></mat-header-row>

View File

@ -2,6 +2,8 @@ import { BaseViewModel } from 'app/site/base/base-view-model';
import { History } from 'app/shared/models/core/history'; import { History } from 'app/shared/models/core/history';
import { ViewUser } from 'app/site/users/models/view-user'; import { ViewUser } from 'app/site/users/models/view-user';
export type ProxyHistory = History & { user?: ViewUser };
/** /**
* View model for history objects * View model for history objects
*/ */
@ -11,38 +13,39 @@ export class ViewHistory extends BaseViewModel {
/** /**
* Private BaseModel of the history * Private BaseModel of the history
*/ */
private _history: History; private _history: ProxyHistory;
/**
* Real representation of the user who altered the history.
* Determined from `History.user_id`
*/
private _user: ViewUser | null;
/** /**
* Read the history property * Read the history property
*/ */
public get history(): History { public get history(): ProxyHistory {
return this._history; return this._history;
} }
/** /**
* Read the user property * Gets the users ViewUser.
*/ */
public get user(): ViewUser { public get user(): ViewUser | null {
return this._user ? this._user : null; return this.history.user;
} }
/** /**
* Get the ID of the history object * Get the id of the history object
* Required by BaseViewModel * Required by BaseViewModel
* *
* @returns the ID as number * @returns the id as number
*/ */
public get id(): number { public get id(): number {
return this.history.id; return this.history.id;
} }
/**
* @returns the users full name
*/
public get user_full_name(): string {
return this.history.user ? this.history.user.full_name : '';
}
/** /**
* Get the elementIDs of the history object * Get the elementIDs of the history object
* *
@ -81,10 +84,9 @@ export class ViewHistory extends BaseViewModel {
* @param history the real history BaseModel * @param history the real history BaseModel
* @param user the real user BaseModel * @param user the real user BaseModel
*/ */
public constructor(history: History, user?: ViewUser) { public constructor(history: ProxyHistory) {
super(History.COLLECTIONSTRING); super(History.COLLECTIONSTRING);
this._history = history; this._history = history;
this._user = user;
} }
/** /**
@ -117,14 +119,5 @@ export class ViewHistory extends BaseViewModel {
return this.history; return this.history;
} }
/** public updateDependencies(update: BaseViewModel): void {}
* Updates the history object with new values
*
* @param update potentially the new values for history or it's components.
*/
public updateDependencies(update: BaseViewModel): void {
if (update instanceof ViewUser && this.history.user_id === update.id) {
this._user = update;
}
}
} }

View File

@ -2,7 +2,7 @@ import { Component } from '@angular/core';
import { BaseSlideComponent } from 'app/slides/base-slide-component'; import { BaseSlideComponent } from 'app/slides/base-slide-component';
import { CommonListOfSpeakersSlideData } from './common-list-of-speakers-slide-data'; import { CommonListOfSpeakersSlideData } from './common-list-of-speakers-slide-data';
import { CollectionStringMapperService } from 'app/core/core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from 'app/core/core-services/collection-string-mapper.service';
import { isBaseAgendaContentObjectRepository } from 'app/core/repositories/base-agenda-content-object-repository'; import { isBaseAgendaContentObjectRepository } from 'app/core/repositories/base-agenda-content-object-repository';
@Component({ @Component({

View File

@ -1,7 +1,7 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { BaseSlideComponent } from 'app/slides/base-slide-component'; import { BaseSlideComponent } from 'app/slides/base-slide-component';
import { ItemListSlideData, SlideItem } from './item-list-slide-data'; import { ItemListSlideData, SlideItem } from './item-list-slide-data';
import { CollectionStringMapperService } from 'app/core/core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from 'app/core/core-services/collection-string-mapper.service';
import { isBaseAgendaContentObjectRepository } from 'app/core/repositories/base-agenda-content-object-repository'; import { isBaseAgendaContentObjectRepository } from 'app/core/repositories/base-agenda-content-object-repository';
@Component({ @Component({