Consolidate event roster/breakdown data sources into a population-parameterized service
Maintainer thường phản hồi trong vòng 1 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 5/5
- Thời gian dự kiến
- Hơn một tuần
- Mức phù hợp với người mới
- 35/100
Hướng nghiên cứu
Bắt đầu với app/services/event_dashboard.rb, app/services/attendees_roster.rb và app/services/attendees_breakdowns.rb, sau đó lần theo EventsController#roster và #attendees đến các partial roster và breakdown dùng chung. Chạy các request/view spec cho Roster theo từng sự kiện và Attendees giữa các sự kiện trước khi thay đổi hành vi. Được xem là hoàn tất khi các service trùng lặp đã được xóa, cả hai context render giống hệt nhau, và các service population, roster và breakdown mới dùng chung một implementation.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
🤖 From Claude: filed at the maintainer's request as a follow-up to the events-IA refactor (PR #2113).
Summary
The per-event Roster page and the cross-event Attendees index render the same shared partials (app/views/events/_registrant_roster.html.erb, app/views/events/_registrant_breakdowns.html.erb) but are fed by two different data sources that each re-implement the same roster/breakdown method surface over a different population:
EventDashboard— one event's active registrants (registered + attended + incomplete_attendance + transferred_in), anchored to the event date.AttendeesRoster/AttendeesBreakdowns— the cross-event population of people who attended a facilitator training, anchored to "now".
This is the deferred "Phase 4" of the IA work. The goal is to collapse the duplicated roster/breakdown logic behind a single population-parameterized service so there's one implementation, differing only in which people, via which registrations, as of when.
Why
- Duplication / drift risk.
AttendeesRoster/AttendeesBreakdownswere deliberately written to mirrorEventDashboard's method names and return shapes so the shared partials read either one. Any change to a breakdown (a new dimension, a bug fix, a query optimization) must be made in two places or they silently diverge. The recent affiliations-N+1 fix, for example, had to be applied toEventDashboardand bothAttendees*services separately. EventDashboardis a god object (~1,300 lines, ~120 public methods) doing money, attendance, scholarships, roster maps, and breakdown datasets. The roster/breakdown concern is cleanly separable.- The "which people / as of when" difference is currently implicit and hand-rolled in each service, rather than an explicit, testable property.
Current state (files)
app/services/event_dashboard.rb— roster maps (registrants,organization_ids_by_registrant,primary_sector_names_by_registrant,scholarship_by_recipient,program_statuses_by_registrant,location_label_by_registrant,ce_registration_by_registrant, …) and breakdown datasets (primary_sectors/sector_counts,age_groups,state_counts,country_counts,program_status_counts/program_status_by_organization/program_status_registrant_ids,life_experiences,settings,registrant_city_breakdown,organizations, scholarship/CE pies, and the*_registrant_ids_by_*drill-in maps).app/services/attendees_roster.rb— the roster-map subset over apeoplecollection (attended-training registrations).app/services/attendees_breakdowns.rb— the breakdown-dataset subset over apeoplecollection.- Shared consumers:
_registrant_roster.html.erb,_registrant_breakdowns.html.erb,_breakdown_card.html.erb,_registrant_city_row.html.erb,_registrant_city_breakdown.html.erb. - Controller:
EventsController#roster(feedsEventDashboard,context: :event) andEventsController#attendees(feedsAttendees*,context: :index).
Proposed design
Introduce three POROs (names from the original plan; open to alternatives):
RegistrantPopulation— the single source of "which people, via which registrations, as of when." Constructors for each context, e.g.RegistrantPopulation.for_event(event)→ the event's active registrations, reference_date = event start.RegistrantPopulation.attended_trainings(people_scope)→ attended facilitator-training registrations, reference_date = today.
Owns the shared, date-anchored primitives currently duplicated:city_by_organization,program_status_by_organization, org/person id maps, the reference-date anchoring, and the org-affiliation preload.
RegistrantRoster.new(population)— every per-registrant lookup map_registrant_rosterreads. ReplacesAttendeesRoster+EventDashboard's roster maps.RegistrantBreakdowns.new(population)— every chart dataset +*_registrant_idsdrill-in map_registrant_breakdownsreads (including the pie denominators currently computed in ERB). ReplacesAttendeesBreakdowns+EventDashboard's breakdown methods.
EventDashboard keeps money / attendance / scholarship-money / applicants / shout-outs, composes a RegistrantPopulation.for_event, and delegates the roster/breakdown method names so dashboard.html.erb / recipients.html.erb / roster.html.erb don't change.
Tasks / scope
- Build
RegistrantPopulationwith both constructors and the shared date-anchored primitives; unit-spec the two anchorings explicitly (the reference-date behavior is currently only implicit). - Build
RegistrantRoster+RegistrantBreakdownsover a population; move the pie-denominator math out of_registrant_breakdowns.html.erbintoRegistrantBreakdowns. - Point
EventsController#attendeesat the new services; haveEventDashboardcompose + delegate. - Delete
AttendeesRoster/AttendeesBreakdowns. - Carry over the org-affiliation preload (the N+1 fix from PR #2113) into
RegistrantPopulationso it lives in one place. - Port the two deleted services' specs to the new names; add per-event-population examples (reference-date anchoring).
- Update
AGENTS.mdservices section.
Risks / notes
- Behavior-preserving refactor. The per-event Roster and cross-event Attendees request/view specs are the safety net — their output must be unchanged. This is the main reason it was deferred:
EventDashboardis large and the roster/breakdown extraction touches many methods. - Watch the subtle sourcing difference already encoded today: on the per-event Roster the scholarship/CE/org columns come from this event's registrations; on the cross-event index they come from each person's attended-training registrations. The population abstraction must make "which registrations" an explicit property so both are expressed without branching in the views.
- The
context: :eventvscontext: :indexdrill-in branching in_registrant_breakdowns.html.erbcan likely be simplified once the population owns the id maps, but that's optional polish.
Out of scope
- No DB/schema changes.
- No UI/behavior changes — pure internal consolidation.
- No policy changes.
Acceptance criteria
AttendeesRosterandAttendeesBreakdownsare gone; one roster service + one breakdown service back both the per-event Roster and the cross-event Attendees index.- The per-event Roster, cross-event Attendees, dashboard, and recipients pages render identically (existing request/view specs pass unchanged).
- Adding a new breakdown dimension requires editing exactly one place.
References
- PR #2113 (events IA refactor) — where these services were renamed and the N+1 was fixed in triplicate (the motivating pain).
- Ngôn ngữ chính
- Ruby
- Star
- 15
- Fork
- 28
- Merge trung bình
- 16 giờ 58 phút
- Pull request đã merge (30 ngày)
- 291
Chuẩn bị môi trường
Khởi chạy dev container của dự án ngay trên trình duyệt, bằng tài khoản GitHub của bạn.
- Có Dockerfile hoặc tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của rubyforgood/awbw
-
blocked
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
rubyforgood/awbw#2824 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
rubyforgood/awbw#2822 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
rubyforgood/awbw#2160 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Update callout faq defualt text when the new person subscription launchesCó thể đã có người làm @maebeale đã nhận 85 ngày trước. Đang mởblocked
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
rubyforgood/awbw#2095 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 84/100
rubyforgood/awbw#2088 ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của rubyforgood/awbw
Issue tương tự
-
JWT::EncodedToken raises TypeError for a non-object headerCó thể đã có người làm @anakinj đã nhận hôm nay. Đang mởbug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
jwt/ruby-jwt#780 · 1 reaction ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 82/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
zold-io/zold-ruby-sdk#85 ·
-
Độ khó 1/5 1-3 giờ Mức phù hợp với người mới 62/100