Tenant Analytics
Reference
Public
Research Evidence
Research evidence for the ERPat Tenant Analytics guide: every file shipped under modules/TenantAnalytics and what each controller, model, library, job, view, migration, and config does.
Guide version: r1
Module version: 1.1.0 Updated: 2026-07-27 Estimated time: 6 min 3 views
Reference
Research Evidence
The evidence base for this guide: every file shipped under
modules/TenantAnalytics/ and exactly what it does. Everything documented in the
other pages traces back to a file listed here — nothing is invented.
????
How to use this page. It maps the module's directory tree to plain-language descriptions
so a reviewer can verify a claim against its source, and a new maintainer can find their way
around quickly. Paths are relative to modules/TenantAnalytics/.
Manifest & module docs
| File | What it is |
module.json | The manifest. Declares the slug
tenant_analytics, version, the three permissions, the three tables, the cron
job, and — importantly — availability.tenant_database = false
(main-database-only). Also documents the deliberate absence of an end-user API. |
README.md | Module overview rendered in Settings → Manage
Modules. |
CHANGELOG.md | Per-module change history. |
docs/ | This offline operator guide (the pages you're reading, plus
assets/guide.css and assets/guide.js). |
composer.json | Module composer metadata and the
Modules\TenantAnalytics\ PSR-4 root (src/). |
config/
| File | What it does |
module_config.php | Registers the module in Manage Modules
(module_key, display name, System category, icon). |
permissions.php | The three RBAC permissions injected into the Roles
editor under System: Tenant Analytics. |
routes.php | The staff UI routes:
tenant_analytics → the tab, and tenant_analytics/(:any)
→ the controller's AJAX actions. |
api_routes.php | The three public API routes on the
api/v1/analytics/* stack (Route DSL), so they land in the generated OpenAPI
spec. |
api_public_routes.php | Declares those three routes as
anonymous to the OpenAPI generator (no bearer, no company key). |
public_api_limits.php | Per-route rate-limit caps (requests/minute
per IP) for the public API, read directly by the rate-limit middleware. |
system_logs.php | Registers the two audit events
(run:tenant_analytics, update:tenant_analytics_settings). |
settings_lists.php | Groups the module under System in the
Manage Modules list. |
menu.php | Empty by design — the module ships no
left-sidebar entry; its UI is a tab on the Tenants page. |
default_menu.php | Empty by design — the required
default-menu slice, empty because there is no sidebar surface. |
controllers/
| File | What it does |
Tenant_analytics.php | The operator UI + AJAX endpoints (tab, KPIs,
snapshots DataTable, aggregate panel, snapshot modal, the sweep console flow, re-sweep,
rollup rebuild, settings). Gated main-DB-only + read permission; heavier per-action gates
for run and configure. |
Tenant_analytics_api.php | The public, keyless API
(groups, summary, group). Reads only the rollups
table, enforces the kill switch, validates the date, and applies small-n money
suppression. |
models/
| File | What it does |
Tenant_analytics_base_model.php | Storage base that pins the primary
(default) DB connection, so writes always land in the main database even while
the active connection points at a tenant mid-sweep. |
Tenant_analytics_snapshots_model.php | Per-tenant snapshot storage:
upsert by natural key, the DataTable source, date/coverage counts, and retention
pruning. |
Tenant_analytics_rollups_model.php | The identifier-free platform
aggregate: read/upsert plus latest-date, available-dates and nearest-date helpers. |
Tenant_analytics_runs_model.php | The sweep audit ledger and resume
cursor; also resolves the fleet's sweep targets from the main clients
registry (optionally excluding inactive tenants). |
Tenant_analytics_probe_model.php | The measurement engine — the
seventeen metric collectors. Runs against the active (tenant) connection and guards
every table and column so a drifted tenant degrades to zeros rather than erroring. |
libraries/ & src/
| File | What it does |
libraries/Tenant_analytics_collector.php | Per-tenant orchestration:
switch into the tenant DB, probe each requested group, upsert results to the main snapshot
table, and always restore the main connection in a finally block (the isolation
invariant). |
libraries/Tenant_analytics_rollup.php | Builds the platform
aggregate for a date: deep-sums numeric leaves, unions scalar lists, and drops every
string/boolean leaf — the structural guarantee that no identifier reaches a
rollup. Overview also gets a tenant-status histogram (counts only). |
src/Middleware/PublicAnalyticsRateLimitMiddleware.php | A dedicated
per-route token-bucket rate limiter for the anonymous public API, swapped in for the
default client-API limiter. |
jobs/
| File | What it does |
TenantAnalyticsSnapshotJob.php | The nightly (03:00) global-scope
cron. Self-gates on the module flag, guards the three tables, sweeps every targeted tenant,
rebuilds the rollups, prunes past-retention snapshots, and closes the run. Slug
tenant_analytics_snapshot. |
views/tenant_analytics/
| File | What it renders |
tab_index.php | The embedded Analytics tab body: KPI cards, the
date/group toolbar, the per-tenant snapshots DataTable, and the platform-aggregate
panel. |
_kpi_cards.php | The four KPI cards (tenants swept, coverage,
failures, last run). |
run_sweep_modal_form.php | The manual-sweep console modal: options,
progress bar, counters, live log, per-tenant status list, and Retry Failed. |
settings_modal.php | The settings form (retention, include-inactive,
batch size, public API, small-n floor). |
snapshot_detail_modal.php | One tenant's full snapshot payload for a
group. |
ℹ
Where the tab is mounted. The Analytics tab is surfaced on the Tenants list page
(in the Tenancy module's tenant-manager view), which is why the module ships no menu entry of
its own.
migrations/ & seeders/
| File | What it does |
migrations/20260726065412_create_tenant_analytics_tables.php |
Creates the three main-database tables (snapshots, rollups, runs). Idempotent and
reversible; a no-op on tenant connections. |
seeders/TenantAnalyticsDemoSeeder.php | Loads demo data (three
pseudo-tenants × three groups of snapshots, a completed run, matching rollups) and
soft-deletes it on --remove. Idempotent and clearly marked so it never touches
real analytics. |
helpers/, language/ & tests/
| File | What it does |
helpers/tenant_analytics_helper.php | A small module helper (a
loaded-marker function). |
language/english/tenant_analytics_lang.php | All user-facing strings
for the tab, modals, KPIs, statuses and messages. |
tests/bootstrap.php | Reuses the core test bootstrap for the module
suite. |
tests/TenantAnalyticsTest.php | The module's PHPUnit suite, run with
php erpat module:test TenantAnalytics. |
Verified design invariants
These properties are enforced in the source and underpin the safety claims elsewhere in this
guide:
- Connection isolation. The collector restores the main connection in a
finally block after every tenant, so a failed tenant can't send subsequent writes
to the wrong database.
- Main-DB storage while reading tenants. Storage models pin the primary connection, so
snapshot/rollup writes land in the main database even while the active connection points at a
tenant.
- No identifiers in the rollup. The rollup builder keeps only numeric leaves and drops
every string/boolean, so tenant slugs, names, statuses and module maps cannot reach the table
the public API reads.
- Fail-soft probing. Every collector guards table and column existence, so a tenant on a
different schema revision yields zeros / omitted metrics instead of an error.
- Idempotent sweeps. Snapshots upsert by their natural key, so re-running a date replaces
rather than double-counts.
Next steps