Skip to content

Commit

Permalink
feat: add multi-query support (#2644)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomwwinter authored Nov 21, 2024
1 parent 961a9de commit 2237f66
Show file tree
Hide file tree
Showing 15 changed files with 467 additions and 16 deletions.
7 changes: 6 additions & 1 deletion src/app/core/export/download-service/download.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,12 @@ export class DownloadService {
result = typeof data === "string" ? data : JSON.stringify(data); // TODO: support exportConfig for json format
return new Blob([result], { type: "application/json" });
case "csv":
result = await this.createCsv(data);
if (Array.isArray(data)) {
result = await this.createCsv(data);
} else {
// assume raw csv data
result = data;
}
return new Blob([result], { type: "text/csv" });
case "pdf":
return new Blob([data], { type: "application/pdf" });
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

:host {
pointer-events: auto;
}
Expand Down
64 changes: 59 additions & 5 deletions src/app/features/reporting/report-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,51 @@ class ReportConfig extends Entity {
@DatabaseField() mode?: string;

/**
* (sql only) list of arguments needed for the sql query. Placeholder "?" will be replaced.
* version of ReportConfig syntax. Just relevant for SqlReports
*/
@DatabaseField() version?: number = 1;

/**
* (sql v1 only) list of arguments needed for the sql query. Placeholder "?" will be replaced.
*/
@DatabaseField() neededArgs?: string[] = [];

/** the definitions to calculate the report's aggregations */
/** (reporting/exporting only, in browser reports) the definitions to calculate the report's aggregations */
@DatabaseField() aggregationDefinitions: any[] = [];

/** (sql only) the definition to calculate the report */
/** (sql v1 only) the definition to calculate the report */
@DatabaseField() aggregationDefinition: string | undefined = undefined;

/**
* (sql v2 only) transformations that are applied to input variables (e.g. startDate, endDate)
* example: {startDate: ["SQL_FROM_DATE"], endDate: ["SQL_TO_DATE"]}
*/
@DatabaseField() transformations: {
[key: string]: string[];
};

/**
* (sql v2 only) ReportDefinitionItem, ether ReportQuery or ReportGroup
*
* Can be ReportQuery:
* {query: "SELECT * FROM foo"}
*
* Can be ReportGroup:
* {groupTitle: "This is a group", items: [...]}
*
*/
@DatabaseField() reportDefinition: ReportDefinitionDto[];
}

export interface ReportDefinitionDto {
/** an SQL query */
query?: string;

/** title (human-readable) for a set of hierarchically grouped sub-items */
groupTitle?: String;

/** hierarchical child items, building a recursive set of report groups display in an indented way */
items?: ReportDefinitionDto[];
}

/**
Expand Down Expand Up @@ -69,13 +105,31 @@ export interface ExportingReport extends ReportConfig {
*/
export interface SqlReport extends ReportConfig {
mode: "sql";

/**
* version of the ReportConfiguration, currently 1 or 2
*/
version: number;

/**
* a valid SQL SELECT statements, can contain "?" placeholder for arguments
* a valid SQL SELECT statements, can contain "?" placeholder for arguments (only v1)
*/
aggregationDefinition: string;

/**
* a list of arguments, passed into the sql statement
* a list of arguments, passed into the sql statement (only v1)
*/
neededArgs: string[];

/**
* see ReportConfig docs
*/
transformations: {
[key: string]: string[];
};

/**
* see ReportConfig docs
*/
reportDefinition: ReportDefinitionDto[];
}
13 changes: 11 additions & 2 deletions src/app/features/reporting/reporting/reporting.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
[loading]="isLoading"
[exportableData]="exportableData"
(calculateClick)="calculateResults($event.report, $event.from, $event.to)"
(dataChanged)="reportCalculation = null; data = null"
(dataChanged)="selectedReportChanged()"
class="view-section"
></app-select-report>

Expand Down Expand Up @@ -42,7 +42,16 @@ <h3 class="header" i18n>Something went wrong calculating the report</h3>
*ngIf="data?.length > 0 && mode === 'reporting'"
[rows]="data"
></app-report-row>

<app-object-table
*ngIf="data?.length > 0 && (mode === 'exporting' || mode === 'sql')"
*ngIf="
data?.length > 0 &&
(mode === 'exporting' || (mode === 'sql' && currentReport?.version !== 2))
"
[objects]="data"
></app-object-table>

<app-sql-v2-table
*ngIf="data?.length > 0 && mode === 'sql' && currentReport?.version === 2"
[reportData]="data"
></app-sql-v2-table>
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ describe("ReportingComponent", () => {
mockReportingService.calculateReport.and.resolveTo([]);
mockSqlReportService = jasmine.createSpyObj([
"query",
"getCsv",
"flattenData",
"fetchReportCalculation",
"createReportCalculation",
"waitForReportData",
Expand Down
30 changes: 27 additions & 3 deletions src/app/features/reporting/reporting/reporting.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
} from "../sql-report/sql-report.service";
import { RouteTarget } from "../../../route-target";
import { firstValueFrom } from "rxjs";
import { SqlV2TableComponent } from "./sql-v2-table/sql-v2-table.component";

@RouteTarget("Reporting")
@Component({
Expand All @@ -33,20 +34,24 @@ import { firstValueFrom } from "rxjs";
ObjectTableComponent,
DatePipe,
JsonPipe,
SqlV2TableComponent,
],
standalone: true,
})
export class ReportingComponent {
reports: ReportEntity[];
mode: ReportEntity["mode"]; // "reporting" (default), "exporting", "sql"

currentReport: ReportEntity;

isLoading: boolean;
isError: boolean = false;
errorDetails: string | null = null;

reportCalculation: ReportCalculation | null = null;

data: any[];
exportableData: any[];
exportableData: any;

constructor(
private dataAggregationService: DataAggregationService,
Expand Down Expand Up @@ -80,9 +85,23 @@ export class ReportingComponent {
return Promise.reject(reason.message || reason);
});

this.currentReport = selectedReport;

this.mode = selectedReport.mode ?? "reporting";
this.exportableData =
this.mode === "reporting" ? this.flattenReportRows() : this.data;

switch (this.mode) {
case "reporting":
this.exportableData = this.flattenReportRows();
break;
case "sql":
this.exportableData = this.sqlReportService.getCsv(
this.sqlReportService.flattenData(this.data),
);
break;
default:
this.exportableData = this.data;
}

this.isLoading = false;
}

Expand Down Expand Up @@ -147,4 +166,9 @@ export class ReportingComponent {
}
return { label: resultLabel, result: header.result };
}

selectedReportChanged() {
this.reportCalculation = null;
this.data = [];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,19 @@ export class SelectReportComponent implements OnChanges {
private checkDateRangeReport(): void {
if (this.selectedReport.mode !== "sql") {
this.isDateRangeReport = true;
} else {
} else if (
this.selectedReport.version == 1 ||
this.selectedReport.version == undefined
) {
this.isDateRangeReport =
this.selectedReport.neededArgs.indexOf("from") !== -1 ||
this.selectedReport.neededArgs.indexOf("to") !== -1;
} else if (this.selectedReport.version == 2) {
this.isDateRangeReport =
!!this.selectedReport.transformations["startDate"] ||
!!this.selectedReport.transformations["endDate"];
} else {
this.isDateRangeReport = false;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<table
mat-table
[dataSource]="dataSource"
class="data-table"
i18n-aria-label="Label for table showing report result"
aria-label="Table showing the report results"
>
<ng-container matColumnDef="Name">
<th mat-header-cell *matHeaderCellDef i18n>Name</th>
<td
mat-cell
*matCellDef="let row"
[style.padding-left.px]="16 + row.level * 20"
>
{{ row.key }}
</td>
</ng-container>

<ng-container matColumnDef="Count">
<th mat-header-cell *matHeaderCellDef i18n>Count</th>
<td mat-cell *matCellDef="let row">
{{ row.value }}
</td>
</ng-container>

<tr mat-header-row *matHeaderRowDef="columns; index as i"></tr>
<tr
mat-row
class="row"
*matRowDef="let row; columns: columns; index as i"
[ngClass]="{ 'bg-even': i % 2 === 0 }"
></tr>
</table>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.data-table {
width: 100%;
line-height: 1.5;
}

.row:hover {
background: #ececec;
}

.bg-even {
background: #fafafa;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { ComponentFixture, TestBed } from "@angular/core/testing";

import { SqlV2TableComponent } from "./sql-v2-table.component";
import { CoreTestingModule } from "../../../../utils/core-testing.module";
import { HttpClient } from "@angular/common/http";
import { of } from "rxjs";

describe("SqlV2TableComponent", () => {
let component: SqlV2TableComponent;
let fixture: ComponentFixture<SqlV2TableComponent>;
let mockHttp: jasmine.SpyObj<HttpClient>;

beforeEach(async () => {
mockHttp = jasmine.createSpyObj(["get"]);
mockHttp.get.and.returnValue(of(undefined));
await TestBed.configureTestingModule({
imports: [SqlV2TableComponent, CoreTestingModule],
providers: [{ provide: HttpClient, useValue: mockHttp }],
}).compileComponents();

fixture = TestBed.createComponent(SqlV2TableComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it("should create", () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Component, inject, Input, OnInit } from "@angular/core";
import { SqlReport } from "../../report-config";
import { JsonPipe, NgClass, NgForOf } from "@angular/common";
import { MatTableDataSource, MatTableModule } from "@angular/material/table";
import { MatSortModule } from "@angular/material/sort";
import {
SqlReportRow,
SqlReportService,
} from "../../sql-report/sql-report.service";
import { Logging } from "../../../../core/logging/logging.service";

@Component({
selector: "app-sql-v2-table",
standalone: true,
imports: [MatTableModule, MatSortModule, NgClass],
templateUrl: "./sql-v2-table.component.html",
styleUrl: "./sql-v2-table.component.scss",
})
export class SqlV2TableComponent implements OnInit {
sqlReportService = inject(SqlReportService);

@Input() report: SqlReport;

@Input() set reportData(value: any[]) {
this.data = this.formatData(value);
}

isError = false;

dataSource = new MatTableDataSource();
columns: string[] = ["Name", "Count"];

data: SqlReportRow[] = [];

ngOnInit(): void {
this.dataSource.data = this.data;
}

private formatData(value: any[]) {
this.isError = false;
try {
return this.sqlReportService.flattenData(value);
} catch (error) {
Logging.error(error);
this.isError = true;
}
}
}
Loading

0 comments on commit 2237f66

Please sign in to comment.