From 6d2ca217d4a5bb6b8bfd9f969f86979b034a05b6 Mon Sep 17 00:00:00 2001 From: Antonio Obradovic Date: Tue, 21 Apr 2026 14:16:29 +0200 Subject: [PATCH 1/2] Add since/until date filtering to activities endpoint --- app/controllers/activities_controller.rb | 2 + app/models/event.rb | 2 + docs/api/sections/activities.md | 2 + .../controllers/activities_controller_test.rb | 69 +++++++++++++++++++ 4 files changed, 75 insertions(+) diff --git a/app/controllers/activities_controller.rb b/app/controllers/activities_controller.rb index 51ae3de8f0..c24704f303 100644 --- a/app/controllers/activities_controller.rb +++ b/app/controllers/activities_controller.rb @@ -25,6 +25,8 @@ def activities .where(action: ACTIONS) .for_creators(params[:creator_ids]) .for_boards(params[:board_ids]) + .since_date(params[:since]) + .until_date(params[:until]) .reverse_chronologically end end diff --git a/app/models/event.rb b/app/models/event.rb index 23495326d2..54b4074abb 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -12,6 +12,8 @@ class Event < ApplicationRecord 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 :since_date, ->(date) { where("events.created_at >= ?", Date.parse(date).beginning_of_day) if date.present? } + scope :until_date, ->(date) { where("events.created_at <= ?", Date.parse(date).end_of_day) if date.present? } scope :preloaded, -> { includes(:creator, :board, { eventable: [ diff --git a/docs/api/sections/activities.md b/docs/api/sections/activities.md index 33bf1bf70b..3edd0b999c 100644 --- a/docs/api/sections/activities.md +++ b/docs/api/sections/activities.md @@ -12,6 +12,8 @@ __Query Parameters:__ |-----------|-------------| | `creator_ids[]` | Filter to activities created by specific user ID(s). Multiple values are ORed. | | `board_ids[]` | Filter to activities on specific board ID(s). Multiple values are ORed. | +| `since` | Return activities created on or after this date (ISO 8601: `YYYY-MM-DD`). | +| `until` | Return activities created on or before this date (ISO 8601: `YYYY-MM-DD`). | Different filter params are ANDed together: `creator_ids[]=A&board_ids[]=X` means activities created by A on board X. diff --git a/test/controllers/activities_controller_test.rb b/test/controllers/activities_controller_test.rb index 82357e3d9f..58249bf8c2 100644 --- a/test/controllers/activities_controller_test.rb +++ b/test/controllers/activities_controller_test.rb @@ -264,6 +264,75 @@ class ActivitiesControllerTest < ActionDispatch::IntegrationTest assert_equal total, all_ids.uniq.count end + test "index filters by since date" do + card = cards(:logo) + old_event = card.board.events.create!( + action: "card_published", + creator: users(:kevin), + eventable: card, + account: accounts("37s"), + created_at: 2.days.ago + ) + + get activities_path(since: Date.today.iso8601), as: :json + assert_response :success + + ids = @response.parsed_body.map { |e| e["id"] } + assert_not_includes ids, old_event.id + end + + test "index filters by until date" do + card = cards(:logo) + future_event = card.board.events.create!( + action: "card_published", + creator: users(:kevin), + eventable: card, + account: accounts("37s"), + created_at: 2.days.from_now + ) + + get activities_path(until: Date.today.iso8601), as: :json + assert_response :success + + ids = @response.parsed_body.map { |e| e["id"] } + assert_not_includes ids, future_event.id + end + + test "index filters by since and until together" do + card = cards(:logo) + board = card.board + + old_event = board.events.create!( + action: "card_published", + creator: users(:kevin), + eventable: card, + account: accounts("37s"), + created_at: 5.days.ago + ) + matching_event = board.events.create!( + action: "card_published", + creator: users(:kevin), + eventable: card, + account: accounts("37s"), + created_at: 2.days.ago + ) + future_event = board.events.create!( + action: "card_published", + creator: users(:kevin), + eventable: card, + account: accounts("37s"), + created_at: 2.days.from_now + ) + + get activities_path(since: 3.days.ago.to_date.iso8601, until: Date.today.iso8601), as: :json + assert_response :success + + ids = @response.parsed_body.map { |e| e["id"] } + assert_includes ids, matching_event.id + assert_not_includes ids, old_event.id + assert_not_includes ids, future_event.id + end + private def next_page_from_link_header(link_header) url = link_header&.match(/<([^>]+)>;\s*rel="next"/)&.captures&.first From 295ea43bfe425c74a15007b2e79c1f8fd67a46d5 Mon Sep 17 00:00:00 2001 From: Antonio Obradovic Date: Tue, 21 Apr 2026 14:41:05 +0200 Subject: [PATCH 2/2] Handle invalid dates and strengthen date filter tests --- app/models/event.rb | 4 +-- .../controllers/activities_controller_test.rb | 36 ++++++++++++------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/app/models/event.rb b/app/models/event.rb index 54b4074abb..9fb8352c3b 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -12,8 +12,8 @@ class Event < ApplicationRecord 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 :since_date, ->(date) { where("events.created_at >= ?", Date.parse(date).beginning_of_day) if date.present? } - scope :until_date, ->(date) { where("events.created_at <= ?", Date.parse(date).end_of_day) if date.present? } + scope :since_date, ->(date) { where("events.created_at >= ?", Date.iso8601(date).beginning_of_day) if date.present? rescue nil } + scope :until_date, ->(date) { where("events.created_at <= ?", Date.iso8601(date).end_of_day) if date.present? rescue nil } scope :preloaded, -> { includes(:creator, :board, { eventable: [ diff --git a/test/controllers/activities_controller_test.rb b/test/controllers/activities_controller_test.rb index 58249bf8c2..72c8116dbb 100644 --- a/test/controllers/activities_controller_test.rb +++ b/test/controllers/activities_controller_test.rb @@ -266,35 +266,47 @@ class ActivitiesControllerTest < ActionDispatch::IntegrationTest test "index filters by since date" do card = cards(:logo) - old_event = card.board.events.create!( - action: "card_published", - creator: users(:kevin), - eventable: card, - account: accounts("37s"), + board = card.board + + old_event = board.events.create!( + action: "card_published", creator: users(:kevin), + eventable: card, account: accounts("37s"), created_at: 2.days.ago ) + matching_event = board.events.create!( + action: "card_published", creator: users(:kevin), + eventable: card, account: accounts("37s"), + created_at: 12.hours.ago + ) - get activities_path(since: Date.today.iso8601), as: :json + get activities_path(since: Date.current.iso8601), as: :json assert_response :success ids = @response.parsed_body.map { |e| e["id"] } + assert_includes ids, matching_event.id assert_not_includes ids, old_event.id end test "index filters by until date" do card = cards(:logo) - future_event = card.board.events.create!( - action: "card_published", - creator: users(:kevin), - eventable: card, - account: accounts("37s"), + board = card.board + + matching_event = board.events.create!( + action: "card_published", creator: users(:kevin), + eventable: card, account: accounts("37s"), + created_at: 12.hours.ago + ) + future_event = board.events.create!( + action: "card_published", creator: users(:kevin), + eventable: card, account: accounts("37s"), created_at: 2.days.from_now ) - get activities_path(until: Date.today.iso8601), as: :json + get activities_path(until: Date.current.iso8601), as: :json assert_response :success ids = @response.parsed_body.map { |e| e["id"] } + assert_includes ids, matching_event.id assert_not_includes ids, future_event.id end