diff --git a/client/src/app/core/core-services/time-travel.service.ts b/client/src/app/core/core-services/time-travel.service.ts index c2f42fd67..cba21feb0 100644 --- a/client/src/app/core/core-services/time-travel.service.ts +++ b/client/src/app/core/core-services/time-travel.service.ts @@ -11,17 +11,8 @@ import { OpenSlidesStatusService } from './openslides-status.service'; import { OpenSlidesService } from './openslides.service'; import { WebsocketService } from './websocket.service'; -/** - * Interface for full history data objects. - * The are not too different from the history-objects, - * but contain full-data and a timestamp in contrast to a date - */ interface HistoryData { - element_id: string; - full_data: BaseModel; - information: string; - timestamp: number; - user_id: number; + [collection: string]: BaseModel[]; } /** @@ -65,19 +56,16 @@ export class TimeTravelService { const updateSlot = await this.DSUpdateManager.getNewUpdateSlot(this.DS); await this.stopTime(history); - const fullDataHistory: HistoryData[] = await this.getHistoryData(history); - for (const historyObject of fullDataHistory) { - let collectionString: string; - let id: string; - [collectionString, id] = historyObject.element_id.split(':'); + const historyData: HistoryData = await this.getHistoryData(history); - if (historyObject.full_data) { - const targetClass = this.modelMapperService.getModelConstructor(collectionString); - await this.DS.add([new targetClass(historyObject.full_data)]); - } else { - await this.DS.remove(collectionString, [+id]); - } - } + const allModels = []; + Object.keys(historyData).forEach(collection => { + const targetClass = this.modelMapperService.getModelConstructor(collection); + historyData[collection].forEach(model => { + allModels.push(new targetClass(model)); + }); + }); + await this.DS.set(allModels, 0); this.DSUpdateManager.commit(updateSlot); } @@ -99,9 +87,13 @@ export class TimeTravelService { * @param date the Date object * @returns the full history on the given date */ - private async getHistoryData(history: History): Promise { + private async getHistoryData(history: History): Promise { const queryParams = { timestamp: Math.ceil(history.timestamp) }; - return this.httpService.get(`${environment.urlPrefix}/core/history/data/`, null, queryParams); + return await this.httpService.get( + `${environment.urlPrefix}/core/history/data/`, + null, + queryParams + ); } /** diff --git a/client/src/app/core/ui-services/base-filter-list.service.ts b/client/src/app/core/ui-services/base-filter-list.service.ts index f89c88214..41c20e573 100644 --- a/client/src/app/core/ui-services/base-filter-list.service.ts +++ b/client/src/app/core/ui-services/base-filter-list.service.ts @@ -3,6 +3,7 @@ import { BehaviorSubject, Observable, Subscription } from 'rxjs'; import { BaseModel } from 'app/shared/models/base/base-model'; import { BaseRepository } from '../repositories/base-repository'; import { BaseViewModel, TitleInformation } from '../../site/base/base-view-model'; +import { OpenSlidesStatusService } from '../core-services/openslides-status.service'; import { StorageService } from '../core-services/storage.service'; /** @@ -149,7 +150,11 @@ export abstract class BaseFilterListService { * @param name the name of the filter service * @param store storage service, to read saved filter variables */ - public constructor(protected name: string, private store: StorageService) {} + public constructor( + protected name: string, + private store: StorageService, + private OSStatus: OpenSlidesStatusService + ) {} /** * Initializes the filterService. @@ -157,7 +162,10 @@ export abstract class BaseFilterListService { * @param inputData Observable array with ViewModels */ public async initFilters(inputData: Observable): Promise { - const storedFilter = await this.store.get('filter_' + this.name); + let storedFilter: OsFilter[] = null; + if (!this.OSStatus.isInHistoryMode) { + storedFilter = await this.store.get('filter_' + this.name); + } if (storedFilter && this.isOsFilter(storedFilter)) { this.filterDefinitions = storedFilter; @@ -221,39 +229,42 @@ export abstract class BaseFilterListService { * Takes the filter definition from children and using {@link getFilterDefinitions} * and sets/updates {@link filterDefinitions} */ - public setFilterDefinitions(): void { + public async setFilterDefinitions(): Promise { if (this.filterDefinitions) { const newDefinitions = this.getFilterDefinitions(); - this.store.get('filter_' + this.name).then(storedFilter => { - if (!!storedFilter) { - for (const newDef of newDefinitions) { - let count = 0; - const matchingExistingFilter = storedFilter.find(oldDef => oldDef.property === newDef.property); - for (const option of newDef.options) { - if (typeof option === 'object') { - if (matchingExistingFilter && matchingExistingFilter.options) { - const existingOption = matchingExistingFilter.options.find( - o => - typeof o !== 'string' && - JSON.stringify(o.condition) === JSON.stringify(option.condition) - ) as OsFilterOption; - if (existingOption) { - option.isActive = existingOption.isActive; - } - if (option.isActive) { - count++; - } + let storedFilter = null; + if (!this.OSStatus.isInHistoryMode) { + storedFilter = await this.store.get('filter_' + this.name); + } + + if (!!storedFilter) { + for (const newDef of newDefinitions) { + let count = 0; + const matchingExistingFilter = storedFilter.find(oldDef => oldDef.property === newDef.property); + for (const option of newDef.options) { + if (typeof option === 'object') { + if (matchingExistingFilter && matchingExistingFilter.options) { + const existingOption = matchingExistingFilter.options.find( + o => + typeof o !== 'string' && + JSON.stringify(o.condition) === JSON.stringify(option.condition) + ) as OsFilterOption; + if (existingOption) { + option.isActive = existingOption.isActive; + } + if (option.isActive) { + count++; } } } - newDef.count = count; } + newDef.count = count; } + } - this.filterDefinitions = newDefinitions; - this.storeActiveFilters(); - }); + this.filterDefinitions = newDefinitions; + this.storeActiveFilters(); } } @@ -302,7 +313,9 @@ export abstract class BaseFilterListService { */ public storeActiveFilters(): void { this.updateFilteredData(); - this.store.set('filter_' + this.name, this.filterDefinitions); + if (!this.OSStatus.isInHistoryMode) { + this.store.set('filter_' + this.name, this.filterDefinitions); + } } /** diff --git a/client/src/app/core/ui-services/base-sort-list.service.ts b/client/src/app/core/ui-services/base-sort-list.service.ts index 95c42ef2d..44ba32b59 100644 --- a/client/src/app/core/ui-services/base-sort-list.service.ts +++ b/client/src/app/core/ui-services/base-sort-list.service.ts @@ -2,6 +2,7 @@ import { TranslateService } from '@ngx-translate/core'; import { BehaviorSubject, Observable, Subscription } from 'rxjs'; import { BaseViewModel } from '../../site/base/base-view-model'; +import { OpenSlidesStatusService } from '../core-services/openslides-status.service'; import { StorageService } from '../core-services/storage.service'; /** @@ -119,7 +120,12 @@ export abstract class BaseSortListService { * @param translate required for language sensitive comparing * @param store to save and load sorting preferences */ - public constructor(protected name: string, protected translate: TranslateService, private store: StorageService) {} + public constructor( + protected name: string, + protected translate: TranslateService, + private store: StorageService, + private OSStatus: OpenSlidesStatusService + ) {} /** * Enforce children to implement a method that returns the fault sorting @@ -139,7 +145,12 @@ export abstract class BaseSortListService { } if (!this.sortDefinition) { - this.sortDefinition = await this.store.get | null>('sorting_' + this.name); + if (this.OSStatus.isInHistoryMode) { + this.sortDefinition = null; + } else { + this.sortDefinition = await this.store.get | null>('sorting_' + this.name); + } + if (this.sortDefinition && this.sortDefinition.sortProperty) { this.updateSortedData(); } else { @@ -198,7 +209,9 @@ export abstract class BaseSortListService { */ private updateSortDefinitions(): void { this.updateSortedData(); - this.store.set('sorting_' + this.name, this.sortDefinition); + if (!this.OSStatus.isInHistoryMode) { + this.store.set('sorting_' + this.name, this.sortDefinition); + } } /** diff --git a/client/src/app/shared/models/core/history.ts b/client/src/app/shared/models/core/history.ts index b17e9e363..92ed32b1d 100644 --- a/client/src/app/shared/models/core/history.ts +++ b/client/src/app/shared/models/core/history.ts @@ -8,8 +8,7 @@ import { Deserializable } from '../base/deserializable'; export class History implements Deserializable { public element_id: string; public timestamp: number; - public information: string; - public restricted: boolean; + public information: string[]; public user_id: number; /** diff --git a/client/src/app/site/agenda/services/agenda-filter-list.service.ts b/client/src/app/site/agenda/services/agenda-filter-list.service.ts index e4d8971df..226bbaab4 100644 --- a/client/src/app/site/agenda/services/agenda-filter-list.service.ts +++ b/client/src/app/site/agenda/services/agenda-filter-list.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; +import { OpenSlidesStatusService } from 'app/core/core-services/openslides-status.service'; import { StorageService } from 'app/core/core-services/storage.service'; import { BaseFilterListService, OsFilter, OsFilterOption } from 'app/core/ui-services/base-filter-list.service'; import { ItemVisibilityChoices } from 'app/shared/models/agenda/item'; @@ -20,8 +21,8 @@ export class AgendaFilterListService extends BaseFilterListService { * @param store * @param translate Translation service */ - public constructor(store: StorageService, private translate: TranslateService) { - super('Agenda', store); + public constructor(store: StorageService, OSStatus: OpenSlidesStatusService, private translate: TranslateService) { + super('Agenda', store, OSStatus); } /** diff --git a/client/src/app/site/assignments/services/assignment-filter.service.ts b/client/src/app/site/assignments/services/assignment-filter.service.ts index 81d709934..ee8133c11 100644 --- a/client/src/app/site/assignments/services/assignment-filter.service.ts +++ b/client/src/app/site/assignments/services/assignment-filter.service.ts @@ -1,5 +1,6 @@ import { Injectable } from '@angular/core'; +import { OpenSlidesStatusService } from 'app/core/core-services/openslides-status.service'; import { StorageService } from 'app/core/core-services/storage.service'; import { BaseFilterListService, OsFilter, OsFilterOption } from 'app/core/ui-services/base-filter-list.service'; import { AssignmentPhases, ViewAssignment } from '../models/view-assignment'; @@ -17,8 +18,8 @@ export class AssignmentFilterListService extends BaseFilterListService { public sortOptions: OsSortingOption[] = [{ property: 'title' }]; - public constructor(translate: TranslateService, store: StorageService) { - super('Motion block', translate, store); + public constructor(translate: TranslateService, store: StorageService, OSStatus: OpenSlidesStatusService) { + super('Motion block', translate, store, OSStatus); } protected async getDefaultDefinition(): Promise> { diff --git a/client/src/app/site/motions/services/motion-filter-list.service.ts b/client/src/app/site/motions/services/motion-filter-list.service.ts index efd16ab0d..8bd471253 100644 --- a/client/src/app/site/motions/services/motion-filter-list.service.ts +++ b/client/src/app/site/motions/services/motion-filter-list.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; +import { OpenSlidesStatusService } from 'app/core/core-services/openslides-status.service'; import { OperatorService } from 'app/core/core-services/operator.service'; import { StorageService } from 'app/core/core-services/storage.service'; import { CategoryRepositoryService } from 'app/core/repositories/motions/category-repository.service'; @@ -123,6 +124,7 @@ export class MotionFilterListService extends BaseFilterListService { */ public constructor( store: StorageService, + OSStatus: OpenSlidesStatusService, categoryRepo: CategoryRepositoryService, motionBlockRepo: MotionBlockRepositoryService, commentRepo: MotionCommentSectionRepositoryService, @@ -132,7 +134,7 @@ export class MotionFilterListService extends BaseFilterListService { private operator: OperatorService, private config: ConfigService ) { - super('Motion', store); + super('Motion', store, OSStatus); this.getWorkflowConfig(); this.updateFilterForRepo(categoryRepo, this.categoryFilterOptions, this.translate.instant('No category set')); diff --git a/client/src/app/site/motions/services/motion-sort-list.service.ts b/client/src/app/site/motions/services/motion-sort-list.service.ts index 05d0425f6..c4fceec00 100644 --- a/client/src/app/site/motions/services/motion-sort-list.service.ts +++ b/client/src/app/site/motions/services/motion-sort-list.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; +import { OpenSlidesStatusService } from 'app/core/core-services/openslides-status.service'; import { StorageService } from 'app/core/core-services/storage.service'; import { Deferred } from 'app/core/deferred'; import { _ } from 'app/core/translate/translation-marker'; @@ -48,8 +49,13 @@ export class MotionSortListService extends BaseSortListService { * @param store required by parent * @param config set the default sorting according to OpenSlides configuration */ - public constructor(translate: TranslateService, store: StorageService, private config: ConfigService) { - super('Motion', translate, store); + public constructor( + translate: TranslateService, + store: StorageService, + OSStatus: OpenSlidesStatusService, + private config: ConfigService + ) { + super('Motion', translate, store, OSStatus); this.config.get('motions_motions_sorting').subscribe(defSortProp => { if (defSortProp) { diff --git a/client/src/app/site/users/services/user-filter-list.service.ts b/client/src/app/site/users/services/user-filter-list.service.ts index eb8f832e3..a33ae9c3c 100644 --- a/client/src/app/site/users/services/user-filter-list.service.ts +++ b/client/src/app/site/users/services/user-filter-list.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; +import { OpenSlidesStatusService } from 'app/core/core-services/openslides-status.service'; import { StorageService } from 'app/core/core-services/storage.service'; import { GroupRepositoryService } from 'app/core/repositories/users/group-repository.service'; import { BaseFilterListService, OsFilter } from 'app/core/ui-services/base-filter-list.service'; @@ -28,8 +29,13 @@ export class UserFilterListService extends BaseFilterListService { * @param groupRepo to filter by groups * @param translate marking some translations that are unique here */ - public constructor(store: StorageService, groupRepo: GroupRepositoryService, private translate: TranslateService) { - super('User', store); + public constructor( + store: StorageService, + OSStatus: OpenSlidesStatusService, + groupRepo: GroupRepositoryService, + private translate: TranslateService + ) { + super('User', store, OSStatus); this.updateFilterForRepo(groupRepo, this.userGroupFilterOptions, this.translate.instant('Default'), [1]); } diff --git a/client/src/app/site/users/services/user-sort-list.service.ts b/client/src/app/site/users/services/user-sort-list.service.ts index ef4684fa5..63a69700d 100644 --- a/client/src/app/site/users/services/user-sort-list.service.ts +++ b/client/src/app/site/users/services/user-sort-list.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; +import { OpenSlidesStatusService } from 'app/core/core-services/openslides-status.service'; import { StorageService } from 'app/core/core-services/storage.service'; import { BaseSortListService, OsSortingDefinition, OsSortingOption } from 'app/core/ui-services/base-sort-list.service'; import { ViewUser } from '../models/view-user'; @@ -34,8 +35,8 @@ export class UserSortListService extends BaseSortListService { * @param translate required by parent * @param store requires by parent */ - public constructor(translate: TranslateService, store: StorageService) { - super('User', translate, store); + public constructor(translate: TranslateService, store: StorageService, OSStatus: OpenSlidesStatusService) { + super('User', translate, store, OSStatus); } /** diff --git a/openslides/core/views.py b/openslides/core/views.py index 3c984fe6f..6c028383c 100644 --- a/openslides/core/views.py +++ b/openslides/core/views.py @@ -1,5 +1,6 @@ import datetime import os +from collections import defaultdict from typing import Any, Dict from django.conf import settings @@ -11,6 +12,8 @@ from django.utils.timezone import now from django.views import static from django.views.generic.base import View +from openslides.utils.utils import split_element_id + from .. import __license__ as license, __url__ as url, __version__ as version from ..users.models import User from ..utils import views as utils_views @@ -523,15 +526,15 @@ class HistoryInformationView(utils_views.APIView): """ data = [] for instance in History.objects.filter(element_id=value).order_by("-now"): - data.append( - { - "element_id": instance.element_id, - "timestamp": instance.now.timestamp(), - "information": instance.information, - "resticted": instance.restricted, - "user_id": instance.user.pk if instance.user else None, - } - ) + if instance.information: + data.append( + { + "element_id": instance.element_id, + "timestamp": instance.now.timestamp(), + "information": instance.information, + "user_id": instance.user.pk if instance.user else None, + } + ) return data def delete(self, request, *args, **kwargs): @@ -563,8 +566,8 @@ class HistoryDataView(utils_views.APIView): def get_context_data(self, **context): """ - Checks if user is in admin group. If yes all history data until - (including) timestamp are added to the response data. + Checks if user is in admin group. If yes, all history data until + (including) timestamp are collected to build a valid dataset for the client. """ if not in_some_groups(self.request.user.pk or 0, [GROUP_ADMIN_PK]): self.permission_denied(self.request) @@ -572,22 +575,25 @@ class HistoryDataView(utils_views.APIView): timestamp = int(self.request.query_params.get("timestamp", 0)) except ValueError: raise ValidationError( - {"detail": "Invalid input. Timestamp should be an integer."} + {"detail": "Invalid input. Timestamp should be an integer."} ) - data = [] queryset = History.objects.select_related("full_data") if timestamp: queryset = queryset.filter( now__lte=datetime.datetime.fromtimestamp(timestamp) ) + + # collection <--> id <--> full_data + dataset: Dict[str, Dict[int, Any]] = defaultdict(dict) for instance in queryset: - data.append( - { - "full_data": instance.full_data.full_data, - "element_id": instance.element_id, - "timestamp": instance.now.timestamp(), - "information": instance.information, - "user_id": instance.user.pk if instance.user else None, - } - ) - return data + collection, id = split_element_id(instance.element_id) + full_data = instance.full_data.full_data + if full_data: + dataset[collection][id] = full_data + else: + del dataset[collection][id] + + return { + collection: list(dataset[collection].values()) + for collection in dataset.keys() + }