Skip to content

Add a Composable Report ​

This guide describes the recommended workflow for adding a composable report backed by the PostgreSQL dataset path.

The example uses an American-market report: Customer Sales Analysis.

Verified: runtime + PostgreSQL reporting anchorsTemplate: implementation workflow

How to read this page. Treat the verified anchors as the confirmed shared pipeline, then use the numbered steps as the recommended way to register and ship a new provider-backed composable report.

Verified anchors behind this guide ​

  • NGB.Runtime/Reporting/ReportEngine.cs
  • NGB.Runtime/Reporting/ReportExecutionPlanner.cs
  • NGB.PostgreSql/Reporting/PostgresReportDatasetCatalog.cs
  • NGB.PostgreSql/Reporting/IPostgresReportDatasetSource.cs
  • NGB.PostgreSql/Reporting/PostgresReportSqlBuilder.cs
  • NGB.PostgreSql/Reporting/PostgresReportDatasetExecutor.cs

1. Recognize the composable use case ​

Choose a composable report when the report can be expressed as:

  • dataset fields;
  • dataset measures;
  • filters;
  • row groups and column groups;
  • sorts;
  • optional detail fields.

This is exactly the shape confirmed by the verified planner and SQL-builder anchors.

2. Define the report dataset at the platform-definition level ​

Template guidance with verified planner behavior

Create a dataset definition that describes the business vocabulary of the report.

For customer_sales_analysis, typical fields and measures might be:

Fields ​

  • customer_display
  • customer_id
  • sales_rep_display
  • sales_rep_id
  • invoice_date
  • warehouse_display
  • warehouse_id
  • document_display
  • document_id

Measures ​

  • net_sales_amount
  • gross_margin_amount
  • quantity_sold

The verified planner source shows that runtime normalizes groups, detail fields, measures, sorts, predicates, and parameters from the selected layout.

3. Register a PostgreSQL dataset binding ​

Verified anchors:

  • PostgresReportDatasetCatalog.cs
  • IPostgresReportDatasetSource.cs

This is the strongest confirmed extension point in the composable-report path.

The catalog gathers datasets from all IPostgresReportDatasetSource registrations. That means the provider-specific part of a new composable report should normally include a dataset source returning one or more PostgresReportDatasetBinding values.

Illustrative shape:

csharp
// Illustrative example, not a verbatim repository excerpt.
public sealed class SalesReportDatasetSource : IPostgresReportDatasetSource
{
    public IReadOnlyList<PostgresReportDatasetBinding> GetDatasets()
        =>
        [
            BuildCustomerSalesAnalysisDataset()
        ];
}

4. Define SQL-facing field and measure bindings deliberately ​

Verified anchor: PostgresReportSqlBuilder.cs

The SQL builder proves that the dataset binding must be able to resolve:

  • field expressions, optionally by time grain;
  • aggregate expressions for measures;
  • predicates;
  • selected row/column/detail projections;
  • sort aliases.

That means the dataset binding is not just a label registry. It is the SQL contract for the composable report.

5. Use interactive support fields correctly ​

One particularly useful source-backed detail appears in PostgresReportSqlBuilder.cs.

When a report projects fields such as:

  • document_display
  • account_display
  • other *_display fields

the builder may also append the corresponding support ID fields so the UI can keep interactive navigation alive.

This means a composable report should usually define display/id pairs together:

  • customer_display + customer_id
  • warehouse_display + warehouse_id
  • document_display + document_id

That pattern is not guesswork. It is directly supported by the verified builder code.

6. Understand current execution semantics ​

Verified anchor: PostgresReportDatasetExecutor.cs

The current PostgreSQL composable executor:

  • builds SQL through the SQL builder;
  • runs it through the current unit-of-work connection;
  • materializes rows through Dapper;
  • uses deterministic cursor predicates and limit + 1 over-fetching for page continuation.

The public HTTP path rejects nonzero offsets and disabled paging. ReportQueryService validates and protects cursors, and ReportPagedQueryExecutor reads the requested groupPath branch. Return hasMore and nextCursor; do not require an exact total on every page. Full downloads use the streaming export path. See Report Browsing and Direct Downloads.

7. Compose it into the vertical host ​

Once the dataset source and report definition exist, ensure the vertical modules that contain them are part of the host composition.

Verified composition anchor: NGB.PropertyManagement.Api/Program.cs

8. Test matrix for a composable report ​

At minimum, cover:

  • dataset registration;
  • duplicate dataset-code prevention;
  • field resolution;
  • measure aggregation resolution;
  • filter predicates;
  • row grouping;
  • column grouping;
  • detail mode;
  • sort behavior;
  • interactive display/id drilldown support;
  • paging behavior.

9. When to stop being composable ​

If the report starts needing:

  • custom running balances;
  • opening-balance windows;
  • highly specialized row synthesis;
  • business logic that does not map cleanly to fields and measures,

then the report should probably become canonical instead of forcing complexity into the dataset binding.

10. Production checklist ​

Before shipping a composable report, confirm:

  • the dataset vocabulary is stable and business-readable;
  • display/id pairs exist for interactive fields;
  • SQL expressions are explicit and index-aware;
  • paging expectations match the verified provider path;
  • the report remains generic enough to benefit from the shared composable pipeline.

Released under the Apache License 2.0.