Audit screen

This commit is contained in:
Adina Țeudan 2021-09-15 23:21:51 +03:00
parent 889def7465
commit 38c5035521
7 changed files with 213 additions and 181 deletions

View File

@ -0,0 +1,38 @@
import { AuditModel } from '@redaction/red-ui-http';
import { Listable } from '@iqser/common-ui';
export class AuditModelWrapper implements Listable {
constructor(public auditModel: AuditModel) {}
get category(): string {
return this.auditModel.category;
}
get details(): any {
return this.auditModel.details;
}
get message(): string {
return this.auditModel.message;
}
get recordId(): string {
return this.auditModel.recordId;
}
get recordDate(): string {
return this.auditModel.recordDate;
}
get objectId(): string {
return this.auditModel.objectId;
}
get userId(): string {
return this.auditModel.userId;
}
get id() {
return this.auditModel.recordDate;
}
}

View File

@ -20,115 +20,100 @@
</div>
<div class="red-content-inner">
<div class="content-container">
<div class="header-item">
<span class="all-caps-label">
{{ 'audit-screen.table-header.title' | translate: { length: logs?.totalHits || 0 } }}
</span>
<div class="actions-wrapper">
<redaction-pagination
(pageChanged)="pageChanged($event)"
[settings]="{ currentPage: logs?.page || 0, totalPages: totalPages }"
></redaction-pagination>
<div class="separator">·</div>
<form [formGroup]="filterForm">
<div class="iqser-input-group w-150 mr-20">
<mat-form-field class="no-label">
<mat-select formControlName="category">
<mat-option *ngFor="let category of categories" [value]="category">
{{ translations[category] | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="iqser-input-group w-150">
<mat-form-field class="no-label">
<mat-select formControlName="userId">
<mat-select-trigger>
<redaction-initials-avatar
*ngIf="filterForm.get('userId').value !== ALL_USERS"
[userId]="filterForm.get('userId').value"
[withName]="true"
size="small"
></redaction-initials-avatar>
<div *ngIf="filterForm.get('userId').value === ALL_USERS" [translate]="ALL_USERS"></div>
</mat-select-trigger>
<mat-option *ngFor="let userId of userIds" [value]="userId">
<redaction-initials-avatar
*ngIf="userId !== ALL_USERS"
[userId]="userId"
[withName]="true"
size="small"
></redaction-initials-avatar>
<div *ngIf="userId === ALL_USERS" [translate]="ALL_USERS"></div>
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="separator">·</div>
<div class="iqser-input-group datepicker-wrapper mr-20">
<input [matDatepicker]="fromPicker" formControlName="from" placeholder="dd/mm/yy" />
<mat-datepicker-toggle [for]="fromPicker" matSuffix>
<mat-icon matDatepickerToggleIcon svgIcon="red:calendar"></mat-icon>
</mat-datepicker-toggle>
<mat-datepicker #fromPicker></mat-datepicker>
</div>
<div class="mr-20" translate="audit-screen.to"></div>
<div class="iqser-input-group datepicker-wrapper">
<input [matDatepicker]="toPicker" formControlName="to" placeholder="dd/mm/yy" />
<mat-datepicker-toggle [for]="toPicker" matSuffix>
<mat-icon matDatepickerToggleIcon svgIcon="red:calendar"></mat-icon>
</mat-datepicker-toggle>
<mat-datepicker #toPicker></mat-datepicker>
</div>
</form>
</div>
</div>
<div class="table-header" iqserSyncWidth="table-item">
<iqser-table-column-name
[label]="'audit-screen.table-col-names.message' | translate"
column="message"
></iqser-table-column-name>
<iqser-table-column-name
[label]="'audit-screen.table-col-names.date' | translate"
column="date"
></iqser-table-column-name>
<iqser-table-column-name
[label]="'audit-screen.table-col-names.user' | translate"
class="user-column"
column="user"
></iqser-table-column-name>
<iqser-table-column-name
[label]="'audit-screen.table-col-names.category' | translate"
column="category"
></iqser-table-column-name>
<div class="scrollbar-placeholder"></div>
</div>
<iqser-empty-state
*ngIf="!logs?.totalHits"
[text]="'audit-screen.no-data.title' | translate"
icon="red:document"
></iqser-empty-state>
<cdk-virtual-scroll-viewport [itemSize]="80" iqserHasScrollbar>
<div *cdkVirtualFor="let log of logs?.data" class="table-item">
<div>
{{ log.message }}
</div>
<div class="small-label">
{{ log.recordDate | date: 'd MMM. yyyy, hh:mm a' }}
</div>
<div class="user-column">
<redaction-initials-avatar [userId]="log.userId" [withName]="true" size="small"></redaction-initials-avatar>
</div>
<div [translate]="translations[log.category]"></div>
<div class="scrollbar-placeholder"></div>
</div>
</cdk-virtual-scroll-viewport>
<iqser-table
[headerTemplate]="headerTemplate"
[itemSize]="80"
[noDataIcon]="'red:document'"
[noDataText]="'audit-screen.no-data.title' | translate"
[totalSize]="logs?.totalHits || 0"
>
</iqser-table>
</div>
</div>
</div>
</section>
<ng-template #headerTemplate>
<div class="actions-wrapper">
<redaction-pagination
(pageChanged)="pageChanged($event)"
[settings]="{ currentPage: logs?.page || 0, totalPages: totalPages }"
></redaction-pagination>
<div class="separator">·</div>
<form [formGroup]="filterForm">
<div class="iqser-input-group w-150 mr-20">
<mat-form-field class="no-label">
<mat-select formControlName="category">
<mat-option *ngFor="let category of categories" [value]="category">
{{ translations[category] | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="iqser-input-group w-150">
<mat-form-field class="no-label">
<mat-select formControlName="userId">
<mat-select-trigger>
<redaction-initials-avatar
*ngIf="filterForm.get('userId').value !== ALL_USERS"
[userId]="filterForm.get('userId').value"
[withName]="true"
size="small"
></redaction-initials-avatar>
<div *ngIf="filterForm.get('userId').value === ALL_USERS" [translate]="ALL_USERS"></div>
</mat-select-trigger>
<mat-option *ngFor="let userId of userIds" [value]="userId">
<redaction-initials-avatar
*ngIf="userId !== ALL_USERS"
[userId]="userId"
[withName]="true"
size="small"
></redaction-initials-avatar>
<div *ngIf="userId === ALL_USERS" [translate]="ALL_USERS"></div>
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="separator">·</div>
<div class="iqser-input-group datepicker-wrapper mr-20">
<input [matDatepicker]="fromPicker" formControlName="from" placeholder="dd/mm/yy" />
<mat-datepicker-toggle [for]="fromPicker" matSuffix>
<mat-icon matDatepickerToggleIcon svgIcon="red:calendar"></mat-icon>
</mat-datepicker-toggle>
<mat-datepicker #fromPicker></mat-datepicker>
</div>
<div class="mr-20" translate="audit-screen.to"></div>
<div class="iqser-input-group datepicker-wrapper">
<input [matDatepicker]="toPicker" formControlName="to" placeholder="dd/mm/yy" />
<mat-datepicker-toggle [for]="toPicker" matSuffix>
<mat-icon matDatepickerToggleIcon svgIcon="red:calendar"></mat-icon>
</mat-datepicker-toggle>
<mat-datepicker #toPicker></mat-datepicker>
</div>
</form>
</div>
</ng-template>
<ng-template #messageTemplate let-log="entity">
<div class="cell">
{{ log.message }}
</div>
</ng-template>
<ng-template #dateTemplate let-log="entity">
<div class="small-label cell">
{{ log.recordDate | date: 'd MMM. yyyy, hh:mm a' }}
</div>
</ng-template>
<ng-template #userTemplate let-log="entity">
<div class="user-column cell">
<redaction-initials-avatar [userId]="log.userId" [withName]="true" size="small"></redaction-initials-avatar>
</div>
</ng-template>
<ng-template #categoryTemplate let-log="entity">
<div [translate]="translations[log.category]" class="cell"></div>
</ng-template>

View File

@ -1,44 +1,24 @@
.content-container {
.header-item {
justify-content: space-between;
:host ::ng-deep iqser-table iqser-table-header .header-item {
justify-content: space-between;
}
.actions-wrapper,
form {
display: flex;
align-items: center;
.iqser-input-group {
margin-top: 0 !important;
}
.actions-wrapper,
form {
display: flex;
align-items: center;
.iqser-input-group {
margin-top: 0 !important;
}
.separator {
margin: 0 20px;
font-weight: bold;
font-size: 16px;
opacity: 0.7;
}
.mr-20 {
margin-right: 20px;
}
.separator {
margin: 0 20px;
font-weight: bold;
font-size: 16px;
opacity: 0.7;
}
cdk-virtual-scroll-viewport {
::ng-deep.cdk-virtual-scroll-content-wrapper {
grid-template-columns: 1fr 1fr 1fr 1fr 11px;
.table-item {
> div {
padding: 0 24px;
}
}
}
&.has-scrollbar:hover {
::ng-deep.cdk-virtual-scroll-content-wrapper {
grid-template-columns: 1fr 1fr 1fr 1fr;
}
}
.mr-20 {
margin-right: 20px;
}
}

View File

@ -1,41 +1,49 @@
import { Component, OnDestroy } from '@angular/core';
import { Component, forwardRef, Injector, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { AuditControllerService, AuditResponse, AuditSearchRequest } from '@redaction/red-ui-http';
import { AuditControllerService, AuditModel, AuditResponse, AuditSearchRequest } from '@redaction/red-ui-http';
import { Moment } from 'moment';
import { applyIntervalConstraints } from '@utils/date-inputs-utils';
import { AutoUnsubscribe, LoadingService } from '@iqser/common-ui';
import { DefaultListingServices, KeysOf, ListingComponent, LoadingService, TableColumnConfig } from '@iqser/common-ui';
import { auditCategoriesTranslations } from '../../translations/audit-categories-translations';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { UserService } from '@services/user.service';
import { AuditModelWrapper } from '../../../../models/audit-model-wrapper.model';
const PAGE_SIZE = 50;
@Component({
selector: 'redaction-audit-screen',
templateUrl: './audit-screen.component.html',
styleUrls: ['./audit-screen.component.scss']
styleUrls: ['./audit-screen.component.scss'],
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => AuditScreenComponent) }]
})
export class AuditScreenComponent extends AutoUnsubscribe implements OnDestroy {
export class AuditScreenComponent extends ListingComponent<AuditModelWrapper> implements OnDestroy, OnInit {
readonly ALL_CATEGORIES = 'allCategories';
readonly ALL_USERS = _('audit-screen.all-users');
readonly translations = auditCategoriesTranslations;
readonly currentUser = this._userService.currentUser;
@ViewChild('messageTemplate', { static: true }) messageTemplate: TemplateRef<never>;
@ViewChild('dateTemplate', { static: true }) dateTemplate: TemplateRef<never>;
@ViewChild('userTemplate', { static: true }) userTemplate: TemplateRef<never>;
@ViewChild('categoryTemplate', { static: true }) categoryTemplate: TemplateRef<never>;
filterForm: FormGroup;
categories: string[] = [];
userIds: Set<string>;
logs: AuditResponse;
tableColumnConfigs: TableColumnConfig<AuditModelWrapper>[];
readonly tableHeaderLabel = _('audit-screen.table-header.title');
protected readonly _primaryKey: KeysOf<AuditModelWrapper> = 'recordDate';
private _previousFrom: Moment;
private _previousTo: Moment;
constructor(
private readonly _userService: UserService,
protected readonly _injector: Injector,
private readonly _formBuilder: FormBuilder,
private readonly _loadingService: LoadingService,
private readonly _auditControllerService: AuditControllerService
) {
super();
super(_injector);
this.filterForm = this._formBuilder.group({
category: [this.ALL_CATEGORIES],
userId: [this.ALL_USERS],
@ -43,13 +51,11 @@ export class AuditScreenComponent extends AutoUnsubscribe implements OnDestroy {
to: []
});
this.addSubscription = this.filterForm.valueChanges.subscribe(value => {
this.addSubscription = this.filterForm.valueChanges.subscribe(async value => {
if (!this._updateDateFilters(value)) {
this._fetchData();
await this._fetchData();
}
});
this._fetchData();
}
get totalPages(): number {
@ -59,8 +65,35 @@ export class AuditScreenComponent extends AutoUnsubscribe implements OnDestroy {
return Math.ceil(this.logs.totalHits / PAGE_SIZE);
}
pageChanged(page: number) {
this._fetchData(page);
async pageChanged(page: number) {
await this._fetchData(page);
}
async ngOnInit() {
this._configureTableColumns();
await this._fetchData();
}
private _configureTableColumns() {
this.tableColumnConfigs = [
{
label: _('audit-screen.table-col-names.message'),
template: this.messageTemplate
},
{
label: _('audit-screen.table-col-names.date'),
template: this.dateTemplate
},
{
label: _('audit-screen.table-col-names.user'),
class: 'user-column',
template: this.userTemplate
},
{
label: _('audit-screen.table-col-names.category'),
template: this.categoryTemplate
}
];
}
private _updateDateFilters(value): boolean {
@ -73,7 +106,7 @@ export class AuditScreenComponent extends AutoUnsubscribe implements OnDestroy {
return false;
}
private _fetchData(page?: number) {
private async _fetchData(page?: number) {
this._loadingService.start();
const promises = [];
const category = this.filterForm.get('category').value;
@ -96,15 +129,16 @@ export class AuditScreenComponent extends AutoUnsubscribe implements OnDestroy {
promises.push(this._auditControllerService.getAuditCategories().toPromise());
promises.push(this._auditControllerService.searchAuditLog(logsRequestBody).toPromise());
Promise.all(promises).then(data => {
this.categories = data[0].map(c => c.category);
this.categories.splice(0, 0, this.ALL_CATEGORIES);
this.logs = data[1];
this.userIds = new Set<string>([this.ALL_USERS]);
for (const id of this.logs.data.map(log => log.userId).filter(uid => !!uid)) {
this.userIds.add(id);
}
this._loadingService.stop();
});
const data = await Promise.all(promises);
this.categories = data[0].map(c => c.category);
this.categories.splice(0, 0, this.ALL_CATEGORIES);
this.logs = data[1];
const entities = this.logs.data.map((log: AuditModel) => new AuditModelWrapper(log));
this.entitiesService.setEntities(entities);
this.userIds = new Set<string>([this.ALL_USERS]);
for (const id of this.logs.data.map(log => log.userId).filter(uid => !!uid)) {
this.userIds.add(id);
}
this._loadingService.stop();
}
}

View File

@ -1,4 +1,4 @@
<div (click)="selectPage(currentPage - 1)" [class.disabled]="currentPage < 2" class="page" translate="pagination.previous"></div>
<div (click)="selectPage(currentPage - 1)" [class.disabled]="currentPage < 1" class="page" translate="pagination.previous"></div>
<span>|</span>
<div
(click)="selectPage(page)"

View File

@ -1,11 +1,10 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
const DISPLAYED_ITEMS = 5;
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
selector: 'redaction-pagination',
templateUrl: './pagination.component.html',
styleUrls: ['./pagination.component.scss']
styleUrls: ['./pagination.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class PaginationComponent {
displayedPages: (number | string)[];
@ -31,10 +30,6 @@ export class PaginationComponent {
this._updatePagesArray();
}
get allDisplayed(): boolean {
return this.totalPages > DISPLAYED_ITEMS;
}
selectPage(page: number | string) {
if (page !== '...') {
this.pageChanged.emit(page as number);

@ -1 +1 @@
Subproject commit 08222bdaeb1e16b37c75b66a2a49b0b3b96d9de7
Subproject commit d6764126b4ad7f94b977b4c0f4e7a6ec31e6f374