Add JSON activities API endpoint (#2783)

* Add JSON events API endpoint

* Add regression test for event particulars defaults

* Move JSON events API to a dedicated ActivitiesController

The events endpoint served both the HTML day timeline and the JSON API
feed, but the two paths shared no data or behavior — the HTML side uses
DayTimelinesScoped while the JSON side built its own query. Splitting
into ActivitiesController gives the API its own home at
GET /:account/activities.json without dragging in the timeline
before_actions.

Also preloads comment creator in Event.preloaded to avoid an N+1 when
rendering comment eventables in the JSON feed.
This commit is contained in:
Rob Zolkos
2026-04-08 08:39:36 -04:00
committed by GitHub
parent 2a4adf3a2b
commit 6a71856b3d
10 changed files with 561 additions and 1 deletions
+30
View File
@@ -0,0 +1,30 @@
class ActivitiesController < ApplicationController
ACTIONS = %w[
card_assigned
card_auto_postponed
card_board_changed
card_closed
card_postponed
card_published
card_reopened
card_sent_back_to_triage
card_title_changed
card_triaged
card_unassigned
comment_created
].freeze
def index
set_page_and_extract_portion_from(activities)
end
private
def activities
Current.user.accessible_events
.preloaded
.where(action: ACTIONS)
.for_creators(params[:creator_ids])
.for_boards(params[:board_ids])
.reverse_chronologically
end
end
+4 -1
View File
@@ -9,10 +9,13 @@ class Event < ApplicationRecord
has_many :webhook_deliveries, class_name: "Webhook::Delivery", dependent: :delete_all
scope :chronologically, -> { order created_at: :asc, id: :desc }
scope :reverse_chronologically, -> { order created_at: :desc, id: :desc }
scope :for_creators, ->(ids) { where(creator_id: ids) if ids.present? }
scope :for_boards, ->(ids) { where(board_id: ids) if ids.present? }
scope :preloaded, -> {
includes(:creator, :board, {
eventable: [
:goldness, :closure, :image_attachment,
:creator, :goldness, :closure, :image_attachment,
{ rich_text_body: :embeds_attachments },
{ rich_text_description: :embeds_attachments },
{ card: [ :goldness, :closure, :image_attachment ] }
+17
View File
@@ -8,4 +8,21 @@ module Event::Particulars
def assignees
@assignees ||= User.where id: assignee_ids
end
def api_particulars
nested = particulars.dig("particulars") || {}
case action.to_s
when "card_assigned", "card_unassigned"
{ "assignee_ids" => Array(assignee_ids) }
when "card_board_changed"
{ "old_board" => nested["old_board"].to_s, "new_board" => nested["new_board"].to_s }
when "card_title_changed"
{ "old_title" => nested["old_title"].to_s, "new_title" => nested["new_title"].to_s }
when "card_triaged"
{ "column" => nested["column"].to_s }
else
{}
end
end
end
@@ -0,0 +1,21 @@
json.(event, :id, :action)
json.created_at event.created_at.utc
json.description event.description_for(Current.user).to_plain_text
json.particulars event.api_particulars
json.url(
case event.eventable
when Comment then card_url(event.eventable.card, anchor: dom_id(event.eventable))
else polymorphic_url(event.eventable)
end
)
json.eventable_type event.eventable_type
json.eventable do
case event.eventable
when Card then json.partial! "cards/card", card: event.eventable
when Comment then json.partial! "cards/comments/comment", comment: event.eventable
end
end
json.board event.board, partial: "boards/board", as: :board
json.creator event.creator, partial: "users/user", as: :user
+1
View File
@@ -0,0 +1 @@
json.array! @page.records, partial: "activities/activity", as: :event