diff --git a/src/app/core/entity/database-field.decorator.ts b/src/app/core/entity/database-field.decorator.ts index 2dfd7b3554..0544806341 100644 --- a/src/app/core/entity/database-field.decorator.ts +++ b/src/app/core/entity/database-field.decorator.ts @@ -10,7 +10,7 @@ import { EntitySchema } from "./schema/entity-schema"; * @param propertySchema (optional) SchemaField definition that configures additional options regarding this field */ export function DatabaseField(propertySchema: EntitySchemaField = {}) { - return (target, propertyName: string) => { + return (target: any, propertyName: string) => { // Retrieve datatype from TypeScript type definition if (propertySchema.dataType === undefined) { const type = Reflect.getMetadata("design:type", target, propertyName); diff --git a/src/app/features/reporting/report-config.ts b/src/app/features/reporting/report-config.ts index d3bd051261..178857153e 100644 --- a/src/app/features/reporting/report-config.ts +++ b/src/app/features/reporting/report-config.ts @@ -23,8 +23,16 @@ class ReportConfig extends Entity { */ @DatabaseField() mode?: string; + /** + * (sql only) list of arguments needed for the sql query. Placeholder "?" will be replaced. + */ + @DatabaseField() neededArgs?: string[] = []; + /** the definitions to calculate the report's aggregations */ @DatabaseField() aggregationDefinitions: any[] = []; + + /** (sql only) the definition to calculate the report */ + @DatabaseField() aggregationDefinition: string | undefined = undefined; } /** @@ -62,7 +70,12 @@ export interface ExportingReport extends ReportConfig { export interface SqlReport extends ReportConfig { mode: "sql"; /** - * Array of valid SQL SELECT statements + * a valid SQL SELECT statements, can contain "?" placeholder for arguments + */ + aggregationDefinition: string; + + /** + * a list of arguments, passed into the sql statement */ - aggregationDefinitions: string[]; + neededArgs: string[]; } diff --git a/src/app/features/reporting/reporting/reporting.component.spec.ts b/src/app/features/reporting/reporting/reporting.component.spec.ts index a7338b61ae..56659b4edf 100644 --- a/src/app/features/reporting/reporting/reporting.component.spec.ts +++ b/src/app/features/reporting/reporting/reporting.component.spec.ts @@ -44,10 +44,7 @@ describe("ReportingComponent", () => { status: "FINISHED_SUCCESS", startDate: "2024-06-07T09:26:56.414", endDate: "2024-06-09T09:26:57.431", - args: new Map([ - ["from", "2024-01-01T00:00:00.000"], - ["to", "2024-01-01T23:59:59.999"], - ]), + args: { from: "2024-01-01T00:00:00.000", to: "2024-01-01T23:59:59.999" }, outcome: { result_hash: "000", }, diff --git a/src/app/features/reporting/reporting/select-report/select-report.component.html b/src/app/features/reporting/reporting/select-report/select-report.component.html index eaebedfd70..c6b811c434 100644 --- a/src/app/features/reporting/reporting/select-report/select-report.component.html +++ b/src/app/features/reporting/reporting/select-report/select-report.component.html @@ -11,7 +11,7 @@
- + Select Report - + Enter a date range { expect(component.selectedReport).toBe(report); }); + + it("should display date range filter when report mode is reporting", () => { + const report = new ReportEntity(); + report.mode = "reporting"; + component.reports = [report]; + + component.ngOnChanges({ reports: undefined }); + + expect(component.selectedReport).toBe(report); + expect(component.isDateRangeReport).toBeTrue(); + }); + + it("should display date range filter when sql report supports it", () => { + const report = new ReportEntity(); + report.mode = "sql"; + report.neededArgs = ["from", "to"]; + component.reports = [report]; + + component.ngOnChanges({ reports: undefined }); + + expect(component.selectedReport).toBe(report); + expect(component.isDateRangeReport).toBeTrue(); + }); + + it("should hide date range filter when sql report does not have these args", () => { + const report = new ReportEntity(); + report.mode = "sql"; + component.reports = [report]; + + component.ngOnChanges({ reports: undefined }); + + expect(component.selectedReport).toBe(report); + expect(component.isDateRangeReport).toBeFalse(); + }); + + it("should reset dates before calculation when sql report is not a DateRangeReport", () => { + const report = new ReportEntity(); + report.mode = "sql"; + component.reports = [report]; + + component.ngOnChanges({ reports: undefined }); + component.fromDate = new Date(); + component.toDate = new Date(); + + component.calculate(); + + expect(component.selectedReport).toBe(report); + expect(component.isDateRangeReport).toBeFalse(); + expect(component.fromDate).toBeUndefined(); + expect(component.toDate).toBeUndefined(); + }); + + it("should not reset dates before calculation when sql report is a DateRangeReport", () => { + const report = new ReportEntity(); + report.mode = "sql"; + component.reports = [report]; + report.neededArgs = ["from", "to"]; + + component.ngOnChanges({ reports: undefined }); + component.fromDate = new Date(); + component.toDate = new Date(); + + component.calculate(); + + expect(component.selectedReport).toBe(report); + expect(component.isDateRangeReport).toBeTrue(); + expect(component.fromDate).toBeDefined(); + expect(component.toDate).toBeDefined(); + }); }); diff --git a/src/app/features/reporting/reporting/select-report/select-report.component.ts b/src/app/features/reporting/reporting/select-report/select-report.component.ts index 296fde2eea..9dadd84db3 100644 --- a/src/app/features/reporting/reporting/select-report/select-report.component.ts +++ b/src/app/features/reporting/reporting/select-report/select-report.component.ts @@ -6,7 +6,7 @@ import { Output, SimpleChanges, } from "@angular/core"; -import { NgForOf, NgIf } from "@angular/common"; +import { JsonPipe, NgForOf, NgIf } from "@angular/common"; import { MatButtonModule } from "@angular/material/button"; import { MatFormFieldModule } from "@angular/material/form-field"; import { MatSelectModule } from "@angular/material/select"; @@ -36,6 +36,7 @@ import { ReportEntity } from "../../report-config"; FontAwesomeModule, MatProgressBarModule, MatTooltipModule, + JsonPipe, ], standalone: true, }) @@ -49,22 +50,49 @@ export class SelectReportComponent implements OnChanges { selectedReport: ReportEntity; fromDate: Date; toDate: Date; + /** whether the currently selected report includes filter parameters for a "from" - "to" date range */ + isDateRangeReport: boolean; ngOnChanges(changes: SimpleChanges): void { if (changes.hasOwnProperty("reports")) { if (this.reports?.length === 1) { this.selectedReport = this.reports[0]; + this.checkDateRangeReport(); } } } + calculate(): void { + if (!this.isDateRangeReport) { + this.fromDate = undefined; + this.toDate = undefined; + } + + this.calculateClick.emit({ + report: this.selectedReport, + from: this.fromDate, + to: this.toDate, + }); + } + reportChange() { this.dataChanged.emit(); + this.checkDateRangeReport(); } dateChange() { this.dataChanged.emit(); } + + private checkDateRangeReport(): void { + if (this.selectedReport.mode !== "sql") { + this.isDateRangeReport = true; + } else { + this.isDateRangeReport = + this.selectedReport.neededArgs.indexOf("from") !== -1 || + this.selectedReport.neededArgs.indexOf("to") !== -1; + } + } } interface CalculateReportOptions { diff --git a/src/app/features/reporting/sql-report/sql-report.service.spec.ts b/src/app/features/reporting/sql-report/sql-report.service.spec.ts index 415290b496..0c31b0d6b6 100644 --- a/src/app/features/reporting/sql-report/sql-report.service.spec.ts +++ b/src/app/features/reporting/sql-report/sql-report.service.spec.ts @@ -25,7 +25,7 @@ describe("SqlReportService", () => { startDate: null, endDate: null, status: "PENDING", - args: new Map(), + args: {}, outcome: { result_hash: "180a94a09c517b24e994aaf8342c58270a775f953eb32af78f06f1c8f61e37b9", @@ -39,10 +39,7 @@ describe("SqlReportService", () => { status: "FINISHED_SUCCESS", startDate: "2024-06-07T09:26:56.414", endDate: "2024-06-09T09:26:57.431", - args: new Map([ - ["from", "2024-01-01T00:00:00.000"], - ["to", "2024-01-01T23:59:59.999"], - ]), + args: { from: "2024-01-01T00:00:00.000", to: "2024-01-01T23:59:59.999" }, outcome: { result_hash: "000", }, @@ -55,10 +52,7 @@ describe("SqlReportService", () => { status: "FINISHED_SUCCESS", startDate: "2024-06-07T09:26:56.414", endDate: "2024-06-07T09:26:57.431", - args: new Map([ - ["from", "2024-01-01T00:00:00.000"], - ["to", "2024-01-01T23:59:59.999"], - ]), + args: { from: "2024-01-01T00:00:00.000", to: "2024-01-01T23:59:59.999" }, outcome: { result_hash: "000", }, diff --git a/src/app/features/reporting/sql-report/sql-report.service.ts b/src/app/features/reporting/sql-report/sql-report.service.ts index 604dae50d5..f5791929d2 100644 --- a/src/app/features/reporting/sql-report/sql-report.service.ts +++ b/src/app/features/reporting/sql-report/sql-report.service.ts @@ -23,7 +23,7 @@ export interface ReportCalculation { }; startDate: string | null; endDate: string | null; - args: Map; + args: { [key: string]: string }; status: "PENDING" | "RUNNING" | "FINISHED_SUCCESS" | "FINISHED_ERROR"; outcome: { result_hash: string; @@ -98,6 +98,14 @@ export class SqlReportService { from: Date, to: Date, ): Observable { + let params = {}; + if (from && to) { + params = { + from: moment(from).format("YYYY-MM-DD"), + to: moment(to).format("YYYY-MM-DD"), + }; + } + return this.http .post<{ id: string; @@ -105,10 +113,7 @@ export class SqlReportService { `${SqlReportService.QUERY_PROXY}/api/v1/reporting/report-calculation/report/${reportId}`, {}, { - params: { - from: moment(from).format("YYYY-MM-DD"), - to: moment(to).format("YYYY-MM-DD"), - }, + params: params, }, ) .pipe( @@ -166,8 +171,8 @@ export class SqlReportService { from: Date, to: Date, ): boolean { - let argFrom = value.args.get("from"); - let argTo = value.args.get("to"); + let argFrom = value.args["from"]; + let argTo = value.args["to"]; if (!argFrom || !argTo) { return false;