Refactured client

This commit is contained in:
FinnStutzenstein 2019-06-12 14:32:08 +02:00
parent 5a5bc77e62
commit ff98ee1b96
16 changed files with 233 additions and 423 deletions

View File

@ -358,6 +358,10 @@ export class OperatorService implements OnAfterAppsLoaded {
return groupIds.some(id => this.user.groups_id.includes(id)); return groupIds.some(id => this.user.groups_id.includes(id));
} }
public isSuperAdmin(): boolean {
return this.isInGroupIdsNonAdminCheck(2);
}
/** /**
* Update the operators permissions and publish the operator afterwards. * Update the operators permissions and publish the operator afterwards.
* Saves the current WhoAmI to storage with the updated permissions * Saves the current WhoAmI to storage with the updated permissions

View File

@ -9,6 +9,7 @@ import { BaseModel } from 'app/shared/models/base/base-model';
import { OpenSlidesStatusService } from './openslides-status.service'; import { OpenSlidesStatusService } from './openslides-status.service';
import { OpenSlidesService } from './openslides.service'; import { OpenSlidesService } from './openslides.service';
import { HttpService } from './http.service'; import { HttpService } from './http.service';
import { DataStoreUpdateManagerService } from './data-store-update-manager.service';
/** /**
* Interface for full history data objects. * Interface for full history data objects.
@ -51,7 +52,8 @@ export class TimeTravelService {
private modelMapperService: CollectionStringMapperService, private modelMapperService: CollectionStringMapperService,
private DS: DataStoreService, private DS: DataStoreService,
private OSStatus: OpenSlidesStatusService, private OSStatus: OpenSlidesStatusService,
private OpenSlides: OpenSlidesService private OpenSlides: OpenSlidesService,
private DSUpdateManager: DataStoreUpdateManagerService
) {} ) {}
/** /**
@ -60,6 +62,8 @@ export class TimeTravelService {
* @param history the desired point in the history of OpenSlides * @param history the desired point in the history of OpenSlides
*/ */
public async loadHistoryPoint(history: History): Promise<void> { public async loadHistoryPoint(history: History): Promise<void> {
const updateSlot = await this.DSUpdateManager.getNewUpdateSlot(this.DS);
await this.stopTime(history); await this.stopTime(history);
const fullDataHistory: HistoryData[] = await this.getHistoryData(history); const fullDataHistory: HistoryData[] = await this.getHistoryData(history);
for (const historyObject of fullDataHistory) { for (const historyObject of fullDataHistory) {
@ -74,6 +78,8 @@ export class TimeTravelService {
await this.DS.remove(collectionString, [+id]); await this.DS.remove(collectionString, [+id]);
} }
} }
this.DSUpdateManager.commit(updateSlot);
} }
/** /**
@ -94,25 +100,16 @@ export class TimeTravelService {
* @returns the full history on the given date * @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.unixtime) }; const queryParams = { timestamp: Math.ceil(history.timestamp) };
return this.httpService.get<HistoryData[]>(`${environment.urlPrefix}/core/history/`, null, queryParams); return this.httpService.get<HistoryData[]>(`${environment.urlPrefix}/core/history/data/`, null, queryParams);
} }
/** /**
* Clears the DataStore and stops the WebSocket connection * Clears the DataStore and stops the WebSocket connection
*/ */
private async stopTime(history: History): Promise<void> { private async stopTime(history: History): Promise<void> {
this.webSocketService.close(); await this.webSocketService.close();
await this.cleanDataStore(); await this.DS.set(); // Same as clear, but not persistent.
this.OSStatus.enterHistoryMode(history); this.OSStatus.enterHistoryMode(history);
} }
/**
* Clean the DataStore to inject old Data.
* Remove everything "but" the history.
*/
private async cleanDataStore(): Promise<void> {
const historyArchive = this.DS.getAll(History);
await this.DS.set(historyArchive);
}
} }

View File

