feat: add audit-trails and view-audit-trail components (#406)
Fixes #404
This commit is contained in:
@@ -35,6 +35,7 @@ import {
|
||||
faExclamationTriangle,
|
||||
faEye,
|
||||
faEyeSlash,
|
||||
faFile,
|
||||
faFileAlt,
|
||||
faFileUpload,
|
||||
faFileWord,
|
||||
@@ -116,6 +117,7 @@ library.add(
|
||||
faExclamationTriangle,
|
||||
faEye,
|
||||
faEyeSlash,
|
||||
faFile,
|
||||
faFileAlt,
|
||||
faFileUpload,
|
||||
faFileWord,
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/** Angular Imports */
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Resolve } from '@angular/router';
|
||||
|
||||
/** rxjs Imports */
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
/** Custom Services */
|
||||
import { SystemService } from '../system.service';
|
||||
|
||||
/**
|
||||
* Audit Trail Search Template data resolver.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AuditTrailSearchTemplateResolver implements Resolve<Object> {
|
||||
|
||||
/**
|
||||
* @param {SystemService} systemService System service.
|
||||
*/
|
||||
constructor(private systemService: SystemService) {}
|
||||
|
||||
/**
|
||||
* Returns the Audit Trail Search Template data.
|
||||
* @returns {Observable<any>}
|
||||
*/
|
||||
resolve(): Observable<any> {
|
||||
return this.systemService.getAuditTrailSearchTemplate();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/** Angular Imports */
|
||||
import { CollectionViewer, DataSource } from '@angular/cdk/collections';
|
||||
|
||||
/** rxjs Imports */
|
||||
import { Observable, BehaviorSubject } from 'rxjs';
|
||||
|
||||
/** Custom Services */
|
||||
import { SystemService } from '../system.service';
|
||||
|
||||
/**
|
||||
* Audit Trails custom data source to implement server side filtering, pagination and sorting.
|
||||
*/
|
||||
export class AuditTrailsDataSource implements DataSource<any> {
|
||||
|
||||
/** Audit Trails behavior subject to represent loaded audit trails page. */
|
||||
private auditTrailsSubject = new BehaviorSubject<any[]>([]);
|
||||
/** Records subject to represent total number of filtered audit trail records. */
|
||||
private recordsSubject = new BehaviorSubject<number>(0);
|
||||
|
||||
/** Records observable which can be subscribed to get the value of total number of filtered audit trail records. */
|
||||
public records$ = this.recordsSubject.asObservable();
|
||||
|
||||
/**
|
||||
* @param {SystemService} systemService System Service.
|
||||
*/
|
||||
constructor(private systemService: SystemService) { }
|
||||
|
||||
/**
|
||||
* Gets audit trails on the basis of provided parameters and emits the value.
|
||||
* @param {any} filterBy Properties by which entries should be filtered.
|
||||
* @param {string} orderBy Property by which entries should be sorted.
|
||||
* @param {string} sortOrder Sort order: ascending or descending.
|
||||
* @param {number} pageIndex Page number.
|
||||
* @param {number} limit Number of entries within the page.
|
||||
*/
|
||||
getAuditTrails(filterBy: any, orderBy: string = '', sortOrder: string = '', pageIndex: number = 0, limit: number = 10) {
|
||||
this.auditTrailsSubject.next([]);
|
||||
this.systemService.getAuditTrails(filterBy, orderBy, sortOrder, pageIndex * limit, limit)
|
||||
.subscribe((auditTrails: any) => {
|
||||
this.recordsSubject.next(auditTrails.totalFilteredRecords);
|
||||
this.auditTrailsSubject.next(auditTrails.pageItems);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {CollectionViewer} collectionViewer
|
||||
*/
|
||||
connect(collectionViewer: CollectionViewer): Observable<any[]> {
|
||||
return this.auditTrailsSubject.asObservable();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {CollectionViewer} collectionViewer
|
||||
*/
|
||||
disconnect(collectionViewer: CollectionViewer): void {
|
||||
this.auditTrailsSubject.complete();
|
||||
this.recordsSubject.complete();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
<div class="container m-b-20" fxLayout="row" fxLayoutAlign="end" fxLayoutGap="20px">
|
||||
<button mat-raised-button color="primary" (click)="downloadCSV()">
|
||||
<fa-icon icon="file"></fa-icon>
|
||||
Download CSV
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="container" fxLayout="row wrap" fxLayoutGap="2%" fxLayout.lt-md="column">
|
||||
|
||||
<mat-form-field fxFlex="48%">
|
||||
<mat-label>Resource ID</mat-label>
|
||||
<input matInput [formControl]="resourceId">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field fxFlex="48%">
|
||||
<mat-label>Status</mat-label>
|
||||
<mat-select [formControl]="processingResult" (selectionChange)="applyFilter($event.value, 'processingResult')">
|
||||
<mat-option *ngFor="let processingResult of auditTrailSearchTemplateData.processingResults" [value]="processingResult.id">
|
||||
{{ processingResult.processingResult }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field fxFlex="48%">
|
||||
<mat-label>User</mat-label>
|
||||
<input matInput [formControl]="user" [matAutocomplete]="userNameAutocomplete">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field fxFlex="48%">
|
||||
<mat-label>Action</mat-label>
|
||||
<input matInput [formControl]="actionName" [matAutocomplete]="actionNameAutocomplete">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field fxFlex="48%">
|
||||
<mat-label>Entity</mat-label>
|
||||
<input matInput [formControl]="entityName" [matAutocomplete]="entityNameAutocomplete">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field fxFlex="48%">
|
||||
<mat-label>Checker</mat-label>
|
||||
<input matInput [formControl]="checker" [matAutocomplete]="checkerAutocomplete">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field fxFlex="48%">
|
||||
<mat-label>Maker From Date</mat-label>
|
||||
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="fromDatePicker" [formControl]="fromDate">
|
||||
<mat-datepicker-toggle matSuffix [for]="fromDatePicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #fromDatePicker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field fxFlex="48%">
|
||||
<mat-label>Maker To Date</mat-label>
|
||||
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="toDatePicker" [formControl]="toDate">
|
||||
<mat-datepicker-toggle matSuffix [for]="toDatePicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #toDatePicker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field fxFlex="48%">
|
||||
<mat-label>Checker From Date</mat-label>
|
||||
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="checkedFromDatePicker"
|
||||
[formControl]="checkedFromDate">
|
||||
<mat-datepicker-toggle matSuffix [for]="checkedFromDatePicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #checkedFromDatePicker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field fxFlex="48%">
|
||||
<mat-label>Checked To Date</mat-label>
|
||||
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="checkedToDatePicker" [formControl]="checkedToDate">
|
||||
<mat-datepicker-toggle matSuffix [for]="checkedToDatePicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #checkedToDatePicker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Autocomplete data -->
|
||||
<mat-autocomplete autoActiveFirstOption #userNameAutocomplete="matAutocomplete" [displayWith]="displayUserName">
|
||||
<mat-option *ngFor="let user of filteredUserData | async " [value]="{ id: user.id, name: user.username }">
|
||||
{{ user.username }}
|
||||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
|
||||
<mat-autocomplete autoActiveFirstOption #actionNameAutocomplete="matAutocomplete" [displayWith]="displayActionName">
|
||||
<mat-option *ngFor="let action of filteredActionData | async" [value]="action">
|
||||
{{ action }}
|
||||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
|
||||
<mat-autocomplete autoActiveFirstOption #entityNameAutocomplete="matAutocomplete" [displayWith]="displayEntityName">
|
||||
<mat-option *ngFor="let entity of filteredEntityData | async" [value]="entity">
|
||||
{{ entity }}
|
||||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
|
||||
<mat-autocomplete autoActiveFirstOption #checkerAutocomplete="matAutocomplete" [displayWith]="displayUserName">
|
||||
<mat-option *ngFor="let user of filteredCheckerData | async " [value]="{ id: user.id, name: user.username }">
|
||||
{{ user.username }}
|
||||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
|
||||
<div class="container mat-elevation-z8">
|
||||
|
||||
<table mat-table [dataSource]="dataSource" matSort>
|
||||
|
||||
<ng-container matColumnDef="id">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Trail ID </th>
|
||||
<td mat-cell *matCellDef="let auditTrail"> {{ auditTrail.id }} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="resourceId">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Resource ID </th>
|
||||
<td mat-cell *matCellDef="let auditTrail"> {{ auditTrail.resourceId }} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="processingResult">
|
||||
<th mat-header-cell *matHeaderCellDef> Status </th>
|
||||
<td mat-cell *matCellDef="let auditTrail"> {{ auditTrail.processingResult }} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="maker">
|
||||
<th mat-header-cell *matHeaderCellDef> Made By </th>
|
||||
<td mat-cell *matCellDef="let auditTrail"> {{ auditTrail.maker }} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actionName">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Action </th>
|
||||
<td mat-cell *matCellDef="let auditTrail"> {{ auditTrail.actionName }} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="entityName">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Entity </th>
|
||||
<td mat-cell *matCellDef="let auditTrail"> {{ auditTrail.entityName }} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="officeName">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Office </th>
|
||||
<td mat-cell *matCellDef="let auditTrail"> {{ auditTrail.officeName }} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="madeOnDate">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Made Date </th>
|
||||
<td mat-cell *matCellDef="let auditTrail"> {{ auditTrail.madeOnDate | date }} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="checker">
|
||||
<th mat-header-cell *matHeaderCellDef> Checker </th>
|
||||
<td mat-cell *matCellDef="let auditTrail"> {{ auditTrail.checker }} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="checkedOnDate">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Checked Date </th>
|
||||
<td mat-cell *matCellDef="let auditTrail"> {{ auditTrail.checkedOnDate | date }} </td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="[row.id]" class="select-row">
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<mat-paginator [length]="dataSource?.records$ | async" [pageSize]="10" [pageSizeOptions]="[10, 25, 50, 100]"
|
||||
showFirstLastButtons></mat-paginator>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
table {
|
||||
width: 100%;
|
||||
|
||||
.select-row:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AuditTrailsComponent } from './audit-trails.component';
|
||||
|
||||
describe('AuditTrailsComponent', () => {
|
||||
let component: AuditTrailsComponent;
|
||||
let fixture: ComponentFixture<AuditTrailsComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ AuditTrailsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AuditTrailsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,417 @@
|
||||
/** Angular Imports */
|
||||
import { Component, OnInit, ViewChild, AfterViewInit } from '@angular/core';
|
||||
import { MatPaginator, MatSort } from '@angular/material';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { DatePipe } from '@angular/common';
|
||||
|
||||
/** Custom Data Source */
|
||||
import { AuditTrailsDataSource } from './audit-trail.datasource';
|
||||
|
||||
/** Custom Services */
|
||||
import { SystemService } from '../system.service';
|
||||
|
||||
/** rxjs Imports */
|
||||
import { merge } from 'rxjs';
|
||||
import { tap, debounceTime, distinctUntilChanged, startWith, map } from 'rxjs/operators';
|
||||
|
||||
/**
|
||||
* Audit Trails Component.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-audit-trails',
|
||||
templateUrl: './audit-trails.component.html',
|
||||
styleUrls: ['./audit-trails.component.scss']
|
||||
})
|
||||
export class AuditTrailsComponent implements OnInit, AfterViewInit {
|
||||
|
||||
/** Minimum date allowed. */
|
||||
minDate = new Date(2000, 0, 1);
|
||||
/** Maximum date allowed. */
|
||||
maxDate = new Date();
|
||||
/** Audit Trails Data */
|
||||
auditTrailsData: any;
|
||||
/** Filtered user data for autocomplete. */
|
||||
filteredUserData: any;
|
||||
/** Filtered action data for autocomplete. */
|
||||
filteredActionData: any;
|
||||
/** Filtered entity data for autocomplete. */
|
||||
filteredEntityData: any;
|
||||
/** Filtered checker data for autocomplete. */
|
||||
filteredCheckerData: any;
|
||||
/** Audit Trail Search Template Data. */
|
||||
auditTrailSearchTemplateData: any;
|
||||
/** Columns to be displayed in audit trails table. */
|
||||
displayedColumns: string[] = ['id', 'resourceId', 'processingResult', 'maker', 'actionName', 'entityName', 'officeName', 'madeOnDate', 'checker', 'checkedOnDate'];
|
||||
/** Data source for audit trails table. */
|
||||
dataSource: AuditTrailsDataSource;
|
||||
/** Audit Trails filter. */
|
||||
filterAuditTrailsBy = [
|
||||
{
|
||||
type: 'actionName',
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
type: 'entityName',
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
type: 'resourceId',
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
type: 'makerId',
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
type: 'makerDateTimeFrom',
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
type: 'makerDateTimeTo',
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
type: 'checkerDateTimeFrom',
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
type: 'checkerDateTimeTo',
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
type: 'checkerId',
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
type: 'processingResult',
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
type: 'dateFormat',
|
||||
value: 'yyyy-MM-dd'
|
||||
},
|
||||
{
|
||||
type: 'locale',
|
||||
value: 'en'
|
||||
}
|
||||
];
|
||||
/** User form control. */
|
||||
user = new FormControl('');
|
||||
/** From date form control. */
|
||||
fromDate = new FormControl();
|
||||
/** Checked from date form control. */
|
||||
checkedFromDate = new FormControl();
|
||||
/** Processing result form control. */
|
||||
processingResult = new FormControl();
|
||||
/** Action name form control. */
|
||||
actionName = new FormControl();
|
||||
/** Resource ID form control. */
|
||||
resourceId = new FormControl('');
|
||||
/** To date form control. */
|
||||
toDate = new FormControl();
|
||||
/** Checked to date form control. */
|
||||
checkedToDate = new FormControl();
|
||||
/** Entity name form control. */
|
||||
entityName = new FormControl();
|
||||
/** Checker form control. */
|
||||
checker = new FormControl();
|
||||
|
||||
/** Paginator for audit trails table. */
|
||||
@ViewChild(MatPaginator) paginator: MatPaginator;
|
||||
/** Sorter for audit trails table. */
|
||||
@ViewChild(MatSort) sort: MatSort;
|
||||
|
||||
/**
|
||||
* Retrieves the audit trail search template data from `resolve`.
|
||||
* @param {ActivatedRoute} route Activated Route.
|
||||
* @param {SystemService} systemService System Service.
|
||||
*/
|
||||
constructor(private route: ActivatedRoute,
|
||||
private systemService: SystemService,
|
||||
private datePipe: DatePipe) {
|
||||
this.route.data.subscribe((data: { auditTrailSearchTemplate: any }) => {
|
||||
this.auditTrailSearchTemplateData = data.auditTrailSearchTemplate;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets filtered users, actions and entities for autocomplete and audit trails table.
|
||||
*/
|
||||
ngOnInit() {
|
||||
this.setFilteredUsers();
|
||||
this.setFilteredActions();
|
||||
this.setFilteredEntities();
|
||||
this.setFilteredCheckers();
|
||||
this.getAuditTrails();
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes to all search filters:
|
||||
* User Name, From Date, To Date, Checked From Date, Checked To Date, Resource ID, Action Name, Entity Name, Checker
|
||||
* sort change and page change.
|
||||
*/
|
||||
ngAfterViewInit() {
|
||||
this.user.valueChanges
|
||||
.pipe(
|
||||
map(value => value.id ? value.id : ''),
|
||||
debounceTime(500),
|
||||
distinctUntilChanged(),
|
||||
tap((filterValue) => {
|
||||
this.applyFilter(filterValue, 'makerId');
|
||||
})
|
||||
).subscribe();
|
||||
|
||||
this.fromDate.valueChanges
|
||||
.pipe(
|
||||
debounceTime(500),
|
||||
distinctUntilChanged(),
|
||||
tap((filterValue) => {
|
||||
this.applyFilter(this.getDate(filterValue), 'makerDateTimeFrom');
|
||||
})
|
||||
).subscribe();
|
||||
|
||||
this.toDate.valueChanges
|
||||
.pipe(
|
||||
debounceTime(500),
|
||||
distinctUntilChanged(),
|
||||
tap((filterValue) => {
|
||||
this.applyFilter(this.getDate(filterValue), 'makerDateTimeTo');
|
||||
})
|
||||
).subscribe();
|
||||
|
||||
this.checkedFromDate.valueChanges
|
||||
.pipe(
|
||||
debounceTime(500),
|
||||
distinctUntilChanged(),
|
||||
tap((filterValue) => {
|
||||
this.applyFilter(this.getDate(filterValue), 'checkerDateTimeFrom');
|
||||
})
|
||||
).subscribe();
|
||||
|
||||
this.checkedToDate.valueChanges
|
||||
.pipe(
|
||||
debounceTime(500),
|
||||
distinctUntilChanged(),
|
||||
tap((filterValue) => {
|
||||
this.applyFilter(this.getDate(filterValue), 'checkerDateTimeTo');
|
||||
})
|
||||
).subscribe();
|
||||
|
||||
this.resourceId.valueChanges
|
||||
.pipe(
|
||||
debounceTime(500),
|
||||
distinctUntilChanged(),
|
||||
tap((filterValue) => {
|
||||
this.applyFilter(filterValue, 'resourceId');
|
||||
})
|
||||
).subscribe();
|
||||
|
||||
this.actionName.valueChanges
|
||||
.pipe(
|
||||
map(value => value ? value : ''),
|
||||
debounceTime(500),
|
||||
distinctUntilChanged(),
|
||||
tap((filterValue) => {
|
||||
this.applyFilter(filterValue, 'actionName');
|
||||
})
|
||||
).subscribe();
|
||||
|
||||
this.entityName.valueChanges
|
||||
.pipe(
|
||||
map(value => value ? value : ''),
|
||||
debounceTime(500),
|
||||
distinctUntilChanged(),
|
||||
tap((filterValue) => {
|
||||
this.applyFilter(filterValue, 'entityName');
|
||||
})
|
||||
).subscribe();
|
||||
|
||||
this.checker.valueChanges
|
||||
.pipe(
|
||||
map(value => value ? value : ''),
|
||||
debounceTime(500),
|
||||
distinctUntilChanged(),
|
||||
tap((filterValue) => {
|
||||
this.applyFilter(filterValue, 'checkerId');
|
||||
})
|
||||
).subscribe();
|
||||
|
||||
this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
|
||||
|
||||
merge(this.sort.sortChange, this.paginator.page)
|
||||
.pipe(
|
||||
tap(() => this.loadAuditTrailsPage())
|
||||
).subscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the data source for audit trails table and loads the first page.
|
||||
*/
|
||||
getAuditTrails() {
|
||||
this.dataSource = new AuditTrailsDataSource(this.systemService);
|
||||
this.dataSource.getAuditTrails(this.filterAuditTrailsBy, this.sort.active, this.sort.direction, this.paginator.pageIndex, this.paginator.pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a page of audit trails.
|
||||
*/
|
||||
loadAuditTrailsPage() {
|
||||
if (!this.sort.direction) {
|
||||
delete this.sort.active;
|
||||
}
|
||||
this.dataSource.getAuditTrails(this.filterAuditTrailsBy, this.sort.active, this.sort.direction, this.paginator.pageIndex, this.paginator.pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters data in audit trails table based on passed value and poperty.
|
||||
* @param {string} filterValue Value to filter data.
|
||||
* @param {string} property Property to filter data by.
|
||||
*/
|
||||
applyFilter(filterValue: string, property: string) {
|
||||
this.paginator.pageIndex = 0;
|
||||
const findIndex = this.filterAuditTrailsBy.findIndex(filter => filter.type === property);
|
||||
this.filterAuditTrailsBy[findIndex].value = filterValue;
|
||||
this.loadAuditTrailsPage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays user name in form control input.
|
||||
* @param {any} user User data.
|
||||
* @returns {string} User name if valid otherwise undefined.
|
||||
*/
|
||||
displayUserName(user?: any): string | undefined {
|
||||
return user ? user.name : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays action name in form control input.
|
||||
* @param {any} action Action data.
|
||||
* @returns {string} Action name if valid otherwise undefined.
|
||||
*/
|
||||
displayActionName(action?: any): string | undefined {
|
||||
return action ? action : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays entity name in form control input.
|
||||
* @param {any} entity Entity data.
|
||||
* @returns {string} Entity name if valid otherwise undefined.
|
||||
*/
|
||||
displayEntityName(entity?: any): string | undefined {
|
||||
return entity ? entity : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets filtered users for autocomplete.
|
||||
*/
|
||||
setFilteredUsers() {
|
||||
this.filteredUserData = this.user.valueChanges
|
||||
.pipe(
|
||||
startWith(''),
|
||||
map((user: any) => typeof user === 'string' ? user : user.name),
|
||||
map((userName: string) => userName ? this.filterUserAutocompleteData(userName) : this.auditTrailSearchTemplateData.appUsers)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets filtered checkers for autocomplete.
|
||||
*/
|
||||
setFilteredCheckers() {
|
||||
this.filteredCheckerData = this.checker.valueChanges
|
||||
.pipe(
|
||||
startWith(''),
|
||||
map((user: any) => typeof user === 'string' ? user : user.name),
|
||||
map((userName: string) => userName ? this.filterUserAutocompleteData(userName) : this.auditTrailSearchTemplateData.appUsers)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets filtered actions for autocomplete.
|
||||
*/
|
||||
setFilteredActions() {
|
||||
this.filteredActionData = this.actionName.valueChanges
|
||||
.pipe(
|
||||
startWith(''),
|
||||
map((action: any) => typeof action === 'string' ? action : ''),
|
||||
map((actionName: string) => actionName ? this.filterActionAutocompleteData(actionName) : this.auditTrailSearchTemplateData.actionNames)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets filtered entities for autocomplete.
|
||||
*/
|
||||
setFilteredEntities() {
|
||||
this.filteredEntityData = this.entityName.valueChanges
|
||||
.pipe(
|
||||
startWith(''),
|
||||
map((entity: any) => typeof entity === 'string' ? entity : ''),
|
||||
map((entityName: string) => entityName ? this.filterEntityAutocompleteData(entityName) : this.auditTrailSearchTemplateData.entityNames)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters users.
|
||||
* @param {string} userName User name to filter user by.
|
||||
* @returns {any} Filtered users.
|
||||
*/
|
||||
private filterUserAutocompleteData(userName: string): any {
|
||||
return this.auditTrailSearchTemplateData.appUsers.filter((user: any) => user.username.toLowerCase().includes(userName.toLowerCase()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters actions.
|
||||
* @param {string} actionName Action name to filter action by.
|
||||
* @returns {any} Filtered actions.
|
||||
*/
|
||||
private filterActionAutocompleteData(actionName: string): any {
|
||||
return this.auditTrailSearchTemplateData.actionNames.filter((action: any) => action.toLowerCase().includes(actionName.toLowerCase()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters entities.
|
||||
* @param {string} entityName Entity name to filter action by.
|
||||
* @returns {any} Filtered entities.
|
||||
*/
|
||||
private filterEntityAutocompleteData(entityName: string): any {
|
||||
return this.auditTrailSearchTemplateData.entityNames.filter((entity: any) => entity.toLowerCase().includes(entityName.toLowerCase()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the CSV file of Audit Trails Data.
|
||||
*/
|
||||
downloadCSV() {
|
||||
const dateFormat = 'yyyy-MM-dd';
|
||||
const replacer = (key: any, value: any) => value === undefined ? '' : value;
|
||||
const header = ['ID', 'Resource ID', 'Status', 'Office', 'Made On', 'Maker', 'Checked On', 'Checker', 'Entity', 'Action', 'Client'];
|
||||
const headerCode = ['id', 'resourceId', 'processingResult', 'officeName', 'madeOnDate', 'maker', 'checkedOnDate', 'checker', 'entityName', 'actionName', 'clientName'];
|
||||
this.systemService.getAuditTrails(this.filterAuditTrailsBy, this.sort.active ? this.sort.active : '', this.sort.direction, 0, -1).subscribe((response: any) => {
|
||||
if (response !== undefined) {
|
||||
let csv = response.pageItems.map((row: any) => headerCode.map(fieldName => (fieldName === 'madeOnDate' || fieldName === 'checkedOnDate') && (JSON.stringify(row[fieldName], replacer) !== '""')
|
||||
? this.datePipe.transform(JSON.stringify(row[fieldName], replacer), dateFormat)
|
||||
: JSON.stringify(row[fieldName], replacer)));
|
||||
csv.unshift(`data:text/csv;charset=utf-8,${header.join()}`);
|
||||
csv = csv.join('\r\n');
|
||||
const link = document.createElement('a');
|
||||
link.setAttribute('href', encodeURI(csv));
|
||||
link.setAttribute('download', 'Audit Trails.csv');
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the date from the passed timestamp.
|
||||
*
|
||||
* TODO: Update once language and date settings are setup.
|
||||
*
|
||||
* @param {any} timestamp Timestamp from which date is to be extracted.
|
||||
*/
|
||||
private getDate(timestamp: any) {
|
||||
const dateFormat = 'yyyy-MM-dd';
|
||||
return this.datePipe.transform(timestamp, dateFormat);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/** Angular Imports */
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Resolve, ActivatedRouteSnapshot } from '@angular/router';
|
||||
|
||||
/** rxjs Imports */
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
/** Custom Services */
|
||||
import { SystemService } from '../../system.service';
|
||||
|
||||
/**
|
||||
* Audit Trail data resolver.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AuditTrailResolver implements Resolve<Object> {
|
||||
|
||||
/**
|
||||
* @param {SystemService} systemService System service.
|
||||
*/
|
||||
constructor(private systemService: SystemService) {}
|
||||
|
||||
/**
|
||||
* Returns the Audit Trail data.
|
||||
* @returns {Observable<any>}
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot): Observable<any> {
|
||||
const auditTrailId = route.paramMap.get('id');
|
||||
return this.systemService.getAuditTrail(auditTrailId);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<div class="container" fxLayout="row" fxLayoutAlign="space-between" fxLayoutGap.lt-md="4%" fxLayout.lt-md="column">
|
||||
|
||||
<div fxFlex="46%">
|
||||
|
||||
<mat-card>
|
||||
|
||||
<mat-card-content>
|
||||
|
||||
<div fxLayout="row wrap" class="content">
|
||||
|
||||
<div fxFlex="50%" class="mat-body-strong">
|
||||
ID
|
||||
</div>
|
||||
|
||||
<div fxFlex="50%">
|
||||
{{ auditTrailData.id }}
|
||||
</div>
|
||||
|
||||
<div fxFlex="50%" class="mat-body-strong">
|
||||
Status
|
||||
</div>
|
||||
|
||||
<div fxFlex="50%">
|
||||
{{ auditTrailData.processingResult }}
|
||||
</div>
|
||||
|
||||
<div fxFlex="50%" class="mat-body-strong">
|
||||
User
|
||||
</div>
|
||||
|
||||
<div fxFlex="50%">
|
||||
{{ auditTrailData.maker }}
|
||||
</div>
|
||||
|
||||
<div fxFlex="50%" class="mat-body-strong">
|
||||
Action
|
||||
</div>
|
||||
|
||||
<div fxFlex="50%">
|
||||
{{ auditTrailData.actionName }}
|
||||
</div>
|
||||
|
||||
<div fxFlex="50%" class="mat-body-strong">
|
||||
Entity
|
||||
</div>
|
||||
|
||||
<div fxFlex="50%">
|
||||
{{ auditTrailData.entityName }}
|
||||
</div>
|
||||
|
||||
<div fxFlex="50%" class="mat-body-strong">
|
||||
Resource ID
|
||||
</div>
|
||||
|
||||
<div fxFlex="50%">
|
||||
{{ auditTrailData.resourceId }}
|
||||
</div>
|
||||
|
||||
<div fxFlex="50%" class="mat-body-strong">
|
||||
Date
|
||||
</div>
|
||||
|
||||
<div fxFlex="50%">
|
||||
{{ auditTrailData.madeOnDate | date }}
|
||||
</div>
|
||||
|
||||
<div fxFlex="50%" class="mat-body-strong" *ngIf="auditTrailData.officeName">
|
||||
Office
|
||||
</div>
|
||||
|
||||
<div fxFlex="50%" *ngIf="auditTrailData.officeName">
|
||||
{{ auditTrailData.officeName }}
|
||||
</div>
|
||||
|
||||
<div fxFlex="50%" class="mat-body-strong" *ngIf="auditTrailData.savingsAccountNo">
|
||||
Savings Account No
|
||||
</div>
|
||||
|
||||
<div fxFlex="50%" *ngIf="auditTrailData.savingsAccountNo">
|
||||
{{ auditTrailData.savingsAccountNo }}
|
||||
</div>
|
||||
|
||||
<div fxFlex="50%" class="mat-body-strong" *ngIf="auditTrailData.groupLevelName">
|
||||
Group Level
|
||||
</div>
|
||||
|
||||
<div fxFlex="50%" *ngIf="auditTrailData.groupLevelName">
|
||||
{{ auditTrailData.groupLevelName }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</mat-card-content>
|
||||
|
||||
</mat-card>
|
||||
|
||||
</div>
|
||||
|
||||
<div fxFlex="46%">
|
||||
|
||||
<table mat-table [dataSource]="dataSource" matSort>
|
||||
|
||||
<ng-container matColumnDef="command">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Command </th>
|
||||
<td mat-cell *matCellDef="let auditTrailCommand"> {{ auditTrailCommand.command }} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="commandValue">
|
||||
<th mat-header-cell *matHeaderCellDef> Command Value </th>
|
||||
<td mat-cell *matCellDef="let auditTrailCommand" class="column-value-wrapper"> {{ auditTrailCommand.commandValue }} </td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,14 @@
|
||||
.content {
|
||||
div {
|
||||
margin: 1rem 0;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.column-value-wrapper {
|
||||
word-break: break-all;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ViewAuditComponent } from './view-audit.component';
|
||||
|
||||
describe('ViewAuditComponent', () => {
|
||||
let component: ViewAuditComponent;
|
||||
let fixture: ComponentFixture<ViewAuditComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ViewAuditComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ViewAuditComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
/** Angular Imports */
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { MatTableDataSource, MatPaginator, MatSort } from '@angular/material';
|
||||
|
||||
/**
|
||||
* View Audit Component.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-view-audit',
|
||||
templateUrl: './view-audit.component.html',
|
||||
styleUrls: ['./view-audit.component.scss']
|
||||
})
|
||||
export class ViewAuditComponent implements OnInit {
|
||||
|
||||
/** Audit Trail Data. */
|
||||
auditTrailData: any;
|
||||
/** Columns to be displayed in audit trail table. */
|
||||
displayedColumns: string[] = ['command', 'commandValue'];
|
||||
/** Data source for audit trail table. */
|
||||
dataSource: MatTableDataSource<any>;
|
||||
|
||||
/** Paginator for audit trails table. */
|
||||
@ViewChild(MatPaginator) paginator: MatPaginator;
|
||||
/** Sorter for audit trails table. */
|
||||
@ViewChild(MatSort) sort: MatSort;
|
||||
|
||||
/**
|
||||
* Retrieves the audit trail data from `resolve`.
|
||||
* @param {ActivatedRoute} route Activated Route.
|
||||
*/
|
||||
constructor(private route: ActivatedRoute) {
|
||||
this.route.data.subscribe((data: { auditTrail: any }) => {
|
||||
this.auditTrailData = data.auditTrail;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the audit trail commands table.
|
||||
*/
|
||||
ngOnInit() {
|
||||
this.setAuditTrailCommands();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initalizes Audit Trail Commands Data.
|
||||
*/
|
||||
get auditTrailCommandsData() {
|
||||
return Object.entries(JSON.parse(this.auditTrailData.commandAsJson))
|
||||
.map(
|
||||
([key, value]) => ({ command: key, commandValue: value })
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the data source, paginator and sorter for audit trail commands table.
|
||||
*/
|
||||
setAuditTrailCommands() {
|
||||
this.dataSource = new MatTableDataSource(this.auditTrailCommandsData);
|
||||
this.dataSource.paginator = this.paginator;
|
||||
this.dataSource.sort = this.sort;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -42,6 +42,8 @@ import { ManageReportsComponent } from './manage-reports/manage-reports.componen
|
||||
import { EditReportComponent } from './manage-reports/edit-report/edit-report.component';
|
||||
import { CreateReportComponent } from './manage-reports/create-report/create-report.component';
|
||||
import { ViewReportComponent } from './manage-reports/view-report/view-report.component';
|
||||
import { AuditTrailsComponent } from './audit-trails/audit-trails.component';
|
||||
import { ViewAuditComponent } from './audit-trails/view-audit/view-audit.component';
|
||||
|
||||
/** Custom Resolvers */
|
||||
import { CodesResolver } from './codes/codes.resolver';
|
||||
@@ -64,6 +66,8 @@ import { NotificationConfigurationResolver } from './external-services/notificat
|
||||
import { AccountNumberPreferencesResolver } from './account-number-preferences/account-number-preferences.resolver';
|
||||
import { AccountNumberPreferencesTemplateResolver } from './account-number-preferences/create-account-number-preference/account-number-preferences-template.resolver';
|
||||
import { AccountNumberPreferenceResolver } from './account-number-preferences/view-account-number-preference/account-number-preference.resolver';
|
||||
import { AuditTrailSearchTemplateResolver } from './audit-trails/audit-trail-search-template.resolver';
|
||||
import { AuditTrailResolver } from './audit-trails/view-audit/audit-trail.resolver';
|
||||
import { ReportsResolver } from './manage-reports/reports.resolver';
|
||||
import { ReportResolver } from './manage-reports/report.resolver';
|
||||
import { ReportTemplateResolver } from './manage-reports/report-template.resolver';
|
||||
@@ -439,6 +443,27 @@ const routes: Routes = [
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'audit-trails',
|
||||
data: { title: extract('Audit Trails'), breadcrumb: 'Audit Trails' },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: AuditTrailsComponent,
|
||||
resolve: {
|
||||
auditTrailSearchTemplate: AuditTrailSearchTemplateResolver
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
component: ViewAuditComponent,
|
||||
data: { title: extract('View Audit'), routeParamBreadcrumb: 'id' },
|
||||
resolve: {
|
||||
auditTrail: AuditTrailResolver
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -471,7 +496,9 @@ const routes: Routes = [
|
||||
AccountNumberPreferenceResolver,
|
||||
ReportsResolver,
|
||||
ReportResolver,
|
||||
ReportTemplateResolver
|
||||
ReportTemplateResolver,
|
||||
AuditTrailSearchTemplateResolver,
|
||||
AuditTrailResolver
|
||||
]
|
||||
})
|
||||
export class SystemRoutingModule { }
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
|
||||
<mat-nav-list>
|
||||
|
||||
<mat-list-item>
|
||||
<mat-list-item [routerLink]="['audit-trails']">
|
||||
<mat-icon matListIcon>
|
||||
<fa-icon icon="money-check" size="sm"></fa-icon>
|
||||
</mat-icon>
|
||||
|
||||
@@ -39,6 +39,8 @@ import { ManageReportsComponent } from './manage-reports/manage-reports.componen
|
||||
import { ViewReportComponent } from './manage-reports/view-report/view-report.component';
|
||||
import { CreateReportComponent } from './manage-reports/create-report/create-report.component';
|
||||
import { EditReportComponent } from './manage-reports/edit-report/edit-report.component';
|
||||
import { AuditTrailsComponent } from './audit-trails/audit-trails.component';
|
||||
import { ViewAuditComponent } from './audit-trails/view-audit/view-audit.component';
|
||||
import { EditHookComponent } from './manage-hooks/edit-hook/edit-hook.component';
|
||||
import { ViewHookComponent } from './manage-hooks/view-hook/view-hook.component';
|
||||
import { CreateHookComponent } from './manage-hooks/create-hook/create-hook.component';
|
||||
@@ -92,7 +94,9 @@ import { AddEventDialogComponent } from './manage-hooks/add-event-dialog/add-eve
|
||||
ViewHookComponent,
|
||||
CreateHookComponent,
|
||||
AddEventDialogComponent,
|
||||
ColumnDialogComponent
|
||||
ColumnDialogComponent,
|
||||
AuditTrailsComponent,
|
||||
ViewAuditComponent
|
||||
],
|
||||
entryComponents: [
|
||||
ReportParameterDialogComponent,
|
||||
|
||||
@@ -352,4 +352,43 @@ export class SystemService {
|
||||
return this.http.delete(`/reports/${reportId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} filterBy Properties by which entries should be filtered.
|
||||
* @param {string} orderBy Property by which entries should be sorted.
|
||||
* @param {string} sortOrder Sort order: ascending or descending.
|
||||
* @param {number} offset Page offset.
|
||||
* @param {number} limit Number of entries within the page.
|
||||
* @returns {Observable<any>} Audit Trails.
|
||||
*/
|
||||
getAuditTrails(filterBy: any, orderBy: string, sortOrder: string, offset: number, limit: number): Observable<any> {
|
||||
let httpParams = new HttpParams()
|
||||
.set('offset', offset.toString())
|
||||
.set('limit', limit.toString())
|
||||
.set('sortOrder', sortOrder)
|
||||
.set('orderBy', orderBy)
|
||||
.set('paged', 'true');
|
||||
// filterBy: actionName, entityName, resourceId, makerId, makerDateTimeFrom, makerDateTimeTo, checkerId, checkerDateTimeFrom, checkerDateTimeTo, processingResult
|
||||
filterBy.forEach(function (filter: any) {
|
||||
if (filter.value !== '') {
|
||||
httpParams = httpParams.set(filter.type, filter.value);
|
||||
}
|
||||
});
|
||||
return this.http.get('/audits', { params: httpParams });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} auditTrailId Audit Trail ID.
|
||||
* @returns {Observable<any>}
|
||||
*/
|
||||
getAuditTrail(auditTrailId: string): Observable<any> {
|
||||
return this.http.get(`/audits/${auditTrailId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Observable<any>} Audit Trail Search Template.
|
||||
*/
|
||||
getAuditTrailSearchTemplate(): Observable<any> {
|
||||
return this.http.get('/audits/searchtemplate');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user