From 393ab69c34157c01473ca2948d247c94c71136b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20=C5=A0vara?= Date: Mon, 19 Jan 2026 13:51:14 +0100 Subject: [PATCH 1/9] Add JSON response support for `notifications/tray`. --- app/views/notifications/trays/show.json.jbuilder | 1 + test/controllers/notifications/trays_controller_test.rb | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 app/views/notifications/trays/show.json.jbuilder diff --git a/app/views/notifications/trays/show.json.jbuilder b/app/views/notifications/trays/show.json.jbuilder new file mode 100644 index 0000000000..0c2928cef7 --- /dev/null +++ b/app/views/notifications/trays/show.json.jbuilder @@ -0,0 +1 @@ +json.array! @notifications, partial: "notifications/notification", as: :notification diff --git a/test/controllers/notifications/trays_controller_test.rb b/test/controllers/notifications/trays_controller_test.rb index 07034fff0d..d0916e305d 100644 --- a/test/controllers/notifications/trays_controller_test.rb +++ b/test/controllers/notifications/trays_controller_test.rb @@ -11,4 +11,13 @@ 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 end From 8849e095eaa7f9784f2b0265bcae726d4091023d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20=C5=A0vara?= Date: Mon, 19 Jan 2026 14:00:26 +0100 Subject: [PATCH 2/9] Extend notification JSON payload. Add avatar url, board name, and column. --- app/views/notifications/_notification.json.jbuilder | 9 +++++++-- test/controllers/notifications_controller_test.rb | 4 ++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/views/notifications/_notification.json.jbuilder b/app/views/notifications/_notification.json.jbuilder index ba27c5425e..5a28e1bea5 100644 --- a/app/views/notifications/_notification.json.jbuilder +++ b/app/views/notifications/_notification.json.jbuilder @@ -6,11 +6,16 @@ json.cache! notification do json.partial! "notifications/notification/#{notification.source_type.underscore}/body", notification: notification - json.creator notification.creator, partial: "users/user", as: :user + json.creator do + json.partial! "users/user", user: notification.creator + json.avatar_url user_avatar_url(notification.creator) + end 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) diff --git a/test/controllers/notifications_controller_test.rb b/test/controllers/notifications_controller_test.rb index e4e7bec38a..3b2223ae43 100644 --- a/test/controllers/notifications_controller_test.rb +++ b/test/controllers/notifications_controller_test.rb @@ -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 From 1ca52a0ed221251107103dffd3abc9d51b7cfe18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20=C5=A0vara?= Date: Mon, 19 Jan 2026 16:57:12 +0100 Subject: [PATCH 3/9] Add `avatar_url` to the shared user JSON partial. --- app/views/users/_user.json.jbuilder | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/users/_user.json.jbuilder b/app/views/users/_user.json.jbuilder index 0884f225ab..ed05bc67f4 100644 --- a/app/views/users/_user.json.jbuilder +++ b/app/views/users/_user.json.jbuilder @@ -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 From cc42534fcf99145f7ea7f4cc903960dc52e4257e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20=C5=A0vara?= Date: Mon, 19 Jan 2026 17:00:14 +0100 Subject: [PATCH 4/9] Use the existing user partial on notifications. --- app/views/notifications/_notification.json.jbuilder | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/views/notifications/_notification.json.jbuilder b/app/views/notifications/_notification.json.jbuilder index 5a28e1bea5..5a6e604c7b 100644 --- a/app/views/notifications/_notification.json.jbuilder +++ b/app/views/notifications/_notification.json.jbuilder @@ -6,10 +6,7 @@ json.cache! notification do json.partial! "notifications/notification/#{notification.source_type.underscore}/body", notification: notification - json.creator do - json.partial! "users/user", user: notification.creator - json.avatar_url user_avatar_url(notification.creator) - end + json.creator notification.creator, partial: "users/user", as: :user json.card do json.(notification.card, :id, :number, :title, :status) From d59fa7b7fc5af6c19a0a7d9a79f094f84ee508bd Mon Sep 17 00:00:00 2001 From: Fernando Olivares Date: Thu, 22 Jan 2026 03:00:17 -0600 Subject: [PATCH 5/9] Add has_attachments to notification card JSON Co-Authored-By: Claude Opus 4.5 --- app/views/notifications/_notification.json.jbuilder | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/notifications/_notification.json.jbuilder b/app/views/notifications/_notification.json.jbuilder index 5a6e604c7b..7f4f87adb9 100644 --- a/app/views/notifications/_notification.json.jbuilder +++ b/app/views/notifications/_notification.json.jbuilder @@ -11,6 +11,7 @@ json.cache! notification do json.card do json.(notification.card, :id, :number, :title, :status) json.board_name notification.card.board.name + json.has_attachments notification.card.has_attachments? json.url card_url(notification.card) json.column notification.card.column, partial: "columns/column", as: :column if notification.card.column end From ba7ce06fc34c698c635ccacd761efe64c61fc364 Mon Sep 17 00:00:00 2001 From: Fernando Olivares Date: Thu, 22 Jan 2026 03:13:31 -0600 Subject: [PATCH 6/9] Add card assignees to notification JSON Includes assignee users in the card object for display in notification UI. Co-Authored-By: Claude Opus 4.5 --- app/views/notifications/_notification.json.jbuilder | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/notifications/_notification.json.jbuilder b/app/views/notifications/_notification.json.jbuilder index 7f4f87adb9..e4ff13f396 100644 --- a/app/views/notifications/_notification.json.jbuilder +++ b/app/views/notifications/_notification.json.jbuilder @@ -12,6 +12,7 @@ json.cache! notification do json.(notification.card, :id, :number, :title, :status) json.board_name notification.card.board.name json.has_attachments notification.card.has_attachments? + json.assignees notification.card.assignees, partial: "users/user", as: :user json.url card_url(notification.card) json.column notification.card.column, partial: "columns/column", as: :column if notification.card.column end From 9d1395d2bd5ada898ce6fb2194a372db49af56bc Mon Sep 17 00:00:00 2001 From: Fernando Olivares Date: Thu, 22 Jan 2026 03:31:23 -0600 Subject: [PATCH 7/9] Remove unused has_attachments and assignees from notification JSON These fields were added but are not used by the iOS client. Co-Authored-By: Claude Opus 4.5 --- app/views/notifications/_notification.json.jbuilder | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/views/notifications/_notification.json.jbuilder b/app/views/notifications/_notification.json.jbuilder index e4ff13f396..5a6e604c7b 100644 --- a/app/views/notifications/_notification.json.jbuilder +++ b/app/views/notifications/_notification.json.jbuilder @@ -11,8 +11,6 @@ json.cache! notification do json.card do json.(notification.card, :id, :number, :title, :status) json.board_name notification.card.board.name - json.has_attachments notification.card.has_attachments? - json.assignees notification.card.assignees, partial: "users/user", as: :user json.url card_url(notification.card) json.column notification.card.column, partial: "columns/column", as: :column if notification.card.column end From a3aefba9849b6aaab17521e7d7838fd1703744f8 Mon Sep 17 00:00:00 2001 From: Alp Keser Date: Wed, 28 Jan 2026 16:43:39 +0300 Subject: [PATCH 8/9] Include read notifications in the tray API response --- .../notifications/trays_controller.rb | 20 ++++++++++++++++++- .../notifications/trays_controller_test.rb | 11 ++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/app/controllers/notifications/trays_controller.rb b/app/controllers/notifications/trays_controller.rb index c2bac662ad..9c0ee4505f 100644 --- a/app/controllers/notifications/trays_controller.rb +++ b/app/controllers/notifications/trays_controller.rb @@ -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? + @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 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 diff --git a/test/controllers/notifications/trays_controller_test.rb b/test/controllers/notifications/trays_controller_test.rb index d0916e305d..57e2997e90 100644 --- a/test/controllers/notifications/trays_controller_test.rb +++ b/test/controllers/notifications/trays_controller_test.rb @@ -20,4 +20,15 @@ class Notifications::TraysControllerTest < ActionDispatch::IntegrationTest 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 From d954ec960655dfe5a376b10b1b5fee7c144d1332 Mon Sep 17 00:00:00 2001 From: Alp Keser Date: Thu, 29 Jan 2026 10:18:20 +0300 Subject: [PATCH 9/9] Add include_unread param to tray API and vary cache --- app/controllers/notifications/trays_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/notifications/trays_controller.rb b/app/controllers/notifications/trays_controller.rb index 9c0ee4505f..fbb24000b8 100644 --- a/app/controllers/notifications/trays_controller.rb +++ b/app/controllers/notifications/trays_controller.rb @@ -9,7 +9,7 @@ def show # 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