Merge pull request #4874 from FinnStutzenstein/history

Improve history
This commit is contained in:
Finn Stutzenstein 2019-08-01 11:19:57 +02:00 committed by GitHub
commit 0383f824d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 139 additions and 96 deletions

View File

@ -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<HistoryData[]> {
private async getHistoryData(history: History): Promise<HistoryData> {
const queryParams = { timestamp: Math.ceil(history.timestamp) };
return this.httpService.get<HistoryData[]>(`${environment.urlPrefix}/core/history/data/`, null, queryParams);
return await this.httpService.get<HistoryData>(
`${environment.urlPrefix}/core/history/data/`,
null,
queryParams
);
}
/**

View File

@ -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<V extends BaseViewModel> {
* @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<V extends BaseViewModel> {
* @param inputData Observable array with ViewModels
*/
public async initFilters(inputData: Observable<V[]>): Promise<void> {
const storedFilter = await this.store.get<OsFilter[]>('filter_' + this.name);
let storedFilter: OsFilter[] = null;
if (!this.OSStatus.isInHistoryMode) {
storedFilter = await this.store.get<OsFilter[]>('filter_' + this.name);
}
if (storedFilter && this.isOsFilter(storedFilter)) {
this.filterDefinitions = storedFilter;
@ -221,39 +229,42 @@ export abstract class BaseFilterListService<V extends BaseViewModel> {
* Takes the filter definition from children and using {@link getFilterDefinitions}
* and sets/updates {@link filterDefinitions}
*/
public setFilterDefinitions(): void {
public async setFilterDefinitions(): Promise<void> {
if (this.filterDefinitions) {
const newDefinitions = this.getFilterDefinitions();
this.store.get<OsFilter[]>('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<OsFilter[]>('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<V extends BaseViewModel> {
*/
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);
}
}
/**

View File

@ -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<V extends BaseViewModel> {
* @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<V extends BaseViewModel> {
}
if (!this.sortDefinition) {
this.sortDefinition = await this.store.get<OsSortingDefinition<V> | null>('sorting_' + this.name);
if (this.OSStatus.isInHistoryMode) {
this.sortDefinition = null;
} else {
this.sortDefinition = await this.store.get<OsSortingDefinition<V> | null>('sorting_' + this.name);
}
if (this.sortDefinition && this.sortDefinition.sortProperty) {
this.updateSortedData();
} else {
@ -198,7 +209,9 @@ export abstract class BaseSortListService<V extends BaseViewModel> {
*/
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);
}
}
/**

View File

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

View File

@ -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<ViewItem> {
* @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);
}
/**

View File

@ -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<ViewAssig
* @param store StorageService
* @param translate translate service
*/
public constructor(store: StorageService) {
super('Assignments', store);
public constructor(store: StorageService, OSStatus: OpenSlidesStatusService) {
super('Assignments', store, OSStatus);
}
/**

View File

@ -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 { ViewAssignment } from '../models/view-assignment';
@ -29,8 +30,8 @@ export class AssignmentSortListService extends BaseSortListService<ViewAssignmen
* @param translate required by parent
* @param storage required by parent
*/
public constructor(translate: TranslateService, storage: StorageService) {
super('Assignment', translate, storage);
public constructor(translate: TranslateService, storage: StorageService, OSStatus: OpenSlidesStatusService) {
super('Assignment', translate, storage, OSStatus);
}
/**

View File

@ -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 { ViewMediafile } from '../models/view-mediafile';
@ -31,8 +32,8 @@ export class MediafilesSortListService extends BaseSortListService<ViewMediafile
* @param translate required by parent
* @param store required by parent
*/
public constructor(translate: TranslateService, store: StorageService) {
super('Mediafiles', translate, store);
public constructor(translate: TranslateService, store: StorageService, OSStatus: OpenSlidesStatusService) {
super('Mediafiles', translate, store, OSStatus);
}
/**

View File

@ -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 { ViewMotionBlock } from '../models/view-motion-block';
@ -12,8 +13,8 @@ import { ViewMotionBlock } from '../models/view-motion-block';
export class MotionBlockSortService extends BaseSortListService<ViewMotionBlock> {
public sortOptions: OsSortingOption<ViewMotionBlock>[] = [{ 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<OsSortingDefinition<ViewMotionBlock>> {

View File

@ -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<ViewMotion> {
*/
public constructor(
store: StorageService,
OSStatus: OpenSlidesStatusService,
categoryRepo: CategoryRepositoryService,
motionBlockRepo: MotionBlockRepositoryService,
commentRepo: MotionCommentSectionRepositoryService,
@ -132,7 +134,7 @@ export class MotionFilterListService extends BaseFilterListService<ViewMotion> {
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'));

View File

@ -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<ViewMotion> {
* @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<string>('motions_motions_sorting').subscribe(defSortProp => {
if (defSortProp) {

View File

@ -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<ViewUser> {
* @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]);
}

View File

@ -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<ViewUser> {
* @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);
}
/**

View File

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