@@ -0,0 +1,16 @@
|
||||
/** Chart Data Model */
|
||||
export class ChartData {
|
||||
|
||||
keysLabel: string;
|
||||
valuesLabel: string;
|
||||
keys: string[];
|
||||
values: number[];
|
||||
|
||||
constructor(response: any) {
|
||||
this.keysLabel = response.columnHeaders[0].columnName;
|
||||
this.valuesLabel = response.columnHeaders[1].columnName;
|
||||
this.keys = response.data.map((object: any) => object.row[0]);
|
||||
this.values = response.data.map((object: any) => object.row[1]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/** Report Parameter Model */
|
||||
export class ReportParameter {
|
||||
|
||||
name: string;
|
||||
variable: string;
|
||||
label: string;
|
||||
displayType: string;
|
||||
formatType: string;
|
||||
defaultVal: any;
|
||||
selectOne: any;
|
||||
selectAll: any;
|
||||
parentParameterName: string;
|
||||
inputName: string;
|
||||
selectOptions: any[] = [];
|
||||
childParameter: any;
|
||||
pentahoName: any;
|
||||
|
||||
constructor(options: any[]) {
|
||||
this.name = options[0];
|
||||
this.variable = options[1];
|
||||
this.label = options[2];
|
||||
this.displayType = options[3];
|
||||
this.formatType = options[4];
|
||||
this.defaultVal = options[5];
|
||||
this.selectOne = options[6];
|
||||
this.selectAll = options[7];
|
||||
this.parentParameterName = options[8];
|
||||
this.inputName = `R_${options[1]}`;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/** Select Option model */
|
||||
export class SelectOption {
|
||||
|
||||
id: number;
|
||||
name: string;
|
||||
|
||||
constructor(options: any[]) {
|
||||
this.id = options[0],
|
||||
this.name = options[1];
|
||||
}
|
||||
}
|
||||
@@ -10,9 +10,11 @@ import { extract } from '../core/i18n/i18n.service';
|
||||
|
||||
/** Custom Components */
|
||||
import { ReportsComponent } from './reports.component';
|
||||
import { RunReportComponent } from './run-report/run-report.component';
|
||||
|
||||
/** Custom Resolvers */
|
||||
import { ReportsResolver } from './reports.resolver';
|
||||
import { RunReportResolver } from './run-report/run-report.resolver';
|
||||
|
||||
/** Reports Routes */
|
||||
const routes: Routes = [
|
||||
@@ -34,6 +36,29 @@ const routes: Routes = [
|
||||
component: ReportsComponent,
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'run-report',
|
||||
data: { title: extract('Reports'), breadcrumb: 'Reports' },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: '/reports', pathMatch: 'full'
|
||||
},
|
||||
{
|
||||
path: ':report-name/:report-type/:report-id',
|
||||
data: { title: extract('Run Report'), routeParamBreadcrumb: 'report-name' },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: RunReportComponent,
|
||||
resolve: {
|
||||
params: RunReportResolver
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
])
|
||||
];
|
||||
@@ -47,7 +72,8 @@ const routes: Routes = [
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
providers: [
|
||||
ReportsResolver
|
||||
ReportsResolver,
|
||||
RunReportResolver
|
||||
]
|
||||
})
|
||||
export class ReportsRoutingModule { }
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;" class="select-row"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;" class="select-row" [routerLink]="['/run-report', row.reportName, row.reportType, row.id]"></tr>
|
||||
|
||||
</table>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/** Angular Imports */
|
||||
import { NgModule } from '@angular/core';
|
||||
import { DecimalPipe } from '@angular/common';
|
||||
|
||||
/** Custom Modules */
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
@@ -7,6 +8,10 @@ import { ReportsRoutingModule } from 'app/reports/reports-routing.module';
|
||||
|
||||
/** Custom Components */
|
||||
import { ReportsComponent } from './reports.component';
|
||||
import { RunReportComponent } from './run-report/run-report.component';
|
||||
import { TableAndSmsComponent } from './run-report/table-and-sms/table-and-sms.component';
|
||||
import { ChartComponent } from './run-report/chart/chart.component';
|
||||
import { PentahoComponent } from './run-report/pentaho/pentaho.component';
|
||||
|
||||
/**
|
||||
* Reports Module
|
||||
@@ -18,6 +23,13 @@ import { ReportsComponent } from './reports.component';
|
||||
SharedModule,
|
||||
ReportsRoutingModule
|
||||
],
|
||||
declarations: [ReportsComponent],
|
||||
declarations: [
|
||||
ReportsComponent,
|
||||
RunReportComponent,
|
||||
TableAndSmsComponent,
|
||||
ChartComponent,
|
||||
PentahoComponent
|
||||
],
|
||||
providers: [ DecimalPipe ]
|
||||
})
|
||||
export class ReportsModule { }
|
||||
|
||||
@@ -4,6 +4,12 @@ import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
|
||||
/** rxjs Imports */
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
/** Custom Models */
|
||||
import { ReportParameter } from './common-models/report-parameter.model';
|
||||
import { SelectOption } from './common-models/select-option.model';
|
||||
import { ChartData } from './common-models/chart-data.model';
|
||||
|
||||
/**
|
||||
* Reports service.
|
||||
@@ -25,4 +31,82 @@ export class ReportsService {
|
||||
return this.http.get('/reports');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} reportName Report name for which parameters are needed.
|
||||
* @returns {Observable<ReportParameter[]>}
|
||||
*/
|
||||
getReportParams(reportName: string): Observable<ReportParameter[]> {
|
||||
const httpParams = new HttpParams()
|
||||
.set('R_reportListing', `'${reportName}'`)
|
||||
.set('parameterType', 'true');
|
||||
return this.http.get(`/runreports/FullParameterList`, {params: httpParams})
|
||||
.pipe(map((response: any) => response.data.map((entry: any) => new ReportParameter(entry.row)) ));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} inputString URL substring containing object details.
|
||||
* @returns {Observable<SelectOption[]>}
|
||||
*/
|
||||
getSelectOptions(inputString: string): Observable<SelectOption[]> {
|
||||
const httpParams = new HttpParams().set('parameterType', 'true');
|
||||
return this.http.get(`/runreports/${inputString}`, {params: httpParams})
|
||||
.pipe(map((response: any) => response.data.map((entry: any) => new SelectOption(entry.row)) ));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} reportId Report id for which pentaho parameters are needed.
|
||||
* @returns {Observable<any>}
|
||||
*/
|
||||
getPentahoParams(reportId: number): Observable<any> {
|
||||
const httpParams = new HttpParams().set('fields', 'reportParameters');
|
||||
return this.http.get(`/reports/${reportId}`, {params: httpParams})
|
||||
.pipe(map((response: any) => response.reportParameters));
|
||||
}
|
||||
|
||||
/**
|
||||
* Run Report Data for Table and SMS.
|
||||
* @param {any} report report object containing 'id', 'name' & 'type' attributes.
|
||||
* @param {object} formData Form Data.
|
||||
* @returns {Observable<any>}
|
||||
*/
|
||||
getRunReportData(report: any, formData: object): Observable<any> {
|
||||
let httpParams = new HttpParams();
|
||||
for (const [key, value] of Object.entries(formData)) {
|
||||
httpParams = httpParams.set(key, value);
|
||||
}
|
||||
return this.http.get(`/runreports/${report.name}`, {params: httpParams});
|
||||
}
|
||||
|
||||
/**
|
||||
* Run Report Data for Charts.
|
||||
* @param {any} report report object containing 'id', 'name' & 'type' attributes.
|
||||
* @param {object} formData Form Data.
|
||||
* @returns {Observable<ChartData>}
|
||||
*/
|
||||
getChartRunReportData(report: any, formData: object): Observable<ChartData> {
|
||||
let httpParams = new HttpParams();
|
||||
for (const [key, value] of Object.entries(formData)) {
|
||||
httpParams = httpParams.set(key, value);
|
||||
}
|
||||
return this.http.get(`/runreports/${report.name}`, {params: httpParams})
|
||||
.pipe(map((response: any) => new ChartData(response)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Run Report Data for Pentaho.
|
||||
* @param {any} report report object containing 'id', 'name' & 'type' attributes.
|
||||
* @param {object} formData Form Data.
|
||||
* @returns {Observable<any>}
|
||||
*/
|
||||
getPentahoRunReportData(report: any, formData: object, tenantIdentifier: string, locale: string, dateFormat: string): Observable<any> {
|
||||
let httpParams = new HttpParams()
|
||||
.set('tenantIdentifier', tenantIdentifier)
|
||||
.set('locale', locale)
|
||||
.set('dateFormat', dateFormat);
|
||||
for (const [key, value] of Object.entries(formData)) {
|
||||
httpParams = httpParams.set(key, value);
|
||||
}
|
||||
return this.http.get(`/runreports/${report.name}`, {responseType: 'arraybuffer', observe: 'response', params: httpParams});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<div class="m-b-20" fxLayoutAlign="end">
|
||||
<mat-button-toggle-group aria-label="Select Chart Type">
|
||||
<mat-button-toggle value="Bar" (click) = "setBarChart(inputData)">Bar Chart</mat-button-toggle>
|
||||
<mat-button-toggle value="Pie" (click) = "setPieChart(inputData)">Pie Chart</mat-button-toggle>
|
||||
</mat-button-toggle-group>
|
||||
</div>
|
||||
|
||||
<div [ngStyle]="{'display': (hideOutput) ? 'none' : 'block'}">
|
||||
<canvas id="output"></canvas>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ChartComponent } from './chart.component';
|
||||
|
||||
describe('ChartComponent', () => {
|
||||
let component: ChartComponent;
|
||||
let fixture: ComponentFixture<ChartComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ChartComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ChartComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,139 @@
|
||||
/** Angular Imports */
|
||||
import { Component, OnChanges, Input } from '@angular/core';
|
||||
|
||||
/** Custom Services */
|
||||
import { ReportsService } from '../../reports.service';
|
||||
|
||||
/** Custom Models */
|
||||
import { ChartData } from '../../common-models/chart-data.model';
|
||||
|
||||
/** Charting Imports */
|
||||
import Chart from 'chart.js';
|
||||
|
||||
/**
|
||||
* Chart Component
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-chart',
|
||||
templateUrl: './chart.component.html',
|
||||
styleUrls: ['./chart.component.scss' ]
|
||||
})
|
||||
export class ChartComponent implements OnChanges {
|
||||
|
||||
/** Run Report Data */
|
||||
@Input() dataObject: any;
|
||||
|
||||
/** chart data object */
|
||||
chart: any;
|
||||
/** substitute for resolver */
|
||||
hideOutput = true;
|
||||
/** Data object for witching charts in view. */
|
||||
inputData: ChartData;
|
||||
|
||||
/**
|
||||
* @param {ReportsService} reportsService Reports Service
|
||||
*/
|
||||
constructor(private reportsService: ReportsService) { }
|
||||
|
||||
/**
|
||||
* Fetches run report data post changes in run report form.
|
||||
*/
|
||||
ngOnChanges() {
|
||||
this.getRunReportData();
|
||||
}
|
||||
|
||||
getRunReportData() {
|
||||
this.reportsService.getChartRunReportData(this.dataObject.reportData, this.dataObject.formData)
|
||||
.subscribe((response: ChartData) => {
|
||||
this.inputData = response;
|
||||
this.setPieChart(this.inputData);
|
||||
this.hideOutput = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates instance of chart.js pie chart.
|
||||
* Refer: https://www.chartjs.org/docs/latest/charts/doughnut.html for configuration details.
|
||||
*/
|
||||
setPieChart(inputData: ChartData) {
|
||||
if (this.chart) {
|
||||
this.chart.destroy();
|
||||
}
|
||||
this.chart = new Chart('output', {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: inputData.keys,
|
||||
datasets: [{
|
||||
label: inputData.valuesLabel,
|
||||
data: inputData.values,
|
||||
backgroundColor: this.randomColorArray(inputData.values.length)
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
title: {
|
||||
display: true,
|
||||
text: inputData.keysLabel
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates instance of chart.js bar chart.
|
||||
* Refer: https://www.chartjs.org/docs/latest/charts/bar.html for configuration details.
|
||||
*/
|
||||
setBarChart(inputData: ChartData) {
|
||||
if (this.chart) {
|
||||
this.chart.destroy();
|
||||
}
|
||||
this.chart = new Chart('output', {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: inputData.keys,
|
||||
datasets: [{
|
||||
label: inputData.valuesLabel,
|
||||
data: inputData.values,
|
||||
backgroundColor: this.randomColorArray(inputData.values.length)
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
legend: { display: false },
|
||||
scales: {
|
||||
xAxes: [{
|
||||
scaleLabel: {
|
||||
display: true,
|
||||
labelString: inputData.keysLabel
|
||||
},
|
||||
ticks: {
|
||||
beginAtZero: true
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates bar/pie-slice colors array for dynamic charts.
|
||||
* @param {number} length Length of dataset array.
|
||||
*/
|
||||
randomColorArray(length: number) {
|
||||
const colorArray: any[] = [];
|
||||
while (length--) {
|
||||
const color = this.randomColor();
|
||||
colorArray.push(color);
|
||||
}
|
||||
return colorArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a random rgb color.
|
||||
*/
|
||||
randomColor() {
|
||||
const r = Math.floor(Math.random() * 255);
|
||||
const g = Math.floor(Math.random() * 255);
|
||||
const b = Math.floor(Math.random() * 255);
|
||||
return `rgb(${r},${g},${b},0.6)`;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<iframe *ngIf = "!hideOutput" [src]="pentahoUrl" frameborder="0" width=100% height="750px;"></iframe>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { PentahoComponent } from './pentaho.component';
|
||||
|
||||
describe('PentahoComponent', () => {
|
||||
let component: PentahoComponent;
|
||||
let fixture: ComponentFixture<PentahoComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ PentahoComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(PentahoComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
/** Angular Imports */
|
||||
import { Component, OnChanges, Input } from '@angular/core';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
|
||||
/** Custom Services */
|
||||
import { ReportsService } from '../../reports.service';
|
||||
|
||||
/**
|
||||
* Pentaho Component
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-pentaho',
|
||||
templateUrl: './pentaho.component.html',
|
||||
styleUrls: ['./pentaho.component.scss']
|
||||
})
|
||||
export class PentahoComponent implements OnChanges {
|
||||
|
||||
/** Run Report Data */
|
||||
@Input() dataObject: any;
|
||||
|
||||
/** substitute for resolver */
|
||||
hideOutput = true;
|
||||
/** trusted resource url for pentaho output */
|
||||
pentahoUrl: any;
|
||||
|
||||
/**
|
||||
* @param {DomSanitizer} sanitizer DOM Sanitizer
|
||||
* @param {ReportsService} reportsService Reports Service
|
||||
*/
|
||||
constructor(private sanitizer: DomSanitizer,
|
||||
private reportsService: ReportsService) { }
|
||||
|
||||
/**
|
||||
* Fetches run report data post changes in run report form.
|
||||
*/
|
||||
ngOnChanges() {
|
||||
this.hideOutput = true;
|
||||
this.getRunReportData();
|
||||
}
|
||||
|
||||
getRunReportData() {
|
||||
this.reportsService.getPentahoRunReportData(this.dataObject.reportData, this.dataObject.formData, 'default', 'en', 'dd MMMM yyyy')
|
||||
.subscribe( (res: any) => {
|
||||
const contentType = res.headers.get('Content-Type');
|
||||
const file = new Blob([res.body], {type: contentType});
|
||||
const filecontent = URL.createObjectURL(file);
|
||||
this.pentahoUrl = this.sanitizer.bypassSecurityTrustResourceUrl(filecontent);
|
||||
this.hideOutput = false;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<div class="container input">
|
||||
|
||||
<mat-card *ngIf="!isCollapsed">
|
||||
|
||||
<form [formGroup]="ReportForm" (ngSubmit)="run()">
|
||||
|
||||
<mat-card-content>
|
||||
|
||||
<div fxLayout="column" *ngFor="let param of paramData">
|
||||
|
||||
<div [ngSwitch]="param.displayType">
|
||||
|
||||
<div *ngIf="ReportForm.contains(param.name)">
|
||||
<mat-form-field fxFlex="100%" *ngSwitchCase="'text'">
|
||||
<mat-label>{{param.label}}</mat-label>
|
||||
<input matInput required [formControlName]="param.name">
|
||||
<mat-error *ngIf="ReportForm.controls[param.name].hasError('required')">
|
||||
{{param.label}} is <strong>required</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div *ngIf="ReportForm.contains(param.name)">
|
||||
<mat-form-field fxFlex="100%" *ngSwitchCase="'date'">
|
||||
<mat-label>{{param.label}}</mat-label>
|
||||
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="runReportDatePicker" required [formControlName]="param.name">
|
||||
<mat-datepicker-toggle matSuffix [for]="runReportDatePicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #runReportDatePicker></mat-datepicker>
|
||||
<mat-error *ngIf="ReportForm.controls[param.name].hasError('required')">
|
||||
{{param.label}} is <strong>required</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div *ngIf="ReportForm.contains(param.name) ">
|
||||
<mat-form-field fxFlex="100%" *ngSwitchCase="'select'" >
|
||||
<mat-label>{{param.label}}</mat-label>
|
||||
<mat-select required [formControlName]="param.name">
|
||||
<mat-option *ngFor="let option of param.selectOptions" [value]="option" >
|
||||
{{option.name}}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-error *ngIf="ReportForm.controls[param.name].hasError('required')">
|
||||
{{param.label}} is <strong>required</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<mat-form-field fxFlex="100%">
|
||||
<mat-label>Decimal Places</mat-label>
|
||||
<mat-select [formControl]="decimalChoice">
|
||||
<mat-option value="0">0</mat-option>
|
||||
<mat-option value="1">1</mat-option>
|
||||
<mat-option value="2">2</mat-option>
|
||||
<mat-option value="3">3</mat-option>
|
||||
<mat-option value="4">4</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div *ngIf="report.type === 'Pentaho' && ReportForm.contains('outputType')">
|
||||
<mat-form-field fxFlex="100%">
|
||||
<mat-label>Output Type</mat-label>
|
||||
<mat-select formControlName="outputType" value="HTML">
|
||||
<mat-option value="HTML">Normal format</mat-option>
|
||||
<mat-option value="XLS">Excel format</mat-option>
|
||||
<mat-option value="XLSX">Excel 2007 format</mat-option>
|
||||
<mat-option value="CSV">CSV format</mat-option>
|
||||
<mat-option value="PDF">PDF format</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
</mat-card-content>
|
||||
|
||||
<mat-card-actions fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center" fxLayoutGap="5px">
|
||||
<button type="button" mat-raised-button [routerLink]="['/reports']">Cancel</button>
|
||||
<button mat-raised-button color="primary" [disabled]="!ReportForm.valid">
|
||||
<fa-icon icon="cogs"></fa-icon>
|
||||
Run Report
|
||||
</button>
|
||||
</mat-card-actions>
|
||||
|
||||
</form>
|
||||
|
||||
</mat-card>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="container output" *ngIf="isCollapsed">
|
||||
|
||||
<mat-card>
|
||||
|
||||
<div class="m-b-20">
|
||||
<button mat-raised-button color="primary" (click)="isCollapsed = false">
|
||||
Parameters
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<mifosx-table-and-sms *ngIf="!hideTable"[dataObject]="dataObject"></mifosx-table-and-sms>
|
||||
<mifosx-chart *ngIf="!hideChart" [dataObject]="dataObject"></mifosx-chart>
|
||||
<mifosx-pentaho *ngIf="!hidePentaho" [dataObject]="dataObject"></mifosx-pentaho>
|
||||
</div>
|
||||
|
||||
</mat-card>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
.input {
|
||||
max-width: 37rem;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { RunReportComponent } from './run-report.component';
|
||||
|
||||
describe('RunReportComponent', () => {
|
||||
let component: RunReportComponent;
|
||||
let fixture: ComponentFixture<RunReportComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ RunReportComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(RunReportComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,201 @@
|
||||
/** Angular Imports */
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { FormControl, FormGroup, Validators } from '@angular/forms';
|
||||
import { DatePipe } from '@angular/common';
|
||||
|
||||
/** Custom Services */
|
||||
import { ReportsService } from '../reports.service';
|
||||
|
||||
/** Custom Models */
|
||||
import { ReportParameter } from '../common-models/report-parameter.model';
|
||||
import { SelectOption } from '../common-models/select-option.model';
|
||||
|
||||
/**
|
||||
* Run report component.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-run-report',
|
||||
templateUrl: './run-report.component.html',
|
||||
styleUrls: ['./run-report.component.scss']
|
||||
})
|
||||
export class RunReportComponent implements OnInit {
|
||||
|
||||
/** Minimum date allowed. */
|
||||
minDate = new Date(2000, 0, 1);
|
||||
/** Maximum date allowed. */
|
||||
maxDate = new Date();
|
||||
/** Contains report specifications i.e: name, type and id */
|
||||
report: any = {};
|
||||
/** Formatted data post labeling of report parameters fetched from API */
|
||||
paramData: any = [];
|
||||
/** Array of all parent parameters */
|
||||
parentParameters: any[] = [];
|
||||
/** Parameter data to configure pentaho output */
|
||||
pentahoReportParameters: any[] = [];
|
||||
/** Data to be passed on to component selectors */
|
||||
dataObject: any;
|
||||
/** Initializes new form group 'ReportForm */
|
||||
ReportForm = new FormGroup({});
|
||||
/** Static Form control for decimal places in output */
|
||||
decimalChoice = new FormControl();
|
||||
/** Toggles Report form */
|
||||
isCollapsed = false;
|
||||
/** Toggles Table output. */
|
||||
hideTable = true;
|
||||
/** Toggles Chart output */
|
||||
hideChart = true;
|
||||
/** Toggles Pentaho output */
|
||||
hidePentaho = true;
|
||||
|
||||
/**
|
||||
* Fetches report specifications from route params and retrieves report parameters data from `resolve`.
|
||||
* @param {ActivatedRoute} route ActivatedRoute.
|
||||
* @param {DomSanitizer} sanitizer DomSanitizer.
|
||||
* @param {ReportsService} reportsService ReportsService
|
||||
* @param {DatePipe} datePipe Date Pipe
|
||||
*/
|
||||
constructor(private route: ActivatedRoute,
|
||||
private reportsService: ReportsService,
|
||||
private datePipe: DatePipe) {
|
||||
this.report.name = this.route.snapshot.params['report-name'];
|
||||
this.report.type = this.route.snapshot.params['report-type'];
|
||||
this.report.id = this.route.snapshot.params['report-id'];
|
||||
this.route.data.subscribe((data: any) => {
|
||||
this.paramData = data.params;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and sets the run report form.
|
||||
*/
|
||||
ngOnInit() {
|
||||
this.createRunReportForm();
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes form controls for Report Parameter's name attribute,
|
||||
* Fetches dropdown options and builds child dependencies.
|
||||
*/
|
||||
createRunReportForm() {
|
||||
this.paramData.forEach(
|
||||
(param: any) => {
|
||||
if (!param.parentParameterName) {
|
||||
this.ReportForm.addControl(param.name, new FormControl('', Validators.required));
|
||||
if (param.displayType === 'select') {
|
||||
this.fetchSelectOptions(param, param.name);
|
||||
}
|
||||
} else {
|
||||
const parent: ReportParameter = this.paramData
|
||||
.find((entry: any) => entry.name === param.parentParameterName);
|
||||
parent.childParameter = param;
|
||||
this.parentParameters.push(parent);
|
||||
}
|
||||
});
|
||||
if (this.report.type === 'Pentaho') {
|
||||
this.ReportForm.addControl('outputType', new FormControl(''));
|
||||
this.mapPentahoParams();
|
||||
}
|
||||
this.decimalChoice.patchValue('0');
|
||||
this.setChildControls();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches Select Dropdown options for param type "Select".
|
||||
* @param {ReportParameter} param Parameter for which dropdown options are required.
|
||||
* @param {string} inputstring url substring for API call.
|
||||
*/
|
||||
fetchSelectOptions(param: ReportParameter, inputstring: string) {
|
||||
this.reportsService.getSelectOptions(inputstring).subscribe((options: SelectOption[]) => {
|
||||
param.selectOptions = options;
|
||||
if (param.selectAll === 'Y') {
|
||||
param.selectOptions.push({id: '-1', name: 'All'});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps pentaho specific names to form-control names.
|
||||
*/
|
||||
mapPentahoParams() {
|
||||
this.reportsService.getPentahoParams(this.report.id).subscribe((data: any) => {
|
||||
data.forEach((entry: any) => {
|
||||
const param: ReportParameter = this.paramData
|
||||
.find((_entry: any) => _entry.name === entry.parameterName);
|
||||
param.pentahoName = `R_${entry.reportParameterName}`;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes to changes in parent parameters value, builds child parameter vis-a-vis parent's value.
|
||||
*/
|
||||
setChildControls() {
|
||||
this.parentParameters.forEach((param: ReportParameter) => {
|
||||
this.ReportForm.get(param.name).valueChanges.subscribe((option: any) => {
|
||||
this.ReportForm.addControl(param.childParameter.name, new FormControl('', Validators.required));
|
||||
if (param.childParameter.displayType === 'select') {
|
||||
const inputstring = `${param.childParameter.name}?${param.inputName}=${option.id}`;
|
||||
this.fetchSelectOptions(param.childParameter, inputstring);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats user response and readies it for utilization by run report function.
|
||||
* @param {any} response Object containing formcontrol values.
|
||||
*/
|
||||
formatUserResponse(response: any) {
|
||||
const formattedResponse: any = {};
|
||||
let newKey: string;
|
||||
for (const [key, value] of Object.entries(response)) {
|
||||
if (key === 'outputType') {
|
||||
formattedResponse['output-type'] = value;
|
||||
continue;
|
||||
}
|
||||
const param: ReportParameter = this.paramData
|
||||
.find((_entry: any) => _entry.name === key);
|
||||
newKey = this.report.type === 'Pentaho' ? param.pentahoName : param.inputName;
|
||||
switch (param.displayType) {
|
||||
case 'text':
|
||||
formattedResponse[newKey] = value;
|
||||
break;
|
||||
case 'select':
|
||||
formattedResponse[newKey] = value['id'];
|
||||
break;
|
||||
case 'date':
|
||||
const dateFormat = 'yyyy-MM-dd';
|
||||
formattedResponse[newKey] = this.datePipe.transform(value, dateFormat);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return formattedResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Core run report functionality.
|
||||
*/
|
||||
run() {
|
||||
this.isCollapsed = true;
|
||||
const userResponse = this.formatUserResponse(this.ReportForm.value);
|
||||
this.dataObject = {
|
||||
formData: userResponse,
|
||||
reportData: this.report,
|
||||
decimalChoice: this.decimalChoice.value
|
||||
};
|
||||
switch (this.report.type) {
|
||||
case 'SMS':
|
||||
case 'Table':
|
||||
this.hideTable = false;
|
||||
break;
|
||||
case 'Chart':
|
||||
this.hideChart = false;
|
||||
break;
|
||||
case 'Pentaho':
|
||||
this.hidePentaho = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/** Angular Imports */
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Resolve, ActivatedRouteSnapshot } from '@angular/router';
|
||||
|
||||
/** rxjs Imports */
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
/** Custom Services */
|
||||
import { ReportsService } from '../reports.service';
|
||||
|
||||
/**
|
||||
* Reports data resolver.
|
||||
*/
|
||||
@Injectable()
|
||||
export class RunReportResolver implements Resolve<Object> {
|
||||
|
||||
/**
|
||||
* @param {ReportsService} reportsService Reports service.
|
||||
*/
|
||||
constructor(private reportsService: ReportsService) {}
|
||||
|
||||
/**
|
||||
* Returns the reports data.
|
||||
* @returns {Observable<any>}
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot): Observable<any> {
|
||||
return this.reportsService.getReportParams(route.params['report-name']);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<div *ngIf = "!hideOutput">
|
||||
|
||||
<div class="m-b-10" *ngIf = "dataObject.reportData.type === 'Table'" fxLayoutAlign="end">
|
||||
<button mat-raised-button color="primary" (click)="downloadCSV()" >
|
||||
Export CSV
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="table-output">
|
||||
<table mat-table [dataSource]="dataSource">
|
||||
<div *ngFor= "let column of displayedColumns index as i">
|
||||
<ng-container [matColumnDef]="column">
|
||||
<th mat-header-cell *matHeaderCellDef>{{column}}</th>
|
||||
<td mat-cell *matCellDef="let param" >
|
||||
<span *ngIf="isDecimal(i)">{{toDecimal(param.row[i])}}</span>
|
||||
<span *ngIf="!isDecimal(i)">{{param.row[i]}}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
</div>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;" class="select-row" ></tr>
|
||||
</table>
|
||||
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,13 @@
|
||||
.table-output {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.select-row:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mat-header-cell, .mat-cell {
|
||||
min-width: 150px;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TableAndSmsComponent } from './table-and-sms.component';
|
||||
|
||||
describe('TableAndSmsComponent', () => {
|
||||
let component: TableAndSmsComponent;
|
||||
let fixture: ComponentFixture<TableAndSmsComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ TableAndSmsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TableAndSmsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,110 @@
|
||||
/** Angular Imports */
|
||||
import { Component, Input, ViewChild, OnChanges } from '@angular/core';
|
||||
import { MatPaginator, MatTableDataSource} from '@angular/material';
|
||||
import { DecimalPipe } from '@angular/common';
|
||||
|
||||
/** Custom Servies */
|
||||
import { ReportsService } from '../../reports.service';
|
||||
|
||||
/**
|
||||
* Table and SMS Component
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-table-and-sms',
|
||||
templateUrl: './table-and-sms.component.html',
|
||||
styleUrls: ['./table-and-sms.component.scss']
|
||||
})
|
||||
export class TableAndSmsComponent implements OnChanges {
|
||||
|
||||
/** Run Report Data */
|
||||
@Input() dataObject: any;
|
||||
|
||||
/** Columns to be displayed in mat-table */
|
||||
displayedColumns: string[] = [];
|
||||
/** Data source for run-report table. */
|
||||
dataSource = new MatTableDataSource();
|
||||
/** Maps column name to type */
|
||||
columnTypes: any[] = [];
|
||||
/** substitute for resolver */
|
||||
hideOutput = true;
|
||||
/** Data to be converted into CSV file */
|
||||
csvData: any;
|
||||
|
||||
/** Paginator for run-report table. */
|
||||
@ViewChild(MatPaginator) paginator: MatPaginator;
|
||||
|
||||
/**
|
||||
* @param {ReportsService} reportsService Reports Service
|
||||
* @param {DecimalPipe} decimalPipe Decimal Pipe
|
||||
*/
|
||||
constructor(private reportsService: ReportsService,
|
||||
private decimalPipe: DecimalPipe ) { }
|
||||
|
||||
/**
|
||||
* Fetches run report data post changes in run report form.
|
||||
*/
|
||||
ngOnChanges() {
|
||||
this.hideOutput = true;
|
||||
this.columnTypes = [];
|
||||
this.displayedColumns = [];
|
||||
this.getRunReportData();
|
||||
}
|
||||
|
||||
getRunReportData() {
|
||||
this.reportsService.getRunReportData(this.dataObject.reportData, this.dataObject.formData)
|
||||
.subscribe( (res: any) => {
|
||||
this.csvData = res.data;
|
||||
this.setOutputTable(res.data);
|
||||
res.columnHeaders.forEach((header: any) => {
|
||||
this.columnTypes.push(header.columnDisplayType);
|
||||
this.displayedColumns.push(header.columnName);
|
||||
});
|
||||
this.hideOutput = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a dynamic Mat Table.
|
||||
* @param {any} data Mat Table data
|
||||
*/
|
||||
setOutputTable(data: any) {
|
||||
this.dataSource = new MatTableDataSource(data);
|
||||
setTimeout(() => {
|
||||
this.dataSource.paginator = this.paginator;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the CSV file dynamically for run report data.
|
||||
*/
|
||||
downloadCSV() {
|
||||
const headers = this.displayedColumns;
|
||||
let csv = this.csvData.map((object: any) => object.row.join());
|
||||
csv.unshift(`data:text/csv;charset=utf-8,${headers.join()}`);
|
||||
csv = csv.join('\r\n');
|
||||
const link = document.createElement('a');
|
||||
link.setAttribute('href', encodeURI(csv));
|
||||
link.setAttribute('download', `${this.dataObject.reportData.name}.csv`);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number formatted as per user's decimal choice.
|
||||
* @param {number} value Value to be formatted as per decimal choice.
|
||||
*/
|
||||
toDecimal(value: number) {
|
||||
const decimalChoice = this.dataObject.decimalChoice;
|
||||
return this.decimalPipe.transform(value, `1.${decimalChoice}-${decimalChoice}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the weather Mat-Table column has decimal display type.
|
||||
* @param {number} index Index of column.
|
||||
*/
|
||||
isDecimal(index: number) {
|
||||
return this.columnTypes[index] === 'DECIMAL';
|
||||
}
|
||||
|
||||
}
|
||||
Vendored
+2
@@ -10,3 +10,5 @@ declare var module: NodeModule;
|
||||
interface NodeModule {
|
||||
id: string;
|
||||
}
|
||||
|
||||
declare module 'chart.js';
|
||||
|
||||
Reference in New Issue
Block a user