Skip to content

Add event participation report + Events reporting page - #2036

Open
maebeale wants to merge 9 commits into
mainfrom
maebeale/events-completed-registrants-totals
Open

Add event participation report + Events reporting page#2036
maebeale wants to merge 9 commits into
mainfrom
maebeale/events-completed-registrants-totals

Conversation

@maebeale

@maebeale maebeale commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

🤖 suggested review level: 5 Inspect 🔬 new cross-event report service with non-additive unique-people aggregation, a new admin page, and registrants drill-down

What is the goal of this PR and why is this important?

  • Adds an Event participation report: how many people completed each training, grouped by calendar year — the people-side sibling of the existing revenue report.
  • "Completed" = fully attended (EventRegistration status "attended"). Headline counts unique people; seats and the status detail are additive registration counts.
  • Adds an Events reporting hub page linked from a single admin-home card, hosting the revenue + participation summaries side by side.

How did you approach the change?

  • New EventParticipationReport service, mirroring EventRevenueReport's year-grouped shape. Counts come from two grouped SQL queries (per-event-per-status, and distinct-people-per-year) — no per-registration Ruby pass.
  • Unique people is not additive: a person attending two trainings in a year is one person trained, so each level (event / year / all-time) runs its own COUNT(DISTINCT registrant_id) rather than summing rows. Seats reveal the repeat attendance.
  • EventsController#participation (admin-only) with an ahoy-style scope select (all events / trainings only) and calendar-year time-period select; view mirrors revenue.html.erb.
  • EventsController#reporting (admin-only) renders both summaries; the admin home links to it via one "Events reporting" card (the two charts moved off the home page).
  • Drill-down: each participation KPI links to the registrants index filtered by attendance status; each per-event outcome links filtered by that event + status. Backed by a new attendance_status param + filter select on the registrants index.
  • Full participation report linked from the events index next to "Revenue report".

Anything else to add?

  • The participation report defaults to all events with a "Trainings only" toggle — confirm that's the intended default population.
  • KPI links filter by status but not year (the registrants index has no year filter); per-event outcome links match exactly (event + status).
  • Screenshots to add for the participation page, the reporting page, and the drill-down.

Copilot AI review requested due to automatic review settings July 31, 2026 21:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@maebeale
maebeale force-pushed the maebeale/events-completed-registrants-totals branch from f1313f2 to 15d5150 Compare July 31, 2026 21:30
Copilot AI review requested due to automatic review settings July 31, 2026 21:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@maebeale
maebeale marked this pull request as ready for review August 2, 2026 02:22
maebeale and others added 2 commits August 1, 2026 22:22
Adds a cross-event participation report (sibling to the revenue report): how
many unique people completed each training, grouped by calendar year, with a
per-status outcome breakdown and an ahoy-style scope/time-period filter.

Unique people is the headline (a person who attends two trainings in a year is
one person trained); seats and the status detail are additive registration
counts, so each aggregation level computes its own distinct-people count rather
than summing rows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Renders the two cross-event report summaries side by side (50% each) at the top
of the admin home, each linking to its full report.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@maebeale
maebeale force-pushed the maebeale/events-completed-registrants-totals branch from 15d5150 to 17ea4a7 Compare August 2, 2026 02:23
Copilot AI review requested due to automatic review settings August 2, 2026 02:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

Suppressed comments (1)

app/models/event.rb:95

  • 🤖 From Copilot: should-fix: Event.in_year will raise (500) if an invalid year is passed (e.g. 0 or a very large number), since Date.new(year) can raise. Guarding here makes the scope safe even if params[:time_period] is tampered with or a stale link passes a bad value.
  # Events whose start date falls in the given calendar year.
  scope :in_year, ->(year) { where(start_date: Date.new(year).all_year) }

…egistrants

Moves the two report summary charts off the admin home onto a dedicated
"Events reporting" page reached from a single admin-home card; the page hosts
the revenue and participation summaries side by side, each linking to its full
report.

Makes the participation report's counts drill down into the registrants index:
each featured-year KPI links to the index filtered by attendance status, and
each per-event outcome links filtered by that event and status. Adds an
attendance-status filter to the registrants index (param + filter select) to
back those links, and orders the per-event outcome grid to read row-major.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 2, 2026 02:38
@maebeale maebeale changed the title Add event participation report (people trained per training) Add event participation report + Events reporting page Aug 2, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated no new comments.

