Skip to content

Creating a report

Five steps from a blank file to a working page. Steps 1–3 are required; skip 4 and you will have to type the URL, skip 5 and nothing breaks.

This builds StockPulse, the smallest of the shipped samples — so if a step does not behave as described, compare against sample/Inventory/models/reports/StockPulse.report.xml.

Create models/reports/StockPulse.report.xml. Name and Slug are the only required attributes; a report needs at least one dataset and one widget to be worth rendering.

<?xml version="1.0" encoding="utf-8"?>
<Report Name="StockPulse" Slug="stock-pulse" Label="Stock Pulse" Icon="fa fa-heart-pulse">
<DataSets>
<DataSet Name="DsPulse" Mode="SingleRow">
<![CDATA[
SELECT COUNT(*) AS ActiveProducts,
SUM(CASE WHEN p.Status = 'Draft' THEN 1 ELSE 0 END) AS DraftProducts
FROM [Inventory].[Products] p
WHERE p.IsDeleted = 0
AND p.IsActive = 1
AND p.CompanyId = @SessionCompanyId
]]>
</DataSet>
</DataSets>
<Widgets>
<Tile Name="TlActiveProducts" Title="Active products" Icon="fa fa-box" Color="primary"
Data="DsPulse" Value="ActiveProducts" Format="Integer">
<Detail Label="Still in draft" Value="DraftProducts" Format="Integer" Color="warning" />
</Tile>
</Widgets>
<Layout>
<Grid>
<Row>
<Column Span="4"><Item Name="TlActiveProducts" Width="12" /></Column>
</Row>
</Grid>
</Layout>
</Report>

Four things to get right, because each one is a hard parse error rather than a silent oddity:

  • A <Tile> must read a Mode="SingleRow" dataset. MultiRow is the default, so a tile needs the attribute stated. (Charts and tables want MultiRow and so say nothing.)
  • Every alias in a SingleRow SELECT becomes a property a tile can map — ActiveProducts and DraftProducts above. Any number of tiles can share one dataset, and that is one query between them.
  • <Item Name> in the layout names a WIDGET, not a column. It must match a widget’s Name exactly.
  • The SQL must be a single plain SELECT. No comments, no second statement, no writes — see the SELECT-only guard.

Scope every query with @SessionCompanyId as above unless you genuinely mean “all tenants”. Filters, charts, tables and embedded views come next; the full vocabulary is in Authoring a report.

Miss this and the page returns 401 for everyone but a System user. A report is gated by an RBAC resource named after its Name, in the Report category — whose defaults are exactly List, View, Export.

In your *.rbac.xml, declare it:

<Resource Name="StockPulse" Category="Report" Description="Minimal product-count dashboard"/>

…and grant it to the roles that should see it. The data endpoint needs List:

<Role Name="InventoryAdmin">
<Grant Resource="StockPulse" Capabilities="List,View"/>
</Role>

An embedded <View> widget needs no extra grant here — it is gated by its own resource, which it already has as a view.

A report definition is a transfer document: it lives in the database, and importing is what puts it there.

  1. Open System → Automation → Reports.
  2. Click Import and select one or more <Report> files.
  3. Each is created, or updated in place, matched by its Name.

A parse error surfaces here, against the file it came from, with a message naming the actual problem — so this is where an authoring mistake shows up, not at startup.

Re-importing an identical file reports Unchanged and adds nothing. A changed one appends a version, so the old definition stays recoverable. Export hands the stored text back byte-for-byte.

The other route is the grid’s Add, which writes a minimal stub and opens the designer. There the loop is Draft → Open Preview → Publish: drafting stores your work without going live, Open Preview renders that draft in a new tab, and Publish is what visitors finally see. A report created this way reads Never published until you publish it once.

The page is live at /reports/stock-pulse the moment step 3 succeeds — but nothing links to it yet. Add an item to your *.navbar.xml:

<Section Label="Reports">
<Item Name="stock-pulse" Label="Stock Pulse" Icon="fa fa-heart-pulse" Route="/reports/stock-pulse" />
</Section>

The route is plural /reports/{slug}. The singular /report-view?report=… is the unrelated PDF viewer, and /object/{name} is a grid — neither will resolve a report page.

Optionally add <ParentView Name="catalog" /> to the <Report> root, naming the navbar module it hangs under, and the breadcrumb will show that module above the report.

Unlike the report itself, a navbar file is swept on startup, so this one needs a restart.

5. Point a dataset at another database (optional)

Section titled “5. Point a dataset at another database (optional)”

By default a dataset runs against the app’s own database. To read somewhere else, name a source:

<DataSet Name="DsFlow" Source="InventoryReports"> … </DataSet>

…and define it in the host’s appsettings.json:

"Genie": {
"Sources": {
"InventoryReports": { "Dialect": "SqlServer", "Connection": "SqlServer" }
}
}

Connection is either a ConnectionStrings key or a literal connection string. Pointing it at a read-only least-privilege login is worth doing — that, more than the SELECT-only guard, is what makes a report unable to write. See Sources.

Symptom Cause
Import fails with a message about a widget, dataset or item An authoring error — the message names it. Fix and re-import.
Page loads but every card says “no data” The SQL matched nothing for this tenant. Check @SessionCompanyId and your filter values.
401 opening the page No RBAC resource, or no List grant to the caller’s role (step 2).
Blank page / falls through to nothing Wrong route — it is /reports/{slug}, plural (step 4).
“Choose your filters” instead of data A Required filter is blank. That gates the page by design and executes no dataset.
A card is missing entirely Its RolesAllowed excludes the caller, or it is Hidden="true".
A tile shows “—” Its Value names no column in the dataset’s projection, or the dataset returned no row.
Designer changes are not on the page They are in the draft. Click Publish — Draft deliberately does not go live.
Grid says Never published and Open is missing The report has a stub document but no published version. Publish it once from the designer.
401 on Open Preview, but the report itself loads The draft preview needs Update on ReportDefinitions, not just View on the report.