Events IA refactor: submenu, cross-event reports, shared stats - #2113
Draft
maebeale wants to merge 17 commits into
Draft
Events IA refactor: submenu, cross-event reports, shared stats#2113maebeale wants to merge 17 commits into
maebeale wants to merge 17 commits into
Conversation
Replace the inconsistent "training"/"non-demand" labels for non-on-demand trainings with a single "Live" term across the scholarship report and form, and add Event.live/.on_demand scopes so the delivery-format split has a canonical query source. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collapse the many mirrored spellings of the shared event-stat rules into single model-level sources, so per-event and cross-event views can't drift: - EventRegistration.attended_facilitator_trainings (was spelled 4 ways across the controller and the training-attendees services) - Scholarship.externally_funded/.org_subsidized + Grant.self_donated_ids for the funded/unfunded split (consumed by EventDashboard, EventRevenueFigures, and the EventRegistration funder scopes) - Affiliation::STATUSES, #status_on, .with_status (Ruby rule in the roster + SQL rule in the controller, now one source with an executable agreement spec) - Address::US_STATE_ABBREVIATIONS (deduped from two copies) - EventRegistration.status_counts_by_event (shared by the dashboard and the participation report) Behavior-preserving except one intentional fix: the EventRegistration funder filter now treats an AWBW self-donated grant as org-subsidized (unfunded), matching EventDashboard — previously it split on grant presence alone. Pinned by a new .funder spec. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The cross-event scholarship report built one EventDashboard per event (each ~8 queries) just to read that event's funded/unfunded dollars, counts, and attended headcount. Replace it with EventScholarshipFigures — a batched loader modeled on EventRevenueFigures that computes every event's figures in a fixed number of grouped queries and mirrors the dashboard's funded/unfunded split (parity spec holds them together). The optional funder: narrowing carries over. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename the cross-event report suite to its new vocabulary and open it to event owners for their own event: - Routes/actions/views: statistics -> reports (the hub), training_attendees -> attendees; Turbo frame ids and view filenames renamed in lockstep (attendees_results / attendees_charts). No redirects for old URLs (internal admin tool). - Authorization: the whole-org view stays admin-only (cross_event_reports?), but a single-event slice (event_id filter, from the per-event Reports/Roster tabs) is visible to that event's owner too (event_reports?). A before_action resolves event_id to the Event and authorizes against it. - Event-scoped eyebrows: when event_id is present, the reports/attendees pages return to that event's dashboard. - Entry points, helper names (hub_to_report_params etc.), labels, return_to tokens, and AGENTS.md updated throughout. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reshape the per-event subnav and retire the Background page in favor of the shared cross-event views: - Subnav is now Dashboard | Manage | Roster | Reports | Scholarships | Staff | Edit event. "Manage" is the renamed Registrants tab (label + heading; route/action stay registrants). Roster and Reports link to the shared attendees / reports pages pre-filtered to this event (event_id), visible to the event's owner via event_reports?. - Remove the Background page entirely (route, action, policy, view) — its roster + charts now live on the shared attendees index. Drop the background_return_path helper and collapse the shared breakdown/roster/ city partials to their index (cross-event) form, keeping the event-scoped city links the recipients page still needs. - Fix the dead subnav highlights: Onboarding and Bulk payments now light up the Manage tab (previously highlighted nothing). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The scholarship-only cities sub-table rendered its rows without the index drill-in paths, so on the cross-event attendees index (where there is no @event) it fell into the event-scoped registrant-link branch and raised UrlGenerationError (nil event). Pass the same row/scholarship drill-in paths the full cities list already uses. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per updated direction: Roster is a per-event page again (not the shared attendees index), showing the event's active registrants as a roster table + demographic charts. It reuses the shared _registrant_roster and _registrant_breakdowns partials (context: :event), fed by EventDashboard — the same data source and partials the old Background page used, so no roster/chart markup is duplicated. - New roster route/action/policy (roster? = manage?, owner-visible); the subnav Roster tab links to roster_event_path (Reports still links to the shared reports hub scoped by event_id). - Restore the dual-context (:event/:index) shared breakdown/roster/city partials that the Background-removal commit had collapsed to :index, renaming background_return_path -> roster_return_path (Manage eyebrow now returns to "← Roster"). - Cross-event index heading renamed "Training attendees" -> "Attendees". - Widen the reports-hub Event dropdown so long event titles fit. Population note: uses the app-wide EventRegistration.active scope (registered, attended, incomplete_attendance, transferred_in). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- The participation report's link to the attendees index was a stale 'Training attendees' button; make it a grey nav link reading 'Attendees' to match the renamed index and the page's other nav links. - Widen the reports-hub Event dropdown (w-80 -> w-96) so a full event title like '2026-10-07 — AWBW Facilitator Training' fits. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The shared revenue/participation/reports event-type dropdown now offers All events (default) · All trainings · Live trainings · On-demand trainings · Other, using the Event.live/.on_demand scopes to narrow the report to a delivery format. Widened the select to fit the longer labels. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The reports-hub Participation card now offers an 'Attendees →' link beneath 'Full report →', carrying the same filter params (event_id etc.) so the attendees index opens scoped to the same context. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reached from the event subnav (Scholarships tab), so the back link should return to the event's dashboard, not the global events index — matching the other per-event pages. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…+ lazy load - Log a 'view.events.<page>' Ahoy event on every event subpage and report (dashboard, roster, manage, scholarships, staff, onboarding, edit, and the revenue/participation/reports/scholarships/attendees reports), plus the nested bulk-payments page. Uses an after_action so it only fires after a successful, authorized render, and skips the lazy Turbo-frame sub-requests so a page counts once. Carries event_id where there is one. - Give the Roster page the attendees-style Hide table / Hide charts toggle, and — the main thing worth borrowing from attendees — lazy-load the demographic charts into their own Turbo frame so the heavier breakdown queries run only when the admin reveals them. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The stat bar and roster table both classify every represented org by facilitator program status. Each classification ran Organization# facilitator_status_on, which fired ~2 affiliations queries per org and was invoked once per org by program_status_counts AND again by program_status_by_organization — ~40 affiliations queries on a populated event. - program_status_counts now derives from program_status_by_organization (classify each org once, not twice). - EventDashboard preloads :affiliations on its organizations, and facilitator_status_on filters the loaded association in Ruby instead of re-querying per org. Net: ~40 affiliations queries -> ~2. (The multi-second page times in the dev log are Vite asset compilation, not the DB — AR time was already ~30ms; this trims the query count that still matters in production.) Added a query-count regression guard. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
TrainingAttendeesRoster/Breakdowns classify each linked org by facilitator program status over a larger (cross-event) population; preload :affiliations on their organizations so facilitator_status_on reads the loaded association instead of a query per org (same fix as the event dashboard). Added a query-count guard on the breakdowns. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…downs} Last vestige of the old 'training attendees' page name — the route, views, frames, and heading are all 'Attendees' now, so the services that back that index should match. Pure rename (files, classes, references, specs, AGENTS.md). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7 tasks
…ents Admins saw the whole org; owners were bounced off an admin-only gate even though the per-event report slice was theirs. Authorize the unfiltered report suite (revenue, participation, scholarships, hub, attendees) for anyone who owns events, and carry the authorization in the rows via a :reportable policy scope rather than trusting the event_id filter — so no filter combination (incl. an event_id[] array) can widen a report past what the viewer may see. Also fixes two review findings on the org program-status classification: - Preload affiliations in person_program_status_ids so facilitator_status_on doesn't fire an affiliations query per org (N+1). - Collapse the decorator's facilitator_status_as_of onto the model's facilitator_status_on (single source of truth), killing the drift-prone twin. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ent reports" Extend event-page Ahoy tracking to the admin preview, the sample-ticket preview and the reminder confirm/send steps. confirm_reminder joins the after_action (guarded so the empty-recipient bounce isn't counted); send_reminder logs inline on a successful send with the recipient count, since it always redirects. Rename the report suite to "Event reports" in the UI — the admin-home card and the report page heading now match. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🤖 suggested review level: 5 Inspect 🔬 multi-phase refactor: event stat services, model scopes, routes, policies, subnav, per-event Roster + cross-event report pages
Phased refactor of the event admin area — consolidating duplicated stats, clarifying naming, and restructuring the per-event nav. Each phase is a self-contained commit.
Subnav (per event)
Dashboard | Manage | Roster | Reports | Scholarships | Staff | Edit event
registrants).EventDashboard; drill-ins → Manage). Owner-visible.event_id. Owner-visible when scoped to their event.What changed (by phase / commit)
Event.live/.on_demand.EventRegistration.attended_facilitator_trainings,Scholarship.externally_funded/.org_subsidized+Grant.self_donated_ids,Affiliation::STATUSES/#status_on/.with_status(SQL↔Ruby agreement spec),Address::US_STATE_ABBREVIATIONS,EventRegistration.status_counts_by_event. One intentional fix: the Manage "funder" filter now treats an AWBW self-donated grant as unfunded, matching the dashboard.EventScholarshipFigures— batched loader replacing the one-EventDashboard-per-event N+1 in the scholarship report.statistics→reports,training_attendees→attendees(frames + files, no redirects). The report suite (revenue, participation, scholarships, hub, attendees) is visible to event owners as well as admins: authorization lives in the rows via anEventPolicy:reportablerelation scope (admin → all events, owner → their own), so no filter combination — including anevent_id[]array — can widen a report past what the viewer may see. An explicitevent_idis still authorized against that event so an unowned one fails loudly.person_program_status_idssofacilitator_status_ondoesn't fire a query per org (N+1); collapse the decorator'sfacilitator_status_as_ofonto the model'sfacilitator_status_on(single source of truth), removing the drift-prone twin.preview,sample_ticket, and the reminderconfirm/sendsteps (send logs inline with a recipient count; confirm skips the empty-recipient bounce). Rename the report suite to "Event reports" in the UI — admin-home card + report page heading now match.Deferred
EventDashboard→RegistrantPopulation/Roster/Breakdownsdecomposition (collapsingTrainingAttendees*). Follow-up.Open
EventRegistration.active(registered, attended, incomplete_attendance, transferred_in). If we should excludeincomplete_attendance, that's a small follow-up (diverges the roster count from the dashboard's "active").Testing
Full suite green except 24 pre-existing
GroupdateMySQL-timezone failures inadmin/ahoy_activities(unrelated to this diff).