@ -276,7 +276,7 @@ export class WebsocketService {
if (data instanceof ArrayBuffer) { if (data instanceof ArrayBuffer) {
const compressedSize = data.byteLength; const compressedSize = data.byteLength;
const decompressedBuffer: Uint8Array = decompress(new Uint8Array(data)); const decompressedBuffer: Uint8Array = decompress(new Uint8Array(data));
console.log( console.debug(
`Recieved ${compressedSize / 1024} KB (${decompressedBuffer.byteLength / `Recieved ${compressedSize / 1024} KB (${decompressedBuffer.byteLength /
1024} KB uncompressed), ratio ${decompressedBuffer.byteLength / compressedSize}` 1024} KB uncompressed), ratio ${decompressedBuffer.byteLength / compressedSize}`
); );
@ -285,7 +285,7 @@ export class WebsocketService {
} }
const message: IncommingWebsocketMessage = JSON.parse(data); const message: IncommingWebsocketMessage = JSON.parse(data);
console.log('Received', message); console.debug('Received', message);
const type = message.type; const type = message.type;
const inResponse = message.in_response; const inResponse = message.in_response;
const callbacks = this.responseCallbacks[inResponse]; const callbacks = this.responseCallbacks[inResponse];

View File

@ -1,18 +0,0 @@
import { TestBed } from '@angular/core/testing';
import { HistoryRepositoryService } from './history-repository.service';
import { E2EImportsModule } from 'e2e-imports.module';
describe('HistoryRepositoryService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [E2EImportsModule],
providers: [HistoryRepositoryService]
});
});
it('should be created', () => {
const service = TestBed.get(HistoryRepositoryService);
expect(service).toBeTruthy();
});
});

View File

@ -1,145 +0,0 @@
import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { CollectionStringMapperService } from 'app/core/core-services/collection-string-mapper.service';
import { DataStoreService } from 'app/core/core-services/data-store.service';
import { BaseRepository } from 'app/core/repositories/base-repository';
import { History } from 'app/shared/models/core/history';
import { Identifiable } from 'app/shared/models/base/identifiable';
import { HttpService } from 'app/core/core-services/http.service';
import { ViewHistory, ProxyHistory, HistoryTitleInformation } from 'app/site/history/models/view-history';
import { TimeTravelService } from 'app/core/core-services/time-travel.service';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { ViewUser } from 'app/site/users/models/view-user';
import { DataSendService } from 'app/core/core-services/data-send.service';
/**
* Repository for the history.
*
* Gets new history objects/entries and provides them for the view.
*/
@Injectable({
providedIn: 'root'
})
export class HistoryRepositoryService extends BaseRepository<ViewHistory, History, HistoryTitleInformation> {
/**
* Constructs the history repository
*
* @param DS The DataStore
* @param mapperService mapps the models to the collection string
* @param httpService OpenSlides own HTTP service
* @param timeTravel To change the time
*/
public constructor(
DS: DataStoreService,
dataSend: DataSendService,
mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService,
translate: TranslateService,
private httpService: HttpService,
private timeTravel: TimeTravelService
) {
super(DS, dataSend, mapperService, viewModelStoreService, translate, History);
}
public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Histories' : 'History');
};
public getTitle = (titleInformation: HistoryTitleInformation) => {
return titleInformation.element_id;
};
/**
* Creates a new ViewHistory objects out of a historyObject
*
* @param history the source history object
* @return a new ViewHistory object
*/
public createViewModel(history: History): ViewHistory {
return new ViewHistory(this.createProxyHistory(history));
}
/**
* 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
*
* @ignore
*/
public async create(): Promise<Identifiable> {
throw new Error('You cannot create a history object');
}
/**
* Overwrites the default procedure
*
* @ignore
*/
public async update(): Promise<void> {
throw new Error('You cannot update a history object');
}
/**
* Overwrites the default procedure
*
* @ignore
*/
public async patch(): Promise<void> {
throw new Error('You cannot patch a history object');
}
/**
* Overwrites the default procedure
*
* Sends a post-request to delete history objects
*/
public async delete(): Promise<void> {
const restPath = '/rest/core/history/clear_history/';
await this.httpService.post(restPath);
}
/**
* Get the ListTitle of a history Element from the dataStore
* using the collection string and the ID.
*
* @param collectionString the models collection string
* @param id the models id
* @returns the ListTitle or null if the model was deleted already
*/
public getOldModelInfo(collectionString: string, id: number): string {
const model = this.viewModelStoreService.get(collectionString, id);
if (model) {
return model.getListTitle();
}
return null;
}
/**
* Get the full data on the given date and use the
* TimeTravelService to browse the history on the
* given date
*
* @param viewHistory determines to point to travel back to
*/
public async browseHistory(viewHistory: ViewHistory): Promise<void> {
return this.timeTravel.loadHistoryPoint(viewHistory.history);
}
}

