Skip to content
22 changes: 20 additions & 2 deletions app/controllers/notifications/trays_controller.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
class Notifications::TraysController < ApplicationController
MAX_ENTRIES_LIMIT = 100

def show
@notifications = Current.user.notifications.preloaded.unread.ordered.limit(100)
@notifications = unread_notifications
if include_unread?
Copy link

@alpkeser alpkeser Jan 29, 2026

Choose a reason for hiding this comment

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

@svara added this query parameter instead of checking for format.json. Let me know if you have any concerns.

@notifications += read_notifications
end

# Invalidate on the whole set instead of the unread set since the max updated at in the unread set
# can stay the same when reading old notifications.
fresh_when Current.user.notifications
fresh_when etag: [ Current.user.notifications, include_unread? ]
end

private
def unread_notifications
Current.user.notifications.preloaded.unread.ordered.limit(MAX_ENTRIES_LIMIT)
end

def read_notifications
Current.user.notifications.preloaded.read.ordered.limit(MAX_ENTRIES_LIMIT)
end

def include_unread?
ActiveModel::Type::Boolean.new.cast(params[:include_unread])
end
end
4 changes: 3 additions & 1 deletion app/views/notifications/_notification.json.jbuilder
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ json.cache! notification do
json.creator notification.creator, partial: "users/user", as: :user

json.card do
json.(notification.card, :id, :title, :status)
json.(notification.card, :id, :number, :title, :status)
json.board_name notification.card.board.name
json.url card_url(notification.card)
json.column notification.card.column, partial: "columns/column", as: :column if notification.card.column
end

json.url notification_url(notification)
Expand Down
1 change: 1 addition & 0 deletions app/views/notifications/trays/show.json.jbuilder
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
json.array! @notifications, partial: "notifications/notification", as: :notification
1 change: 1 addition & 0 deletions app/views/users/_user.json.jbuilder
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ json.cache! user do
json.created_at user.created_at.utc

json.url user_url(user)
json.avatar_url user_avatar_url(user)
end
20 changes: 20 additions & 0 deletions test/controllers/notifications/trays_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,24 @@ class Notifications::TraysControllerTest < ActionDispatch::IntegrationTest
assert_response :success
assert_select "div", text: /Layout is broken/
end

test "show as JSON" do
expected_ids = users(:kevin).notifications.unread.ordered.limit(100).pluck(:id)

get tray_notifications_path(format: :json)

assert_response :success
assert_equal expected_ids, @response.parsed_body.map { |notification| notification["id"] }
end

test "show as JSON with include_unread includes read notifications" do
notifications = users(:kevin).notifications
expected_ids = notifications.unread.ordered.limit(100).pluck(:id) +
notifications.read.ordered.limit(100).pluck(:id)

get tray_notifications_path(format: :json, include_unread: true)

assert_response :success
assert_equal expected_ids, @response.parsed_body.map { |notification| notification["id"] }
end
end
4 changes: 4 additions & 0 deletions test/controllers/notifications_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,9 @@ class NotificationsControllerTest < ActionDispatch::IntegrationTest
assert_not_nil notification["created_at"]
assert_not_nil notification["card"]
assert_not_nil notification["creator"]
assert_not_nil notification.dig("creator", "avatar_url")
assert_not_nil notification.dig("card", "number")
assert_not_nil notification.dig("card", "board_name")
assert_not_nil notification.dig("card", "column")
end
end