From e604da32406a50781d8dd14279bf9dfc8f27db2f Mon Sep 17 00:00:00 2001 From: FinnStutzenstein Date: Mon, 17 Jun 2019 13:45:16 +0200 Subject: [PATCH] Quicklink to history (closes #4777) --- .../collection-string-mapper.service.ts | 25 +++++++++++ client/src/app/site/base/base-view-model.ts | 7 +++ .../history-list/history-list.component.ts | 45 ++++++++++++++----- .../motion-detail.component.html | 7 +++ openslides/core/views.py | 2 +- 5 files changed, 74 insertions(+), 12 deletions(-) diff --git a/client/src/app/core/core-services/collection-string-mapper.service.ts b/client/src/app/core/core-services/collection-string-mapper.service.ts index deea97923..f1c5169be 100644 --- a/client/src/app/core/core-services/collection-string-mapper.service.ts +++ b/client/src/app/core/core-services/collection-string-mapper.service.ts @@ -112,4 +112,29 @@ export class CollectionStringMapperService { public getAllRepositories(): BaseRepository[] { return Object.values(this.collectionStringMapping).map((types: CollectionStringMappedTypes) => types[2]); } + + /** + * Validates the given element id. It must have the form `:`, with + * being a registered collection and the id a valid integer greater then 0. + * + * @param elementId The element id. + * @returns true, if the element id is valid. + */ + public isElementIdValid(elementId: any): boolean { + if (!elementId || typeof elementId !== 'string') { + return false; + } + + const splitted = elementId.split(':'); + if (splitted.length !== 2) { + return false; + } + + const id = parseInt(splitted[1], 10); + if (isNaN(id) || id <= 0) { + return false; + } + + return Object.keys(this.collectionStringMapping).some(collection => collection === splitted[0]); + } } diff --git a/client/src/app/site/base/base-view-model.ts b/client/src/app/site/base/base-view-model.ts index aa55b7b18..f6751e5cb 100644 --- a/client/src/app/site/base/base-view-model.ts +++ b/client/src/app/site/base/base-view-model.ts @@ -38,6 +38,13 @@ export abstract class BaseViewModel return this._collectionString; } + /** + * @returns the element id of the model + */ + public get elementId(): string { + return `${this.collectionString}:${this.id}`; + } + public getTitle: () => string; public getListTitle: () => string; diff --git a/client/src/app/site/history/components/history-list/history-list.component.ts b/client/src/app/site/history/components/history-list/history-list.component.ts index c23e22ecd..87e3fea25 100644 --- a/client/src/app/site/history/components/history-list/history-list.component.ts +++ b/client/src/app/site/history/components/history-list/history-list.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit } from '@angular/core'; import { MatSnackBar, MatTableDataSource } from '@angular/material'; -import { Router } from '@angular/router'; +import { Router, ActivatedRoute } from '@angular/router'; import { Title } from '@angular/platform-browser'; import { TranslateService } from '@ngx-translate/core'; @@ -21,6 +21,7 @@ import { MotionRepositoryService } from 'app/core/repositories/motions/motion-re 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'; +import { CollectionStringMapperService } from 'app/core/core-services/collection-string-mapper.service'; /** * A list view for the history. @@ -40,10 +41,6 @@ export class HistoryListComponent extends BaseViewComponent implements OnInit { public dataSource: MatTableDataSource = new MatTableDataSource(); - public get isSuperAdmin(): boolean { - return this.operator.isSuperAdmin(); - } - public pageSizes = [50, 100, 150, 200, 250]; /** @@ -67,6 +64,10 @@ export class HistoryListComponent extends BaseViewComponent implements OnInit { return this.modelSelectForm.controls.model.value; } + public get isSuperAdmin(): boolean { + return this.operator.isSuperAdmin(); + } + /** * Constructor for the history list component * @@ -89,7 +90,9 @@ export class HistoryListComponent extends BaseViewComponent implements OnInit { private http: HttpService, private formBuilder: FormBuilder, private motionRepo: MotionRepositoryService, - private promptService: PromptService + private promptService: PromptService, + private activatedRoute: ActivatedRoute, + private collectionMapper: CollectionStringMapperService ) { super(titleService, translate, matSnackBar); @@ -99,7 +102,15 @@ export class HistoryListComponent extends BaseViewComponent implements OnInit { this.collectionObserver = this.motionRepo.getViewModelListBehaviorSubject(); this.modelSelectForm.controls.model.valueChanges.subscribe((id: number) => { - this.queryElementId(this.currentCollection, id); + const elementId = `${this.currentCollection}:${id}`; + this.queryByElementId(elementId); + + // Update the URL. + this.router.navigate([], { + relativeTo: this.activatedRoute, + queryParams: { element: elementId }, + replaceUrl: true + }); }); } @@ -136,6 +147,18 @@ export class HistoryListComponent extends BaseViewComponent implements OnInit { .indexOf(filter) >= 0 ); }; + + // If an element id is given, validate it and update the view. + const params = this.activatedRoute.snapshot.queryParams; + if (this.collectionMapper.isElementIdValid(params.element)) { + this.queryByElementId(params.element); + this.modelSelectForm.patchValue( + { + model: parseInt(params.element.split(':')[1], 10) + }, + { emitEvent: false } + ); + } } /** @@ -201,7 +224,7 @@ export class HistoryListComponent extends BaseViewComponent implements OnInit { public refresh(): void { if (this.currentCollection && this.currentModelId) { - this.queryElementId(this.currentCollection, this.currentModelId); + this.queryByElementId(`${this.currentCollection}:${this.currentModelId}`); } } @@ -238,12 +261,12 @@ export class HistoryListComponent extends BaseViewComponent implements OnInit { } /** - * Sets the data source to the request element id given by the collection string and the id. + * Sets the data source to the requested element id. */ - private async queryElementId(collectionString: string, id: number): Promise { + private async queryByElementId(elementId: string): Promise { const historyData = await this.http.get(`${environment.urlPrefix}/core/history/information/`, null, { type: 'element', - value: `${collectionString}:${id}` + value: elementId }); this.dataSource.data = historyData.map(data => new History(data)); } diff --git a/client/src/app/site/motions/modules/motion-detail/components/motion-detail/motion-detail.component.html b/client/src/app/site/motions/modules/motion-detail/components/motion-detail/motion-detail.component.html index ea0c7b9e1..f0f4e2441 100644 --- a/client/src/app/site/motions/modules/motion-detail/components/motion-detail/motion-detail.component.html +++ b/client/src/app/site/motions/modules/motion-detail/components/motion-detail/motion-detail.component.html @@ -101,6 +101,13 @@ Show entire motion text + +
diff --git a/openslides/core/views.py b/openslides/core/views.py index b310ca6f2..3fb78ea38 100644 --- a/openslides/core/views.py +++ b/openslides/core/views.py @@ -504,7 +504,7 @@ class HistoryInformationView(utils_views.APIView): """ Checks permission and parses query parameters. """ - if not has_perm(self.request.user, "users.can_see_history"): + if not has_perm(self.request.user, "core.can_see_history"): self.permission_denied(self.request) type = self.request.query_params.get("type") value = self.request.query_params.get("value")