View File

@ -16,7 +16,7 @@ export class PromptService {
* @param title The title to display in the dialog * @param title The title to display in the dialog
* @param content The content in the dialog * @param content The content in the dialog
*/ */
public async open(title: string, content: string): Promise<any> { public async open(title: string, content: string = ''): Promise<any> {
const dialogRef = this.dialog.open(PromptDialogComponent, { const dialogRef = this.dialog.open(PromptDialogComponent, {
width: '250px', width: '250px',
data: { title: title, content: content } data: { title: title, content: content }

View File

@ -1,16 +1,15 @@
import { BaseModel } from '../base/base-model'; import { Deserializable } from '../base/deserializable';
/** /**
* Representation of a history object. * Representation of a history object.
* *
* @ignore * @ignore
*/ */
export class History extends BaseModel { export class History implements Deserializable {
public static COLLECTIONSTRING = 'core/history';
public id: number;
public element_id: string; public element_id: string;
public now: string; public timestamp: number;
public information: string; public information: string;
public restricted: boolean;
public user_id: number; public user_id: number;
/** /**
@ -19,18 +18,21 @@ export class History extends BaseModel {
* @returns a Data object * @returns a Data object
*/ */
public get date(): Date { public get date(): Date {
return new Date(this.now); return new Date(this.timestamp * 1000);
} }
/** public get collectionString(): string {
* Converts the timestamp to unix time return this.element_id.split(':')[0];
*/
public get unixtime(): number {
return Date.parse(this.now) / 1000;
} }
public constructor(input?: any) { public get modelId(): number {
super(History.COLLECTIONSTRING, input); return +this.element_id.split(':')[1];
}
public constructor(input: History) {
if (input) {
this.deserialize(input);
}
} }
/** /**
@ -42,4 +44,8 @@ export class History extends BaseModel {
public getLocaleString(locale: string): string { public getLocaleString(locale: string): string {
return this.date.toLocaleString(locale); return this.date.toLocaleString(locale);
} }
public deserialize(input: any): void {
Object.assign(this, input);
}
} }

View File

@ -5,20 +5,41 @@
<!-- Menu --> <!-- Menu -->
<div class="menu-slot"> <div class="menu-slot">
<!-- Hidden for everyone but the superadmin --> <!-- Hidden for everyone but the superadmin -->
<button *osPerms="'superadmin'" type="button" mat-icon-button [matMenuTriggerFor]="historyMenu"> <button *ngIf="isSuperAdmin" type="button" mat-icon-button [matMenuTriggerFor]="historyMenu">
<mat-icon>more_vert</mat-icon> <mat-icon>more_vert</mat-icon>
</button> </button>
</div> </div>
</os-head-bar> </os-head-bar>
<div class="custom-table-header"> <div class="custom-table-header">
<mat-form-field> <div>
<input matInput (keyup)="applySearch($event.target.value)" placeholder="{{ 'Search' | translate }}" /> <span>
<mat-icon matSuffix>search</mat-icon> <os-search-value-selector
</mat-form-field> ngDefaultControl
[form]="modelSelectForm"
[formControl]="modelSelectForm.get('model')"
[multiple]="false"
[includeNone]="false"
listname="{{ 'Motion' | translate }}"
[InputListValues]="collectionObserver"
></os-search-value-selector>
</span>
<span class="spacer-left-20">
<button mat-button (click)="refresh()" *ngIf="currentModelId">
<mat-icon>refresh</mat-icon>
<span translate>Refresh</span>
</button>
</span>
</div>
<div>
<mat-form-field>
<input matInput (keyup)="applySearch($event.target.value)" placeholder="{{ 'Search' | translate }}" />
<mat-icon matSuffix>search</mat-icon>
</mat-form-field>
</div>
</div> </div>
<mat-table class="os-headed-listview-table on-transition-fade" [dataSource]="dataSource" matSort> <mat-table class="on-transition-fade" [dataSource]="dataSource" matSort>
<!-- Timestamp --> <!-- Timestamp -->
<ng-container matColumnDef="time"> <ng-container matColumnDef="time">
<mat-header-cell *matHeaderCellDef translate>Timestamp</mat-header-cell> <mat-header-cell *matHeaderCellDef translate>Timestamp</mat-header-cell>
@ -43,23 +64,23 @@
<!-- Info --> <!-- Info -->
<ng-container matColumnDef="info"> <ng-container matColumnDef="info">
<mat-header-cell *matHeaderCellDef translate>Comment</mat-header-cell> <mat-header-cell *matHeaderCellDef translate>Comment</mat-header-cell>
<mat-cell *matCellDef="let history">{{ parseInformation(history.information) }}</mat-cell> <mat-cell *matCellDef="let history">{{ parseInformation(history) }}</mat-cell>
</ng-container> </ng-container>
<!-- 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_full_name }}</mat-cell> <mat-cell *matCellDef="let history">{{ getUserName(history) }}</mat-cell>
</ng-container> </ng-container>
<mat-header-row *matHeaderRowDef="getRowDef()"></mat-header-row> <mat-header-row *matHeaderRowDef="getRowDef()"></mat-header-row>
<mat-row *matRowDef="let row; columns: getRowDef()" (click)="onClickRow(row)"></mat-row> <mat-row *matRowDef="let row; columns: getRowDef()" (click)="onClickRow(row)"></mat-row>
</mat-table> </mat-table>
<mat-paginator class="on-transition-fade" [pageSizeOptions]="pageSize"></mat-paginator> <mat-paginator class="on-transition-fade" [pageSizeOptions]="pageSizes"></mat-paginator>
<mat-menu #historyMenu="matMenu"> <mat-menu #historyMenu="matMenu">
<button mat-menu-item class="red-warning-text" (click)="onDeleteAllButton()"> <button mat-menu-item class="red-warning-text" (click)="clearHistory()">
<mat-icon>delete</mat-icon> <mat-icon>delete</mat-icon>
<span translate>Delete whole history</span> <span translate>Delete whole history</span>
</button> </button>

View File

@ -1,4 +1,4 @@
.os-listview-table { .mat-table {
/** Time */ /** Time */
.mat-column-time { .mat-column-time {
flex: 1 0 50px; flex: 1 0 50px;
@ -24,3 +24,12 @@
font-style: italic; font-style: italic;
color: slategray; // TODO: Colors per theme color: slategray; // TODO: Colors per theme
} }
.custom-table-header {
justify-content: space-between;
text-align: left;
& > div {
padding: 0 20px;
}
}

View File

@ -1,19 +1,26 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { MatSnackBar } from '@angular/material'; import { MatSnackBar, MatTableDataSource } from '@angular/material';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { Title } from '@angular/platform-browser'; import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { Subject } from 'rxjs'; import { Subject, BehaviorSubject } from 'rxjs';
import { History } from 'app/shared/models/core/history'; import { environment } from 'environments/environment';
import { HistoryRepositoryService } from 'app/core/repositories/history/history-repository.service';
import { isDetailNavigable } from 'app/shared/models/base/detail-navigable'; import { isDetailNavigable } from 'app/shared/models/base/detail-navigable';
import { ListViewBaseComponent } from 'app/site/base/list-view-base';
import { OperatorService } from 'app/core/core-services/operator.service'; import { OperatorService } from 'app/core/core-services/operator.service';
import { ViewHistory } from '../../models/view-history';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { langToLocale } from 'app/shared/utils/lang-to-locale'; import { langToLocale } from 'app/shared/utils/lang-to-locale';
import { TimeTravelService } from 'app/core/core-services/time-travel.service';
import { HttpService } from 'app/core/core-services/http.service';
import { BaseViewComponent } from 'app/site/base/base-view';
import { History } from 'app/shared/models/core/history';
import { ViewUser } from 'app/site/users/models/view-user';
import { FormGroup, FormBuilder } from '@angular/forms';
import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service';
import { BaseViewModel } from 'app/site/base/base-view-model';
import { Motion } from 'app/shared/models/motions/motion';
import { PromptService } from 'app/core/ui-services/prompt.service';
/** /**
* A list view for the history. * A list view for the history.
@ -25,13 +32,41 @@ import { langToLocale } from 'app/shared/utils/lang-to-locale';
templateUrl: './history-list.component.html', templateUrl: './history-list.component.html',
styleUrls: ['./history-list.component.scss'] styleUrls: ['./history-list.component.scss']
}) })
export class HistoryListComponent extends ListViewBaseComponent<ViewHistory, History, HistoryRepositoryService> export class HistoryListComponent extends BaseViewComponent implements OnInit {
implements OnInit {
/** /**
* Subject determine when the custom timestamp subject changes * Subject determine when the custom timestamp subject changes
*/ */
public customTimestampChanged: Subject<number> = new Subject<number>(); public customTimestampChanged: Subject<number> = new Subject<number>();
public dataSource: MatTableDataSource<History> = new MatTableDataSource<History>();
public get isSuperAdmin(): boolean {
return this.operator.isSuperAdmin();
}
public pageSizes = [50, 100, 150, 200, 250];
/**
* The form for the selection of the model
* When more models are supproted, add a "collection"-dropdown
*/
public modelSelectForm: FormGroup;
/**
* The observer for the selected collection, which is currently hardcoded
* to motions.
*/
public collectionObserver: BehaviorSubject<BaseViewModel[]>;
/**
* The current selected collection. THis may move to `modelSelectForm`, if this can be choosen.
*/
private currentCollection = Motion.COLLECTIONSTRING;
public get currentModelId(): number | null {
return this.modelSelectForm.controls.model.value;
}
/** /**
* Constructor for the history list component * Constructor for the history list component
* *
@ -47,12 +82,25 @@ export class HistoryListComponent extends ListViewBaseComponent<ViewHistory, His
titleService: Title, titleService: Title,
translate: TranslateService, translate: TranslateService,
matSnackBar: MatSnackBar, matSnackBar: MatSnackBar,
private repo: HistoryRepositoryService,
private viewModelStore: ViewModelStoreService, private viewModelStore: ViewModelStoreService,
private router: Router, private router: Router,
private operator: OperatorService private operator: OperatorService,
private timeTravelService: TimeTravelService,
private http: HttpService,
private formBuilder: FormBuilder,
private motionRepo: MotionRepositoryService,
private promptService: PromptService
) { ) {
super(titleService, translate, matSnackBar, repo); super(titleService, translate, matSnackBar);
this.modelSelectForm = this.formBuilder.group({
model: []
});
this.collectionObserver = this.motionRepo.getViewModelListBehaviorSubject();
this.modelSelectForm.controls.model.valueChanges.subscribe((id: number) => {
this.queryElementId(this.currentCollection, id);
});
} }
/** /**
@ -60,23 +108,34 @@ export class HistoryListComponent extends ListViewBaseComponent<ViewHistory, His
*/ */
public ngOnInit(): void { public ngOnInit(): void {
super.setTitle('History'); super.setTitle('History');
this.initTable();
this.setFilters();
this.repo.getViewModelListObservable().subscribe(history => { this.dataSource.filterPredicate = (history: History, filter: string) => {
this.sortAndPublish(history); filter = filter ? filter.toLowerCase() : '';
});
}
/** if (!history) {
* Sorts the given ViewHistory array and sets it in the table data source return false;
* }
* @param unsortedHistoryList
*/ const userfullname = this.getUserName(history);
private sortAndPublish(unsortedHistoryList: ViewHistory[]): void { if (userfullname.toLowerCase().indexOf(filter) >= 0) {
const sortedList = unsortedHistoryList.map(history => history).filter(item => item.information.length > 0); return true;
sortedList.sort((a, b) => b.history.unixtime - a.history.unixtime); }
this.dataSource.data = sortedList;
if (
this.getElementInfo(history) &&
this.getElementInfo(history)
.toLowerCase()
.indexOf(filter) >= 0
) {
return true;
}
return (
this.parseInformation(history)
.toLowerCase()
.indexOf(filter) >= 0
);
};
} }
/** /**
@ -92,17 +151,12 @@ export class HistoryListComponent extends ListViewBaseComponent<ViewHistory, His
* Tries get the title of the BaseModel element corresponding to * Tries get the title of the BaseModel element corresponding to
* a history object. * a history object.
* *
* @param history the history * @param history a history object
* @returns the title of an old element or null if it could not be found * @returns the title of the history element or null if it could not be found
*/ */
public getElementInfo(history: ViewHistory): string { public getElementInfo(history: History): string {
const oldElementTitle = this.repo.getOldModelInfo(history.getCollectionString(), history.getModelId()); const model = this.viewModelStore.get(history.collectionString, history.modelId);
return model ? model.getListTitle() : null;
if (oldElementTitle) {
return oldElementTitle;
} else {
return null;
}
} }
/** /**
@ -111,46 +165,67 @@ export class HistoryListComponent extends ListViewBaseComponent<ViewHistory, His
* *
* @param history Represents the selected element * @param history Represents the selected element
*/ */
public async onClickRow(history: ViewHistory): Promise<void> { public async onClickRow(history: History): Promise<void> {
if (this.operator.isInGroupIds(2)) { if (!this.isSuperAdmin) {
await this.repo.browseHistory(history); return;
const element = this.viewModelStore.get(history.getCollectionString(), history.getModelId()); }
if (element && isDetailNavigable(element)) {
this.router.navigate([element.getDetailStateURL()]); await this.timeTravelService.loadHistoryPoint(history);
} else { const element = this.viewModelStore.get(history.collectionString, history.modelId);
const message = this.translate.instant('Cannot navigate to the selected history element.'); if (element && isDetailNavigable(element)) {
this.raiseError(message); this.router.navigate([element.getDetailStateURL()]);
} else {
const message = this.translate.instant('Cannot navigate to the selected history element.');
this.raiseError(message);
}
}
public getTimestamp(history: History): string {
return history.getLocaleString(langToLocale(this.translate.currentLang));
}
/**
* clears the whole history.
*/
public async clearHistory(): Promise<void> {
const title = this.translate.instant('Are you sure you want delete the whole history?');
if (await this.promptService.open(title)) {
try {
await this.http.delete(`${environment.urlPrefix}/core/history/information/`);
this.refresh();
} catch (e) {
this.raiseError(e);
} }
} }
} }
public getTimestamp(viewHistory: ViewHistory): string { public refresh(): void {
return viewHistory.history.getLocaleString(langToLocale(this.translate.currentLang)); if (this.currentCollection && this.currentModelId) {
} this.queryElementId(this.currentCollection, this.currentModelId);
/**
* Handler for the delete all button
*/
public onDeleteAllButton(): void {
if (this.operator.isInGroupIds(2)) {
this.repo.delete();
} }
} }
/** /**
* Returns a translated history information string which contains optional (translated) arguments. * Returns a translated history information string which contains optional (translated) arguments.
* *
* @param information history information string * @param history the history
*/ */
public parseInformation(information: string): string { public parseInformation(history: History): string {
if (information.length) { if (!history.information || !history.information.length) {
const base_string = this.translate.instant(information[0]); return '';
let argument_string;
if (information.length > 1) {
argument_string = this.translate.instant(information[1]);
}
return base_string.replace(/{arg1}/g, argument_string);
} }
const baseString = this.translate.instant(history.information[0]);
let argumentString;
if (history.information.length > 1) {
argumentString = this.translate.instant(history.information[1]);
}
return baseString.replace(/{arg1}/g, argumentString);
}
public getUserName(history: History): string {
const user = this.viewModelStore.get(ViewUser, history.user_id);
return user ? user.full_name : '';
} }
/** /**
@ -163,31 +238,13 @@ export class HistoryListComponent extends ListViewBaseComponent<ViewHistory, His
} }
/** /**
* Overwrites the dataSource's string filter with a more advanced option * Sets the data source to the request element id given by the collection string and the id.
* using the display methods of this class.
*/ */
private setFilters(): void { private async queryElementId(collectionString: string, id: number): Promise<void> {
this.dataSource.filterPredicate = (data, filter) => { const historyData = await this.http.get<History[]>(`${environment.urlPrefix}/core/history/information/`, null, {
if (!data || !data.information) { type: 'element',
return false; value: `${collectionString}:${id}`
} });
filter = filter ? filter.toLowerCase() : ''; this.dataSource.data = historyData.map(data => new History(data));
if (
this.getElementInfo(data) &&
this.getElementInfo(data)
.toLowerCase()
.indexOf(filter) >= 0
) {
return true;
}
if (data.user && data.user.full_name.toLowerCase().indexOf(filter) >= 0) {
return true;
}
return (
this.parseInformation(data.information)
.toLowerCase()
.indexOf(filter) >= 0
);
};
} }
} }

View File

@ -1,7 +1,4 @@
import { AppConfig } from '../../core/app-config'; import { AppConfig } from '../../core/app-config';
import { History } from 'app/shared/models/core/history';
import { HistoryRepositoryService } from 'app/core/repositories/history/history-repository.service';
import { ViewHistory } from './models/view-history';
/** /**
* Config object for history. * Config object for history.
@ -9,14 +6,6 @@ import { ViewHistory } from './models/view-history';
*/ */
export const HistoryAppConfig: AppConfig = { export const HistoryAppConfig: AppConfig = {
name: 'history', name: 'history',
models: [
{
collectionString: 'core/history',
model: History,
viewModel: ViewHistory,
repository: HistoryRepositoryService
}
],
mainMenuEntries: [ mainMenuEntries: [
{ {
route: '/history', route: '/history',

View File

@ -1,102 +0,0 @@
import { BaseViewModel } from 'app/site/base/base-view-model';
import { History } from 'app/shared/models/core/history';
import { ViewUser } from 'app/site/users/models/view-user';
export type ProxyHistory = History & { user?: ViewUser };
export interface HistoryTitleInformation {
element_id: string;
}
/**
* View model for history objects
*/
export class ViewHistory extends BaseViewModel<ProxyHistory> implements HistoryTitleInformation {
public static COLLECTIONSTRING = History.COLLECTIONSTRING;
/**
* Read the history property
*/
public get history(): ProxyHistory {
return this._model;
}
/**
* Gets the users ViewUser.
*/
public get user(): ViewUser | null {
return this.history.user;
}
/**
* Get the id of the history object
* Required by BaseViewModel
*
* @returns the id as number
*/
public get id(): number {
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
*
* @returns the element ID as String
*/
public get element_id(): string {
return this.history.element_id;
}
/**
* Get the information about the history
*
* @returns a string with the information to the history object
*/
public get information(): string {
return this.history.information;
}
/**
* Get the time of the history as number
*
* @returns the unix timestamp as number
*/
public get now(): string {
return this.history.now;
}
/**
* Construction of a ViewHistory
*
* @param history the real history BaseModel
* @param user the real user BaseModel
*/
public constructor(history: ProxyHistory) {
super(History.COLLECTIONSTRING, history);
}
/**
* Converts elementID into collection string
* @returns the CollectionString to the model
*/
public getCollectionString(): string {
return this.element_id.split(':')[0];
}
/**
* Extract the models ID from the elementID
* @returns a model id
*/
public getModelId(): number {
return +this.element_id.split(':')[1];
}
public updateDependencies(update: BaseViewModel): void {}
}

View File

@ -530,6 +530,9 @@ button.mat-menu-item.selected {
.spacer-left-10 { .spacer-left-10 {
margin-left: 10px; margin-left: 10px;
} }
.spacer-left-20 {
margin-left: 20px;
}
.spacer-left-50 { .spacer-left-50 {
margin-left: 50px !important; margin-left: 50px !important;
} }

View File

@ -20,7 +20,7 @@
], ],
"no-arg": true, "no-arg": true,
"no-bitwise": true, "no-bitwise": true,
"no-console": [true, "debug", "info", "time", "timeEnd", "trace"], "no-console": [true, "table", "clear", "count", "countReset", "info", "time", "timeEnd", "timeline", "timelineEnd", "trace"],
"no-construct": true, "no-construct": true,
"no-debugger": true, "no-debugger": true,
"no-duplicate-super": true, "no-duplicate-super": true,

View File

@ -494,7 +494,6 @@ class HistoryInformationView(utils_views.APIView):
Examples: Examples:
/?type=element&value=motions%2Fmotion%3A42 if your search for motion 42 /?type=element&value=motions%2Fmotion%3A42 if your search for motion 42
/?type=text&value=my%20question
Use DELETE to clear the history. Use DELETE to clear the history.
""" """
@ -509,15 +508,12 @@ class HistoryInformationView(utils_views.APIView):
self.permission_denied(self.request) self.permission_denied(self.request)
type = self.request.query_params.get("type") type = self.request.query_params.get("type")
value = self.request.query_params.get("value") value = self.request.query_params.get("value")
if type not in ("element", "text"): if type not in ("element"):
raise ValidationError( raise ValidationError(
{"detail": "Invalid input. Type should be 'element' or 'text'."} {"detail": "Invalid input. Type should be 'element' or 'text'."}
) )
if type == "element": # We currently just support searching by element id.
data = self.get_data_element_search(value) data = self.get_data_element_search(value)
else:
# type == "text"
data = self.get_data_text_search(value)
return data return data
def get_data_element_search(self, value): def get_data_element_search(self, value):
@ -525,7 +521,7 @@ class HistoryInformationView(utils_views.APIView):
Retrieves history information for element search. Retrieves history information for element search.
""" """
data = [] data = []
for instance in History.objects.filter(element_id=value): for instance in History.objects.filter(element_id=value).order_by("-now"):
data.append( data.append(
{ {
"element_id": instance.element_id, "element_id": instance.element_id,
@ -537,13 +533,6 @@ class HistoryInformationView(utils_views.APIView):
) )
return data return data
def get_data_text_search(self, value):
"""
Retrieves history information for text search.
"""
# TODO: Add results here.
return []
def delete(self, request, *args, **kwargs): def delete(self, request, *args, **kwargs):
""" """
Deletes and rebuilds the history. Deletes and rebuilds the history.

View File

@ -111,7 +111,7 @@ class WebsocketThroughputLogger:
async def check_and_flush(self) -> None: async def check_and_flush(self) -> None:
# If we waited longer then 60 seconds, flush the data. # If we waited longer then 60 seconds, flush the data.
current_time = time.time() current_time = time.time()
if current_time > (self.time + 20): if current_time > (self.time + 60):
send_ratio = receive_ratio = 1.0 send_ratio = receive_ratio = 1.0
if self.send_compressed > 0: if self.send_compressed > 0: