mirror of https://github.com/tootsuite/mastodon
Fix last paginated notification group only including data on a single notification
parent
ad68686bec
commit
74ff263f5d
app
controllers/api/v2
models
spec/requests/api/v2
|
@ -80,7 +80,19 @@ class Api::V2::NotificationsController < Api::BaseController
|
||||||
return [] if @notifications.empty?
|
return [] if @notifications.empty?
|
||||||
|
|
||||||
MastodonOTELTracer.in_span('Api::V2::NotificationsController#load_grouped_notifications') do
|
MastodonOTELTracer.in_span('Api::V2::NotificationsController#load_grouped_notifications') do
|
||||||
NotificationGroup.from_notifications(@notifications, pagination_range: (@notifications.last.id)..(@notifications.first.id), grouped_types: params[:grouped_types])
|
pagination_range = (@notifications.last.id)..@notifications.first.id
|
||||||
|
|
||||||
|
if @notifications.size < limit_param(DEFAULT_NOTIFICATIONS_LIMIT)
|
||||||
|
if params[:min_id].present?
|
||||||
|
pagination_range = @notifications.last.id...(params[:max_id]&.to_i)
|
||||||
|
else
|
||||||
|
range_start = params[:since_id]&.to_i
|
||||||
|
range_start += 1 unless range_start.nil?
|
||||||
|
pagination_range = range_start..(@notifications.first.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
NotificationGroup.from_notifications(@notifications, pagination_range: pagination_range, grouped_types: params[:grouped_types])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -64,21 +64,31 @@ class NotificationGroup < ActiveModelSerializers::Model
|
||||||
binds = [
|
binds = [
|
||||||
account_id,
|
account_id,
|
||||||
SAMPLE_ACCOUNTS_SIZE,
|
SAMPLE_ACCOUNTS_SIZE,
|
||||||
pagination_range.begin,
|
|
||||||
pagination_range.end,
|
|
||||||
ActiveRecord::Relation::QueryAttribute.new('group_keys', group_keys, ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.new(ActiveModel::Type::String.new)),
|
ActiveRecord::Relation::QueryAttribute.new('group_keys', group_keys, ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.new(ActiveModel::Type::String.new)),
|
||||||
|
pagination_range.begin || 0,
|
||||||
]
|
]
|
||||||
|
binds << pagination_range.end unless pagination_range.end.nil?
|
||||||
|
|
||||||
|
upper_bound_cond = begin
|
||||||
|
if pagination_range.end.nil?
|
||||||
|
''
|
||||||
|
elsif pagination_range.exclude_end?
|
||||||
|
'AND id < $5'
|
||||||
|
else
|
||||||
|
'AND id <= $5'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
ActiveRecord::Base.connection.select_all(<<~SQL.squish, 'grouped_notifications', binds).cast_values.to_h { |k, *values| [k, values] }
|
ActiveRecord::Base.connection.select_all(<<~SQL.squish, 'grouped_notifications', binds).cast_values.to_h { |k, *values| [k, values] }
|
||||||
SELECT
|
SELECT
|
||||||
groups.group_key,
|
groups.group_key,
|
||||||
(SELECT id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key AND id <= $4 ORDER BY id DESC LIMIT 1),
|
(SELECT id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key #{upper_bound_cond} ORDER BY id DESC LIMIT 1),
|
||||||
array(SELECT from_account_id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key AND id <= $4 ORDER BY id DESC LIMIT $2),
|
array(SELECT from_account_id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key #{upper_bound_cond} ORDER BY id DESC LIMIT $2),
|
||||||
(SELECT count(*) FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key AND id <= $4) AS notifications_count,
|
(SELECT count(*) FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key #{upper_bound_cond}) AS notifications_count,
|
||||||
(SELECT id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key AND id >= $3 ORDER BY id ASC LIMIT 1) AS min_id,
|
(SELECT id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key AND id >= $4 ORDER BY id ASC LIMIT 1) AS min_id,
|
||||||
(SELECT created_at FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key AND id <= $4 ORDER BY id DESC LIMIT 1)
|
(SELECT created_at FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key #{upper_bound_cond} ORDER BY id DESC LIMIT 1)
|
||||||
FROM
|
FROM
|
||||||
unnest($5::text[]) AS groups(group_key);
|
unnest($3::text[]) AS groups(group_key);
|
||||||
SQL
|
SQL
|
||||||
else
|
else
|
||||||
binds = [
|
binds = [
|
||||||
|
|
|
@ -143,6 +143,55 @@ RSpec.describe 'Notifications' do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when there are numerous notifications for the same final group' do
|
||||||
|
before do
|
||||||
|
user.account.notifications.destroy_all
|
||||||
|
5.times.each { FavouriteService.new.call(Fabricate(:account), user.account.statuses.first) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with no options' do
|
||||||
|
it 'returns a notification group covering all notifications' do
|
||||||
|
subject
|
||||||
|
|
||||||
|
notification_ids = user.account.notifications.reload.pluck(:id)
|
||||||
|
|
||||||
|
expect(response).to have_http_status(200)
|
||||||
|
expect(response.content_type)
|
||||||
|
.to start_with('application/json')
|
||||||
|
expect(response.parsed_body[:notification_groups]).to contain_exactly(
|
||||||
|
a_hash_including(
|
||||||
|
type: 'favourite',
|
||||||
|
sample_account_ids: have_attributes(size: 5),
|
||||||
|
page_min_id: notification_ids.first.to_s,
|
||||||
|
page_max_id: notification_ids.last.to_s
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with min_id param' do
|
||||||
|
let(:params) { { min_id: user.account.notifications.reload.first.id - 1 } }
|
||||||
|
|
||||||
|
it 'returns a notification group covering all notifications' do
|
||||||
|
subject
|
||||||
|
|
||||||
|
notification_ids = user.account.notifications.reload.pluck(:id)
|
||||||
|
|
||||||
|
expect(response).to have_http_status(200)
|
||||||
|
expect(response.content_type)
|
||||||
|
.to start_with('application/json')
|
||||||
|
expect(response.parsed_body[:notification_groups]).to contain_exactly(
|
||||||
|
a_hash_including(
|
||||||
|
type: 'favourite',
|
||||||
|
sample_account_ids: have_attributes(size: 5),
|
||||||
|
page_min_id: notification_ids.first.to_s,
|
||||||
|
page_max_id: notification_ids.last.to_s
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'with no options' do
|
context 'with no options' do
|
||||||
it 'returns expected notification types', :aggregate_failures do
|
it 'returns expected notification types', :aggregate_failures do
|
||||||
subject
|
subject
|
||||||
|
|
Loading…
Reference in New Issue