Refresh clients cache when the database was migrated
This commit is contained in:
parent
e9a60a54fd
commit
a715c0e432
@ -4,13 +4,14 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { take, filter } from 'rxjs/operators';
|
||||
|
||||
import { ConfigService } from './core/ui-services/config.service';
|
||||
import { ConstantsService } from './core/ui-services/constants.service';
|
||||
import { ConstantsService } from './core/core-services/constants.service';
|
||||
import { CountUsersService } from './core/ui-services/count-users.service';
|
||||
import { LoadFontService } from './core/ui-services/load-font.service';
|
||||
import { LoginDataService } from './core/ui-services/login-data.service';
|
||||
import { OperatorService } from './core/core-services/operator.service';
|
||||
import { ServertimeService } from './core/core-services/servertime.service';
|
||||
import { ThemeService } from './core/ui-services/theme.service';
|
||||
import { DataStoreUpgradeService } from './core/core-services/data-store-upgrade.service';
|
||||
|
||||
/**
|
||||
* Angular's global App Component
|
||||
@ -48,7 +49,8 @@ export class AppComponent {
|
||||
themeService: ThemeService,
|
||||
countUsersService: CountUsersService, // Needed to register itself.
|
||||
configService: ConfigService,
|
||||
loadFontService: LoadFontService
|
||||
loadFontService: LoadFontService,
|
||||
dataStoreUpgradeService: DataStoreUpgradeService // to start it.
|
||||
) {
|
||||
// manually add the supported languages
|
||||
translate.addLangs(['en', 'de', 'cs']);
|
||||
|
@ -145,4 +145,22 @@ export class AutoupdateService {
|
||||
console.log('requesting changed objects with DS max change id', this.DS.maxChangeId + 1);
|
||||
this.websocketService.send('getElements', { change_id: this.DS.maxChangeId + 1 });
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a full update: Requests all data from the server and sets the DS to the fresh data.
|
||||
*/
|
||||
public async doFullUpdate(): Promise<void> {
|
||||
const response = await this.websocketService.sendAndGetResponse<{}, AutoupdateFormat>('getElements', {});
|
||||
|
||||
let allModels: BaseModel[] = [];
|
||||
for (const collection of Object.keys(response.changed)) {
|
||||
if (this.modelMapper.isCollectionRegistered(collection)) {
|
||||
allModels = allModels.concat(this.mapObjectsToBaseModels(collection, response.changed[collection]));
|
||||
} else {
|
||||
console.error(`Unregistered collection "${collection}". Ignore it.`);
|
||||
}
|
||||
}
|
||||
|
||||
await this.DS.set(allModels, response.to_change_id);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { WebsocketService } from '../core-services/websocket.service';
|
||||
import { WebsocketService } from './websocket.service';
|
||||
import { Observable, of, Subject } from 'rxjs';
|
||||
|
||||
/**
|
||||
@ -15,7 +15,7 @@ interface Constants {
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* this.constantsService.get('OpenSlidesSettings').subscribe(constant => {
|
||||
* this.constantsService.get('Settings').subscribe(constant => {
|
||||
* console.log(constant);
|
||||
* });
|
||||
* ```
|
@ -0,0 +1,17 @@
|
||||
import { TestBed, inject } from '@angular/core/testing';
|
||||
|
||||
import { E2EImportsModule } from '../../../e2e-imports.module';
|
||||
import { DataStoreUpgradeService } from './data-store-upgrade.service';
|
||||
|
||||
describe('DataStoreUpgradeService', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [E2EImportsModule],
|
||||
providers: [DataStoreUpgradeService]
|
||||
});
|
||||
});
|
||||
|
||||
it('should be created', inject([DataStoreUpgradeService], (service: DataStoreUpgradeService) => {
|
||||
expect(service).toBeTruthy();
|
||||
}));
|
||||
});
|
@ -0,0 +1,34 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { ConstantsService } from './constants.service';
|
||||
import { AutoupdateService } from './autoupdate.service';
|
||||
import { StorageService } from './storage.service';
|
||||
|
||||
const MIGRATIONVERSION = 'MigrationVersion';
|
||||
|
||||
/**
|
||||
* Manages upgrading the DataStore, if the migration version from the server is higher than the current one.
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class DataStoreUpgradeService {
|
||||
/**
|
||||
* @param autoupdateService
|
||||
* @param constantsService
|
||||
* @param storageService
|
||||
*/
|
||||
public constructor(
|
||||
autoupdateService: AutoupdateService,
|
||||
constantsService: ConstantsService,
|
||||
storageService: StorageService
|
||||
) {
|
||||
constantsService.get<number>(MIGRATIONVERSION).subscribe(async version => {
|
||||
const currentVersion = await storageService.get<number>(MIGRATIONVERSION);
|
||||
await storageService.set(MIGRATIONVERSION, version);
|
||||
if (currentVersion && currentVersion !== version) {
|
||||
autoupdateService.doFullUpdate();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -10,12 +10,26 @@ import { formatQueryParams, QueryParams } from '../query-params';
|
||||
/**
|
||||
* The generic message format in which messages are send and recieved by the server.
|
||||
*/
|
||||
interface WebsocketMessage {
|
||||
interface BaseWebsocketMessage {
|
||||
type: string;
|
||||
content: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outgoing messages must have an id.
|
||||
*/
|
||||
interface OutgoingWebsocketMessage extends BaseWebsocketMessage {
|
||||
id: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Incomming messages may have an `in_response`, if they are an answer to a previously
|
||||
* submitted request.
|
||||
*/
|
||||
interface IncommingWebsocketMessage extends BaseWebsocketMessage {
|
||||
in_response?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service that handles WebSocket connections. Other services can register themselfs
|
||||
* with {@method getOberservable} for a specific type of messages. The content will be published.
|
||||
@ -89,6 +103,8 @@ export class WebsocketService {
|
||||
*/
|
||||
private subjects: { [type: string]: Subject<any> } = {};
|
||||
|
||||
private responseCallbacks: { [id: string]: [(val: any) => boolean, (error: string) => void | null] } = {};
|
||||
|
||||
/**
|
||||
* Saves, if the WS Connection should be closed (e.g. after an explicit `close()`). Prohibits
|
||||
* retry connection attempts.
|
||||
@ -180,16 +196,7 @@ export class WebsocketService {
|
||||
|
||||
this.websocket.onmessage = (event: MessageEvent) => {
|
||||
this.zone.run(() => {
|
||||
const message: WebsocketMessage = JSON.parse(event.data);
|
||||
const type: string = message.type;
|
||||
if (type === 'error') {
|
||||
console.error('Websocket error', message.content);
|
||||
} else if (this.subjects[type]) {
|
||||
// Pass the content to the registered subscribers.
|
||||
this.subjects[type].next(message.content);
|
||||
} else {
|
||||
console.log(`Got unknown websocket message type "${type}" with content`, message.content);
|
||||
}
|
||||
this.handleMessage(event.data);
|
||||
});
|
||||
};
|
||||
|
||||
@ -234,6 +241,48 @@ export class WebsocketService {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an incomming message.
|
||||
*
|
||||
* @param data The message
|
||||
*/
|
||||
private handleMessage(data: string): void {
|
||||
const message: IncommingWebsocketMessage = JSON.parse(data);
|
||||
const type = message.type;
|
||||
const inResponse = message.in_response;
|
||||
const callbacks = this.responseCallbacks[inResponse];
|
||||
if (callbacks) {
|
||||
delete this.responseCallbacks[inResponse];
|
||||
}
|
||||
|
||||
if (type === 'error') {
|
||||
console.error('Websocket error', message.content);
|
||||
if (inResponse && callbacks && callbacks[1]) {
|
||||
callbacks[1](message.content as string);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to fire a response callback directly. If it returnes true, the message is handeled
|
||||
// and not distributed further
|
||||
if (inResponse && callbacks && callbacks[0](message.content)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.subjects[type]) {
|
||||
// Pass the content to the registered subscribers.
|
||||
this.subjects[type].next(message.content);
|
||||
} else {
|
||||
console.warn(
|
||||
`Got unknown websocket message type "${type}" (inResponse: ${inResponse}) with content`,
|
||||
message.content
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the connection error notice
|
||||
*/
|
||||
private dismissConnectionErrorNotice(): void {
|
||||
if (this.connectionErrorNotice) {
|
||||
this.connectionErrorNotice.dismiss();
|
||||
@ -269,13 +318,23 @@ export class WebsocketService {
|
||||
*
|
||||
* @param type the message type
|
||||
* @param content the actual content
|
||||
* @param success an optional success callback for a response
|
||||
* @param error an optional error callback for a response
|
||||
* @param id an optional id for the message. If not given, a random id will be generated and returned.
|
||||
* @returns the message id
|
||||
*/
|
||||
public send<T>(type: string, content: T, id?: string): void {
|
||||
public send<T, R>(
|
||||
type: string,
|
||||
content: T,
|
||||
success?: (val: R) => boolean,
|
||||
error?: (error: string) => void,
|
||||
id?: string
|
||||
): string {
|
||||
if (!this.websocket) {
|
||||
return;
|
||||
}
|
||||
|
||||
const message: WebsocketMessage = {
|
||||
const message: OutgoingWebsocketMessage = {
|
||||
type: type,
|
||||
content: content,
|
||||
id: id
|
||||
@ -290,6 +349,10 @@ export class WebsocketService {
|
||||
}
|
||||
}
|
||||
|
||||
if (success) {
|
||||
this.responseCallbacks[message.id] = [success, error];
|
||||
}
|
||||
|
||||
// Either send directly or add to queue, if not connected.
|
||||
const jsonMessage = JSON.stringify(message);
|
||||
if (this.isConnected) {
|
||||
@ -297,5 +360,29 @@ export class WebsocketService {
|
||||
} else {
|
||||
this.sendQueueWhileNotConnected.push(jsonMessage);
|
||||
}
|
||||
|
||||
return message.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message and waits for the response
|
||||
*
|
||||
* @param type the message type
|
||||
* @param content the actual content
|
||||
* @param id an optional id for the message. If not given, a random id will be generated and returned.
|
||||
*/
|
||||
public sendAndGetResponse<T, R>(type: string, content: T, id?: string): Promise<R> {
|
||||
return new Promise<R>((resolve, reject) => {
|
||||
this.send<T, R>(
|
||||
type,
|
||||
content,
|
||||
val => {
|
||||
resolve(val);
|
||||
return true;
|
||||
},
|
||||
reject,
|
||||
id
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,18 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { Observable, BehaviorSubject } from 'rxjs';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { BaseRepository } from 'app/core/repositories/base-repository';
|
||||
import { Config } from 'app/shared/models/core/config';
|
||||
import { DataSendService } from 'app/core/core-services/data-send.service';
|
||||
import { DataStoreService } from 'app/core/core-services/data-store.service';
|
||||
import { ConstantsService } from 'app/core/ui-services/constants.service';
|
||||
import { ConstantsService } from 'app/core/core-services/constants.service';
|
||||
import { HttpService } from 'app/core/core-services/http.service';
|
||||
import { Identifiable } from 'app/shared/models/base/identifiable';
|
||||
import { CollectionStringMapperService } from 'app/core/core-services/collectionStringMapper.service';
|
||||
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
|
||||
import { ViewConfig } from 'app/site/config/models/view-config';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
/**
|
||||
* Holds a single config item.
|
||||
@ -107,7 +107,7 @@ export class ConfigRepositoryService extends BaseRepository<ViewConfig, Config>
|
||||
) {
|
||||
super(DS, dataSend, mapperService, viewModelStoreService, translate, Config);
|
||||
|
||||
this.constantsService.get('OpenSlidesConfigVariables').subscribe(constant => {
|
||||
this.constantsService.get('ConfigVariables').subscribe(constant => {
|
||||
this.createConfigStructure(constant);
|
||||
this.updateConfigStructure(...Object.values(this.viewModelStore));
|
||||
this.updateConfigListObservable();
|
||||
|
@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
|
||||
|
||||
import { BaseRepository } from '../base-repository';
|
||||
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service';
|
||||
import { ConstantsService } from '../../ui-services/constants.service';
|
||||
import { ConstantsService } from '../../core-services/constants.service';
|
||||
import { DataSendService } from '../../core-services/data-send.service';
|
||||
import { DataStoreService } from '../../core-services/data-store.service';
|
||||
import { Group } from 'app/shared/models/users/group';
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { Observable, BehaviorSubject } from 'rxjs';
|
||||
|
||||
import { Config } from '../../shared/models/core/config';
|
||||
import { DataStoreService } from '../core-services/data-store.service';
|
||||
|
||||
|
@ -12,7 +12,7 @@ interface ContentObject {
|
||||
|
||||
/**
|
||||
* Determine visibility states for agenda items
|
||||
* Coming from "OpenSlidesConfigVariables" property "agenda_hide_internal_items_on_projector"
|
||||
* Coming from "ConfigVariables" property "agenda_hide_internal_items_on_projector"
|
||||
*/
|
||||
export const itemVisibilityChoices = [
|
||||
{ key: 1, name: 'Public item', csvName: '' },
|
||||
|
@ -11,7 +11,7 @@ import { Assignment } from 'app/shared/models/assignments/assignment';
|
||||
import { AssignmentPollService } from '../../services/assignment-poll.service';
|
||||
import { AssignmentRepositoryService } from 'app/core/repositories/assignments/assignment-repository.service';
|
||||
import { BaseViewComponent } from 'app/site/base/base-view';
|
||||
import { ConstantsService } from 'app/core/ui-services/constants.service';
|
||||
import { ConstantsService } from 'app/core/core-services/constants.service';
|
||||
import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service';
|
||||
import { LocalPermissionsService } from 'app/site/motions/services/local-permissions.service';
|
||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||
|
@ -5,7 +5,7 @@ import { Assignment } from 'app/shared/models/assignments/assignment';
|
||||
import { BaseFilterListService, OsFilter } from 'app/core/ui-services/base-filter-list.service';
|
||||
import { StorageService } from 'app/core/core-services/storage.service';
|
||||
import { ViewAssignment, AssignmentPhase } from '../models/view-assignment';
|
||||
import { ConstantsService } from 'app/core/ui-services/constants.service';
|
||||
import { ConstantsService } from 'app/core/core-services/constants.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
@ -4,7 +4,7 @@ import { MatDialog } from '@angular/material';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { CalculablePollKey } from 'app/core/ui-services/poll.service';
|
||||
import { ConstantsService } from 'app/core/ui-services/constants.service';
|
||||
import { ConstantsService } from 'app/core/core-services/constants.service';
|
||||
import { LocalPermissionsService } from 'app/site/motions/services/local-permissions.service';
|
||||
import { MotionPoll } from 'app/shared/models/motions/motion-poll';
|
||||
import { MotionPollDialogComponent } from './motion-poll-dialog.component';
|
||||
@ -209,7 +209,7 @@ export class MotionPollComponent implements OnInit {
|
||||
* Subscribe to the available majority choices as given in the server-side constants
|
||||
*/
|
||||
private subscribeMajorityChoices(): void {
|
||||
this.constants.get<any>('OpenSlidesConfigVariables').subscribe(constants => {
|
||||
this.constants.get<any>('ConfigVariables').subscribe(constants => {
|
||||
const motionconst = constants.find(c => c.name === 'Motions');
|
||||
if (motionconst) {
|
||||
const ballotConst = motionconst.subgroups.find(s => s.name === 'Voting and ballot papers');
|
||||
|
@ -3,9 +3,9 @@ import { Injectable } from '@angular/core';
|
||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||
import { ViewMotion } from '../models/view-motion';
|
||||
import { ConfigService } from 'app/core/ui-services/config.service';
|
||||
import { ConstantsService } from 'app/core/ui-services/constants.service';
|
||||
import { ConstantsService } from 'app/core/core-services/constants.service';
|
||||
|
||||
interface OpenSlidesSettings {
|
||||
interface Settings {
|
||||
MOTIONS_ALLOW_AMENDMENTS_OF_AMENDMENTS: boolean;
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ export class LocalPermissionsService {
|
||||
.get<boolean>('motions_amendments_enabled')
|
||||
.subscribe(enabled => (this.amendmentEnabled = enabled));
|
||||
this.constants
|
||||
.get<OpenSlidesSettings>('OpenSlidesSettings')
|
||||
.get<Settings>('Settings')
|
||||
.subscribe(settings => (this.amendmentOfAmendment = settings.MOTIONS_ALLOW_AMENDMENTS_OF_AMENDMENTS));
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ from typing import Any, Dict, List, Set
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.conf import settings
|
||||
from django.db.models import Max
|
||||
from django.db.models.signals import post_migrate, pre_delete
|
||||
|
||||
|
||||
@ -155,7 +156,7 @@ class CoreAppConfig(AppConfig):
|
||||
# Settings key does not exist. Do nothing. The client will
|
||||
# treat this as undefined.
|
||||
pass
|
||||
constants["OpenSlidesSettings"] = client_settings_dict
|
||||
constants["Settings"] = client_settings_dict
|
||||
|
||||
# Config variables
|
||||
config_groups: List[Any] = []
|
||||
@ -181,7 +182,14 @@ class CoreAppConfig(AppConfig):
|
||||
)
|
||||
# Add the config variable to the current group and subgroup.
|
||||
config_groups[-1]["subgroups"][-1]["items"].append(config_variable.data)
|
||||
constants["OpenSlidesConfigVariables"] = config_groups
|
||||
constants["ConfigVariables"] = config_groups
|
||||
|
||||
# get max migration id -> the "version" of the DB
|
||||
from django.db.migrations.recorder import MigrationRecorder
|
||||
|
||||
constants["MigrationVersion"] = MigrationRecorder.Migration.objects.aggregate(
|
||||
Max("id")
|
||||
)["id__max"]
|
||||
|
||||
return constants
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user