Suppressed comments (1)

app/models/event.rb:95

  • 🤖 From Copilot: should-fix: start_date is a datetime column, so where(start_date: Date.new(year).all_year) can translate to an end bound at YYYY-12-31 00:00:00, excluding events later on Dec 31. Using a time-range in the app time zone avoids off-by-one-day edge cases.
  # Events whose start date falls in the given calendar year.
  scope :in_year, ->(year) { where(start_date: Date.new(year).all_year) }

The revenue and participation full-page eyebrows now return to the Events
reporting hub (their default origin) instead of the events index. Full-page
titles pluralize to "Events revenue" / "Events participation"; the reporting
hub's summary cards drop the redundant "Event" prefix (just "Revenue" /
"Participation").

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 2, 2026 02:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated no new comments.

Suppressed comments (1)

app/models/event.rb:95

  • 🤖 From Copilot: should-fix: Event.in_year uses a Date range against a datetime column (events.start_date), which can exclude events later on Dec 31 because the end of the range casts to midnight. Use a time-range in the app time zone so the whole year is covered.
  # Events whose start date falls in the given calendar year.
  scope :in_year, ->(year) { where(start_date: Date.new(year).all_year) }

The events index linked to the revenue and participation reports separately;
now that the Events reporting hub is the entry point, a single "Events
reporting" button replaces both.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 2, 2026 03:04
maebeale and others added 2 commits August 1, 2026 23:07
Arriving from the events index now returns there (via return_to=events);
arriving from the admin-home card keeps the default "← Admin home" back link.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Widens the participation report's per-event status grid to all seven outcomes
across one row on desktop (two columns on mobile), instead of wrapping to two.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated no new comments.

Suppressed comments (1)

app/views/events/participation.html.erb:27

  • 🤖 From Copilot: should-fix: The participation filter form doesn’t preserve return_to/event_id, so if you arrive from an event dashboard the first filter change drops those params and the eyebrow link stops returning to the dashboard.
    <%= form_with url: participation_events_path, method: :get, local: true,
          class: "flex flex-wrap items-end gap-4 mb-8" do %>
      <div class="flex flex-col gap-1">

Copilot AI review requested due to automatic review settings August 2, 2026 03:08
Going Events → reporting → a full report → back to reporting dropped the
origin, so the reporting eyebrow reverted to "← Admin home". Thread return_to
through: the reporting summaries' "Full report" links carry the origin, each
full report's eyebrow rebuilds the reporting link with it, and the participation
filter form keeps it on re-submit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated no new comments.

Suppressed comments (1)

app/views/event_registrations/_search_boxes.html.erb:40

  • 🤖 From Copilot: should-fix: The attendance status filter labels use status.humanize, which will render e.g. "Incomplete attendance" instead of the app’s established attendance_status_label (e.g. "Partial (1-day)"), leading to inconsistent UI wording; reuse the existing label helper used elsewhere for these statuses.
      <%= label_tag :attendance_status, "Attendance", class: "block text-sm font-medium text-gray-700 mb-1" %>
      <%= select_tag :attendance_status,
                     options_for_select(EventRegistration::ATTENDANCE_STATUSES.map { |status| [ status.humanize, status ] }, params[:attendance_status]),
                     include_blank: "All statuses",

Copilot AI review requested due to automatic review settings August 2, 2026 03:12
The md:grid-cols-7 utility wasn't taking effect (rendered at the grid-cols-2
fallback), so switch to an explicit inline grid-template-columns of seven
columns — the same approach the revenue table uses — so all outcomes sit in
one row reliably.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated no new comments.

Suppressed comments (1)

app/views/event_registrations/_search_boxes.html.erb:41

  • nit: This dropdown builds option labels with status.humanize, while other attendance-status selects use EventRegistration#attendance_status_label; using the shared label method avoids UI-label drift if the mapping ever changes.
      <%= label_tag :attendance_status, "Attendance", class: "block text-sm font-medium text-gray-700 mb-1" %>
      <%= select_tag :attendance_status,
                     options_for_select(EventRegistration::ATTENDANCE_STATUSES.map { |status| [ status.humanize, status ] }, params[:attendance_status]),
                     include_blank: "All statuses",

Copilot AI review requested due to automatic review settings August 2, 2026 03:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants