Report pages
A report page is a dashboard authored as a single *.report.xml document: filters at the top, then
a layout of cards — KPI tiles, charts, a small table, or an existing view embedded read-only. It is
served at /reports/{slug} and draws entirely from SQL you author.
It is the third of Genie’s three kinds of report, and the only one that is a page:
| Kind | Authored as | Produces |
|---|---|---|
| Code-defined report | a C# ReportBase class |
a downloadable file (PDF, CSV) |
| Report grid | a read-only *.view.xml |
one interactive, filterable table |
| Report page | *.report.xml |
a dashboard of tiles, charts and tables |
Reach for a report page when one number or one grid is not the answer — when the question is “how are we doing?” and needs several figures side by side.
The four blocks
Section titled “The four blocks”<Report Name="InventoryOverview" Slug="inventory-overview" Label="Inventory Overview"> <Filters> … the SAME grammar as a view's PreFilters … </Filters> <DataSets> … read-only SELECTs … </DataSets> <Widgets> … Tile | Chart | DataTable | View … </Widgets> <Layout> … the 12-column Grid/Row/Column walk … </Layout></Report><Filters>is the identicalPreExecuteFiltersblock a view declares, rendered by the same panel. Every declared filter is bound to every dataset.<DataSets>are named, read-onlySELECTstatements. Nothing else is accepted — see the SELECT-only guard.<Widgets>are the cards. The element name is the kind.<Layout>is a form layout, except<Item Name="…">names a widget instead of a field.
Full element and attribute reference: Authoring a report.
Definitions are managed, not migrated
Section titled “Definitions are managed, not migrated”A report page follows the Workflow and Wizard model, not the view model. Its definition lives in the
database (Genie.ReportStore), and *.report.xml is a transfer document — you import it, rather
than dropping it in models/ for the startup sweep to pick up.
So a *.report.xml sitting on disk is inert until it is imported, and adding *.report.xml to
Genie:Migration:FilePatterns does nothing.
There are two ways in, and they write the same document:
- The report designer — build it visually. Add on the definitions grid creates one and opens it in the designer; the row’s Design action edits an existing one.
- Import — open System → Automation → Reports and use Import, selecting one or more
<Report>files. See Creating a report for the whole sequence.
Either way the definition is created or updated by its Name. Export hands the stored text back
byte-for-byte, so a definition round-trips unchanged — which is what lets you design one and then
commit the file.
Why managed rather than migrated
Section titled “Why managed rather than migrated”Because a report is not a view. It does not live in the ViewStore’s polymorphic column, it does not
share a name namespace with tables and forms, and — like a workflow — it is the kind of artefact an
administrator edits in a running system rather than redeploys.
Versioning
Section titled “Versioning”Every import that changes the document appends a row to Genie.ReportStoreHistory under a fresh
six-character version id, and repoints the definition at it. So:
- the current version is a history row, not a special case;
- re-importing an identical file is reported Unchanged and adds nothing;
- two imports of identical text are still two versions, each with its own note and timestamp — the id is deliberately random, not a content hash, so neither one is collapsed away.
This mirrors workflow and wizard versioning exactly.
Draft and published
Section titled “Draft and published”A definition has two documents: the published one, which /reports/{slug} serves, and an optional
draft, which is what the designer edits. Only a publish appends a version — a draft save is not
versioned at all, which is what makes it cheap enough to press constantly while authoring.
So a report is in one of four states, and the definitions grid names them:
| Status | Meaning |
|---|---|
| Never published | The row exists (created by Add) but nothing has ever gone live. /reports/{slug} shows an empty page, and the grid’s Open action is hidden. |
| Draft | Never published, and there is work in progress. |
| Published + Draft | Live, with newer unpublished work beside it. |
| Published | Live, with nothing pending. |
The published marker is the definition’s version, not the presence of XML: Add writes a minimal parseable stub so the designer can always open something, so a row can have a document and still never have been published.
One draft per report. Publishing clears it; Discard draft throws it away. Importing a
*.report.xml publishes directly and ignores drafts entirely, so re-importing over an open draft
leaves that draft in place, flagged stale.
Permissions
Section titled “Permissions”A report page is gated by an RBAC resource named after the report, in the Report category —
whose defaults are exactly List, View, Export:
<Resource Name="InventoryOverview" Category="Report" Description="Stock value, replenishment pressure and movement flow"/>Its data endpoint requires the List verb. Two consequences worth knowing:
- Structure is not gated beyond authentication. The widget and filter layout is fetched from a separate, SQL-free endpoint — the same split the object endpoints use. This is why an unpublished draft has its own endpoints rather than a flag on this one: a flag here would hand every authenticated user an author’s work in progress.
- An embedded
<View>is gated by its own resource. Embedding never launders a permission: a caller who cannot openlow-stock-reportsees its error inside the card, not its rows.
A widget can also be narrowed by role with RolesAllowed. A widget the caller’s roles exclude is
dropped server-side — its card is not rendered and its dataset is never queried.
Authoring, and the draft preview
Section titled “Authoring, and the draft preview”Authoring is gated on a different resource: ReportDefinitions, the one behind the definitions
grid — View to open a report in the designer, Update to draft or publish it. That separation is
deliberate: gating a save on the report’s own resource would make “can view this dashboard” mean “can
rewrite it”, and would leave a brand-new report, whose resource does not exist yet, ungated entirely.
The draft preview (/report-preview) sits on the authoring side too, and needs Update — running
an unpublished document is more than reading one, and it belongs to whoever may author it.
A draft preview applies the same row-level filter the published report applies, resolved against the report’s own resource name — never against the draft document’s, which the author can change at will. Otherwise renaming a draft would be a way out of a row filter.
Worked examples
Section titled “Worked examples”Four samples ship in sample/Inventory/models/reports. Between them they exercise the whole
vocabulary — a build-failing test (SampleReportFilesTests) parses all four and asserts that every
widget kind, every chart kind, both dataset modes and all three filter shapes still have an example.
| File | Demonstrates |
|---|---|
StockPulse.report.xml |
The smallest valid report: one dataset, one tile, no filters. Start here. |
InventoryOverview.report.xml |
A required DateRange (so the page gates until it is filled), a SingleRow KPI strip, a horizontal bar and a stacked area chart, a Source, a DataTable, and an embedded view. |
PurchasingPerformance.report.xml |
No required filter, so it loads on open. Static-select and boolean filters, Tab blocks, vertical bar and line charts, Percent/Decimal formats, Drill on a tile, and the {{permission-filter}} placeholder. |
WarehouseActivity.report.xml |
A dense layout — a six-tile strip at Span="2", three slice charts to a row, and a chart beside each table — plus Pie and Donut, a collapsed Section, a widget withheld by RolesAllowed, a Hidden widget, a sortable table with a hidden and a role-gated column, two embedded views, and a single-Date filter. |
They are transfer documents like any other, so import them from the Reports definitions grid.
Where to go next
Section titled “Where to go next”- Creating a report — start here: write it, grant it, import it, link it.
- The report designer — build one visually instead, at
/report-designer. - Authoring a report — the complete
*.report.xmlreference. - Runtime & sources — how datasets execute,
Genie:ReportingSources, row caps, and the guards. - Report endpoints — the two calls a page makes.