Managing Saved Reports
View, re-run, edit, share, delete — and what's coming next
The Reports page at /analytics/reports is where every report you've built lives. It's also where institution-wide and role-shared reports appear — reports other instructors or admins have made visible to you.
The Reports Grid
Each report shows up as a card with:
- Name and optional description (first two lines only)
- Entity badge — which data source it queries (enrollments, courses, etc.)
- Chart type badge — table, kpi, line, pie, etc.
- Visibility badge — Private, Institution-wide, or Role-scoped
- Updated / Last run timestamps
- View button (opens the viewer) and Delete (creator only)
Searching
The search box above the grid filters by name, description, or entity. Search is case-insensitive and matches substrings.
Viewing a Report
Click View on any report card to open the viewer at /analytics/reports/[id]. The viewer:
- Loads the saved definition
- Runs it against your current data (with your role scope — see "Sharing and scope" below)
- Renders the result using the saved chart type
- Shows row count, execution time, and any warnings
Buttons in the viewer:
- Re-run — re-executes the definition (useful after data changes)
- Edit — opens the builder with the definition seeded
- Delete — creator-only; a confirmation prompt guards it
The "Results" header
Under the card title you'll see something like 142 rows in 34ms (truncated) · cached:
- N rows — total rows returned (capped at 10,000)
- N ms — execution time
- truncated — present if the result was cut off at the row cap; tighten your filters to get complete data
- cached — present when the engine served the result from Redis cache. Fresh runs compute live; repeats within ~60 seconds typically come from cache. Mutations to the underlying data (new enrollments, lesson completions, etc.) automatically invalidate the cache for your institution, so stale reads are rare.
Editing
Click Edit from the viewer. The builder opens seeded with the existing definition. Make your changes and click Save. The new definition overwrites the saved one — there's no version history in v1, so if you want to keep the old version, use Save As first to fork a copy under a new name.
Only the report creator can edit. If you're viewing an institution-shared report someone else made, the Edit button is hidden. If you want to build on it, open it, click Save As in the builder's top bar — the existing definition is preserved and you get a fresh copy under your own ownership.
Deleting
Click Delete from either the grid or the viewer. You'll be asked to confirm. Deletion is permanent — the report and its run history are removed. The Prisma schema cascades the delete to LMSReportRun, LMSReportAlert, and LMSReportSchedule rows, so any scheduled email digests stop immediately.
Only the creator can delete. Institution admins can't (currently) force-delete other people's reports.
Sharing and Scope
This is the part of the system that's most important to understand, because it's how OptiLearn preserves privacy when multiple people use the same report.
Visibility levels
The save dialog has a Visibility dropdown with two options:
- Private (only you) — the default. The report only appears in your own Reports list.
- Institution (everyone in your institution) — the report appears in every user's Reports list. Non-creators see it as read-only (View, but not Edit or Delete).
A third visibility level — role-scoped (e.g. "shared with all teachers but not students") — is modeled in the schema and will be exposed in a future release. For now, use Institution if you want anyone else to see the report.
The critical distinction: sharing a definition ≠ sharing data
When you share a report, you're sharing the question you're asking, not the answer you got. Every time a shared report is viewed, it runs fresh against the viewer's role scope.
Example: You're an admin and you save a report "Enrollments in last 30 days by course". You share it with your institution. A teacher opens it. The report runs, but the teacher's scope is ownCourses — the executor injects a courseId IN (select id from Course where createdById = <teacher>) filter. The teacher sees enrollments only in their own courses, not yours.
This means:
- Sharing a report with students gives them only their own data — no risk of leaking other students' activity.
- Sharing with teachers shows them their own courses only — they can't see admin data through a shared report.
- Sharing between admins shows the same data to everyone — admins have institution scope.
You never have to think about "should this report be safe to share?" The engine handles it.
Re-running
Clicking Re-run in the viewer forces the engine to recompute the report. Most of the time a fresh page load will already serve live numbers — the Redis cache TTL is short (60 seconds for live queries) and any write to the underlying data (new enrollment, lesson completed, certificate issued, etc.) automatically invalidates the cache for your institution via a Prisma client extension. So you only need to click Re-run if you're impatient or you want to bypass a specific sub-60-second cache window.
If a chart is backed by the hourly snapshot path (you'll see a Served from pre-aggregated snapshot — refreshed hourly. Live data may be up to 1h behind. warning line), Re-run won't update the numbers any sooner than the next cron tick. Snapshot data is only as fresh as the last snapshot write. See the Analytics Overview for the details.
Audit trail
Every run, edit, and delete of a report writes a row to LMSAuditLog with:
- The action (
report.run,report.create,report.update,report.delete) - Who did it (userId, userEmail)
- What was affected (resourceId, metadata)
- When
- Where (IP address, user agent)
Institution admins will get an audit log viewer in a later release. For now the data is collected and queryable via direct DB access.
Exporting
The viewer has an Export dropdown next to the Re-run button. It downloads the current result as CSV, Excel (XLSX), or PDF — see Exporting Reports for the format reference and audit semantics.
The export uses the same scope as the live view: a teacher exporting a shared admin report still only gets their own courses' data. Sharing a report's definition is not the same as sharing the underlying data.
Scheduling deliveries and alerts
The viewer has two more tabs alongside Result: Schedules and Alerts.
- Schedules — recurring email delivery on a daily, weekly, or monthly cadence. Picks a format (CSV/XLSX/PDF) and a recipient list. The hourly cron picks up due schedules and emails the rendered file as an attachment.
- Alerts — threshold rules like "email me when overdue assignments > 5". Alerts evaluate during scheduled runs in v1, so an alert without an accompanying schedule won't auto-fire. The Alerts tab shows a banner when this is the case.
See Scheduling Reports for the full walkthrough, cadence semantics, timezone notes, and audit trail.
What's coming in future releases
A few polish items are deferred; none of them block using the feature today:
- Role-scoped visibility — share with "All Teachers" or "Admins only" instead of the whole institution. Currently you can only choose Private or Institution.
- Folders — organize reports into hierarchies with per-folder access. The
LMSReportFoldermodel exists; the UI is planned. - Pause toggle for schedules — explicit pause without deletion (currently you delete the schedule to stop it).
- Standalone alert cron — alerts that fire on their own cadence rather than piggybacking on schedules. For now, an alert on a report without a schedule will never auto-fire.
- Local-time helper for schedules — the hour picker is UTC-only; a timezone converter is on the list.
- Audit log viewer UI — every report run / save / export / schedule fire already writes to
LMSAuditLog, but there's no in-app viewer yet.
Tips
- Name reports descriptively. "My report" is useless six months from now. "Biology 101 — weekly completion rate" is searchable.
- Describe the use case. The description field is where you explain why the report exists — "Used in our Monday staff meeting to track cohort progress."
- Delete stale reports. Old, unused reports clutter the list and confuse new users. Prune quarterly.
- Start simple, then iterate. A basic "list of enrollments where status = ACTIVE" is a legitimate report. You can always add groupings and aggregations later by hitting Edit.