Add fallback attributes to notifications for new and infrequent notifications (#38832)
This commit is contained in:
parent
afeb63d287
commit
5b395774c0
@ -16,7 +16,7 @@ class Api::V1::NotificationsController < Api::BaseController
|
|||||||
@relationships = StatusRelationshipsPresenter.new(target_statuses_from_notifications, current_user&.account_id)
|
@relationships = StatusRelationshipsPresenter.new(target_statuses_from_notifications, current_user&.account_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
render json: @notifications, each_serializer: REST::NotificationSerializer, relationships: @relationships
|
render json: @notifications, each_serializer: REST::NotificationSerializer, relationships: @relationships, supported_notification_types: params[:supported_types]
|
||||||
end
|
end
|
||||||
|
|
||||||
def unread_count
|
def unread_count
|
||||||
@ -29,7 +29,7 @@ class Api::V1::NotificationsController < Api::BaseController
|
|||||||
|
|
||||||
def show
|
def show
|
||||||
@notification = current_account.notifications.without_suspended.find(params[:id])
|
@notification = current_account.notifications.without_suspended.find(params[:id])
|
||||||
render json: @notification, serializer: REST::NotificationSerializer
|
render json: @notification, serializer: REST::NotificationSerializer, supported_notification_types: params[:supported_types]
|
||||||
end
|
end
|
||||||
|
|
||||||
def clear
|
def clear
|
||||||
@ -89,6 +89,8 @@ class Api::V1::NotificationsController < Api::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def pagination_params(core_params)
|
def pagination_params(core_params)
|
||||||
params.slice(:limit, :account_id, :types, :exclude_types, :include_filtered).permit(:limit, :account_id, :include_filtered, types: [], exclude_types: []).merge(core_params)
|
params.slice(:limit, :account_id, :types, :exclude_types, :include_filtered, :supported_types)
|
||||||
|
.permit(:limit, :account_id, :include_filtered, types: [], exclude_types: [], supported_types: [])
|
||||||
|
.merge(core_params)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -33,7 +33,7 @@ class Api::V2::NotificationsController < Api::BaseController
|
|||||||
'app.notification_grouping.expand_accounts_param' => expand_accounts_param
|
'app.notification_grouping.expand_accounts_param' => expand_accounts_param
|
||||||
)
|
)
|
||||||
|
|
||||||
render json: @presenter, serializer: REST::DedupNotificationGroupSerializer, relationships: @relationships, expand_accounts: expand_accounts_param
|
render json: @presenter, serializer: REST::DedupNotificationGroupSerializer, relationships: @relationships, expand_accounts: expand_accounts_param, supported_notification_types: params[:supported_types]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ class Api::V2::NotificationsController < Api::BaseController
|
|||||||
def show
|
def show
|
||||||
@notification = current_account.notifications.without_suspended.by_group_key(params[:group_key]).take!
|
@notification = current_account.notifications.without_suspended.by_group_key(params[:group_key]).take!
|
||||||
presenter = GroupedNotificationsPresenter.new(NotificationGroup.from_notifications([@notification]))
|
presenter = GroupedNotificationsPresenter.new(NotificationGroup.from_notifications([@notification]))
|
||||||
render json: presenter, serializer: REST::DedupNotificationGroupSerializer
|
render json: presenter, serializer: REST::DedupNotificationGroupSerializer, supported_notification_types: params[:supported_types]
|
||||||
end
|
end
|
||||||
|
|
||||||
def clear
|
def clear
|
||||||
@ -138,7 +138,9 @@ class Api::V2::NotificationsController < Api::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def pagination_params(core_params)
|
def pagination_params(core_params)
|
||||||
params.slice(:limit, :include_filtered, :types, :exclude_types, :grouped_types).permit(:limit, :include_filtered, types: [], exclude_types: [], grouped_types: []).merge(core_params)
|
params.slice(:limit, :include_filtered, :types, :exclude_types, :grouped_types, :supported_types)
|
||||||
|
.permit(:limit, :include_filtered, types: [], exclude_types: [], grouped_types: [], supported_types: [])
|
||||||
|
.merge(core_params)
|
||||||
end
|
end
|
||||||
|
|
||||||
def expand_accounts_param
|
def expand_accounts_param
|
||||||
|
|||||||
@ -76,6 +76,15 @@ class TextFormatter
|
|||||||
rescue Addressable::URI::InvalidURIError, IDN::Idna::IdnaError
|
rescue Addressable::URI::InvalidURIError, IDN::Idna::IdnaError
|
||||||
h(url)
|
h(url)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def link_to_mention(account, with_domain: false)
|
||||||
|
url = ActivityPub::TagManager.instance.url_for(account)
|
||||||
|
display_username = with_domain ? account.pretty_acct : account.username
|
||||||
|
|
||||||
|
<<~HTML.squish
|
||||||
|
<span class="h-card" translate="no"><a href="#{h(url)}" class="u-url mention">@<span>#{h(display_username)}</span></a></span>
|
||||||
|
HTML
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@ -136,12 +145,7 @@ class TextFormatter
|
|||||||
|
|
||||||
return "@#{h(entity[:screen_name])}" if account.nil?
|
return "@#{h(entity[:screen_name])}" if account.nil?
|
||||||
|
|
||||||
url = ActivityPub::TagManager.instance.url_for(account)
|
TextFormatter.link_to_mention(account, with_domain: same_username_hits&.positive? || with_domains?)
|
||||||
display_username = same_username_hits&.positive? || with_domains? ? account.pretty_acct : account.username
|
|
||||||
|
|
||||||
<<~HTML.squish
|
|
||||||
<span class="h-card" translate="no"><a href="#{h(url)}" class="u-url mention">@<span>#{h(display_username)}</span></a></span>
|
|
||||||
HTML
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def entity_cache
|
def entity_cache
|
||||||
|
|||||||
@ -37,54 +37,71 @@ class Notification < ApplicationRecord
|
|||||||
PROPERTIES = {
|
PROPERTIES = {
|
||||||
mention: {
|
mention: {
|
||||||
filterable: true,
|
filterable: true,
|
||||||
|
baseline: true,
|
||||||
}.freeze,
|
}.freeze,
|
||||||
status: {
|
status: {
|
||||||
filterable: false,
|
filterable: false,
|
||||||
|
baseline: true,
|
||||||
}.freeze,
|
}.freeze,
|
||||||
reblog: {
|
reblog: {
|
||||||
filterable: true,
|
filterable: true,
|
||||||
|
baseline: true,
|
||||||
}.freeze,
|
}.freeze,
|
||||||
follow: {
|
follow: {
|
||||||
filterable: true,
|
filterable: true,
|
||||||
|
baseline: true,
|
||||||
}.freeze,
|
}.freeze,
|
||||||
follow_request: {
|
follow_request: {
|
||||||
filterable: true,
|
filterable: true,
|
||||||
|
baseline: true,
|
||||||
}.freeze,
|
}.freeze,
|
||||||
favourite: {
|
favourite: {
|
||||||
filterable: true,
|
filterable: true,
|
||||||
|
baseline: true,
|
||||||
}.freeze,
|
}.freeze,
|
||||||
poll: {
|
poll: {
|
||||||
filterable: false,
|
filterable: false,
|
||||||
|
baseline: true,
|
||||||
}.freeze,
|
}.freeze,
|
||||||
update: {
|
update: {
|
||||||
filterable: false,
|
filterable: false,
|
||||||
|
baseline: true,
|
||||||
}.freeze,
|
}.freeze,
|
||||||
severed_relationships: {
|
severed_relationships: {
|
||||||
filterable: false,
|
filterable: false,
|
||||||
|
baseline: false,
|
||||||
}.freeze,
|
}.freeze,
|
||||||
moderation_warning: {
|
moderation_warning: {
|
||||||
filterable: false,
|
filterable: false,
|
||||||
|
baseline: false,
|
||||||
}.freeze,
|
}.freeze,
|
||||||
annual_report: {
|
annual_report: {
|
||||||
filterable: false,
|
filterable: false,
|
||||||
|
baseline: true,
|
||||||
}.freeze,
|
}.freeze,
|
||||||
'admin.sign_up': {
|
'admin.sign_up': {
|
||||||
filterable: false,
|
filterable: false,
|
||||||
|
baseline: false,
|
||||||
}.freeze,
|
}.freeze,
|
||||||
'admin.report': {
|
'admin.report': {
|
||||||
filterable: false,
|
filterable: false,
|
||||||
|
baseline: false,
|
||||||
}.freeze,
|
}.freeze,
|
||||||
quote: {
|
quote: {
|
||||||
filterable: true,
|
filterable: true,
|
||||||
|
baseline: true,
|
||||||
}.freeze,
|
}.freeze,
|
||||||
quoted_update: {
|
quoted_update: {
|
||||||
filterable: false,
|
filterable: false,
|
||||||
|
baseline: true,
|
||||||
}.freeze,
|
}.freeze,
|
||||||
added_to_collection: {
|
added_to_collection: {
|
||||||
filterable: true,
|
filterable: true,
|
||||||
|
baseline: false,
|
||||||
}.freeze,
|
}.freeze,
|
||||||
collection_update: {
|
collection_update: {
|
||||||
filterable: false,
|
filterable: false,
|
||||||
|
baseline: false,
|
||||||
}.freeze,
|
}.freeze,
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
|
|||||||
96
app/serializers/concerns/notification_fallback_concern.rb
Normal file
96
app/serializers/concerns/notification_fallback_concern.rb
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module NotificationFallbackConcern
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
def fallback
|
||||||
|
{
|
||||||
|
title: fallback_title,
|
||||||
|
summary: fallback_summary,
|
||||||
|
description: nil,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def needs_fallback?
|
||||||
|
return false if instance_options[:supported_notification_types].nil?
|
||||||
|
return false if Notification::PROPERTIES.dig(object.type, :baseline) || instance_options[:supported_notification_types].include?(object.type.to_s)
|
||||||
|
|
||||||
|
# In rare cases, a notification might be missing its activity, in which case we can't do much
|
||||||
|
case object.type
|
||||||
|
when :severed_relationships
|
||||||
|
object.account_relationship_severance_event.present?
|
||||||
|
when :'admin.report'
|
||||||
|
object.report.present?
|
||||||
|
when :added_to_collection, :collection_update
|
||||||
|
object.target_collection.present?
|
||||||
|
else
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def fallback_title
|
||||||
|
account = object.is_a?(NotificationGroup) ? object.sample_accounts.first : object.from_account
|
||||||
|
|
||||||
|
case object.type
|
||||||
|
when :severed_relationships
|
||||||
|
I18n.t(
|
||||||
|
'notification_fallbacks.severed_relationships.title',
|
||||||
|
name: object.account_relationship_severance_event.target_name
|
||||||
|
)
|
||||||
|
when :moderation_warning
|
||||||
|
I18n.t('notification_fallbacks.moderation_warning.title')
|
||||||
|
when :'admin.sign_up'
|
||||||
|
count = object.is_a?(NotificationGroup) ? object.sample_accounts.count : 1
|
||||||
|
if count > 1
|
||||||
|
I18n.t(
|
||||||
|
'notification_fallbacks.admin_sign_up.title_and_others_html',
|
||||||
|
name: TextFormatter.link_to_mention(account),
|
||||||
|
count: count - 1
|
||||||
|
)
|
||||||
|
else
|
||||||
|
I18n.t(
|
||||||
|
'notification_fallbacks.admin_sign_up.title_html',
|
||||||
|
name: TextFormatter.link_to_mention(account)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
when :'admin.report'
|
||||||
|
I18n.t(
|
||||||
|
'notification_fallbacks.admin_report.title_html',
|
||||||
|
name: account.remote? ? account.domain : TextFormatter.link_to_mention(account),
|
||||||
|
target: TextFormatter.link_to_mention(object.report.target_account)
|
||||||
|
)
|
||||||
|
when :added_to_collection
|
||||||
|
I18n.t(
|
||||||
|
'notification_fallbacks.added_to_collection.title_html',
|
||||||
|
name: TextFormatter.link_to_mention(account)
|
||||||
|
)
|
||||||
|
when :collection_update
|
||||||
|
I18n.t(
|
||||||
|
'notification_fallbacks.collection_update.title_html',
|
||||||
|
name: account
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def fallback_summary
|
||||||
|
case object.type
|
||||||
|
when :severed_relationships
|
||||||
|
I18n.t(
|
||||||
|
'notification_fallbacks.severed_relationships.summary_html',
|
||||||
|
from: Rails.configuration.x.local_domain,
|
||||||
|
target: object.account_relationship_severance_event.target_name,
|
||||||
|
link: link_to(I18n.t('notification_fallbacks.generic.sign_in'), severed_relationships_url)
|
||||||
|
)
|
||||||
|
when :moderation_warning
|
||||||
|
I18n.t(
|
||||||
|
'notification_fallbacks.moderation_warning.summary_html',
|
||||||
|
link: link_to(I18n.t('notification_fallbacks.generic.sign_in'), disputes_strike_url(object.account_warning.id))
|
||||||
|
)
|
||||||
|
when :'admin.sign_up', :'admin.report', :added_to_collection, :collection_update
|
||||||
|
I18n.t(
|
||||||
|
'notification_fallbacks.generic.summary_html',
|
||||||
|
link: link_to(I18n.t('notification_fallbacks.generic.sign_in'), root_url)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -1,6 +1,10 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class REST::NotificationGroupSerializer < ActiveModel::Serializer
|
class REST::NotificationGroupSerializer < ActiveModel::Serializer
|
||||||
|
include RoutingHelper
|
||||||
|
include ActionView::Helpers::UrlHelper
|
||||||
|
include NotificationFallbackConcern
|
||||||
|
|
||||||
# Please update app/javascript/mastodon/api_types/notifications.ts when making changes to the attributes
|
# Please update app/javascript/mastodon/api_types/notifications.ts when making changes to the attributes
|
||||||
attributes :group_key, :notifications_count, :type, :most_recent_notification_id
|
attributes :group_key, :notifications_count, :type, :most_recent_notification_id
|
||||||
|
|
||||||
@ -8,6 +12,8 @@ class REST::NotificationGroupSerializer < ActiveModel::Serializer
|
|||||||
attribute :page_max_id, if: :paginated?
|
attribute :page_max_id, if: :paginated?
|
||||||
attribute :latest_page_notification_at, if: :paginated?
|
attribute :latest_page_notification_at, if: :paginated?
|
||||||
|
|
||||||
|
attribute :fallback, if: :needs_fallback?
|
||||||
|
|
||||||
attribute :sample_account_ids
|
attribute :sample_account_ids
|
||||||
attribute :status_id, if: :status_type?
|
attribute :status_id, if: :status_type?
|
||||||
belongs_to :report, if: :report_type?, serializer: REST::ReportSerializer
|
belongs_to :report, if: :report_type?, serializer: REST::ReportSerializer
|
||||||
|
|||||||
@ -1,11 +1,17 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class REST::NotificationSerializer < ActiveModel::Serializer
|
class REST::NotificationSerializer < ActiveModel::Serializer
|
||||||
|
include RoutingHelper
|
||||||
|
include ActionView::Helpers::UrlHelper
|
||||||
|
include NotificationFallbackConcern
|
||||||
|
|
||||||
# Please update app/javascript/mastodon/api_types/notifications.ts when making changes to the attributes
|
# Please update app/javascript/mastodon/api_types/notifications.ts when making changes to the attributes
|
||||||
attributes :id, :type, :created_at, :group_key
|
attributes :id, :type, :created_at, :group_key
|
||||||
|
|
||||||
attribute :filtered, if: :filtered?
|
attribute :filtered, if: :filtered?
|
||||||
|
|
||||||
|
attribute :fallback, if: :needs_fallback?
|
||||||
|
|
||||||
belongs_to :from_account, key: :account, serializer: REST::AccountSerializer
|
belongs_to :from_account, key: :account, serializer: REST::AccountSerializer
|
||||||
belongs_to :target_status, key: :status, if: :status_type?, serializer: REST::StatusSerializer
|
belongs_to :target_status, key: :status, if: :status_type?, serializer: REST::StatusSerializer
|
||||||
belongs_to :report, if: :report_type?, serializer: REST::ReportSerializer
|
belongs_to :report, if: :report_type?, serializer: REST::ReportSerializer
|
||||||
|
|||||||
@ -1731,6 +1731,27 @@ en:
|
|||||||
copy_account_note_text: 'This user moved from %{acct}, here were your previous notes about them:'
|
copy_account_note_text: 'This user moved from %{acct}, here were your previous notes about them:'
|
||||||
navigation:
|
navigation:
|
||||||
toggle_menu: Toggle menu
|
toggle_menu: Toggle menu
|
||||||
|
notification_fallbacks:
|
||||||
|
added_to_collection:
|
||||||
|
title_html: "%{name} added you to a collection"
|
||||||
|
admin_report:
|
||||||
|
title_html: "%{name} reported %{target}"
|
||||||
|
admin_sign_up:
|
||||||
|
title_and_others_html:
|
||||||
|
one: "%{name} and one other signed up"
|
||||||
|
other: "%{name} and %{count} others signed up"
|
||||||
|
title_html: "%{name} signed up"
|
||||||
|
collection_update:
|
||||||
|
title_html: "%{name} updated a collection you are in"
|
||||||
|
generic:
|
||||||
|
sign_in: Sign in to the Mastodon web app
|
||||||
|
summary_html: You're on an app that does not support the most recent version of Mastodon. %link for full functionality.
|
||||||
|
moderation_warning:
|
||||||
|
summary_html: You're on an app that does not support the most recent version of Mastodon. %link
|
||||||
|
title: You have received a moderation warning.
|
||||||
|
severed_relationships:
|
||||||
|
summary_html: An admin from %{from} has suspended %{target}, which means you can no longer receive updates from them or interact with them. %{link} to retrieve a list of the lost relationships.
|
||||||
|
title: Lost connections with %{name}
|
||||||
notification_mailer:
|
notification_mailer:
|
||||||
admin:
|
admin:
|
||||||
report:
|
report:
|
||||||
|
|||||||
@ -6,10 +6,16 @@ RSpec.describe REST::NotificationGroupSerializer do
|
|||||||
subject do
|
subject do
|
||||||
serialized_record_json(
|
serialized_record_json(
|
||||||
notification_group,
|
notification_group,
|
||||||
described_class
|
described_class,
|
||||||
|
options: {
|
||||||
|
scope: current_user,
|
||||||
|
scope_name: :current_user,
|
||||||
|
supported_notification_types: [],
|
||||||
|
}
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
let(:current_user) { Fabricate(:user) }
|
||||||
let(:notification_group) { NotificationGroup.new pagination_data: { latest_notification_at: 3.days.ago }, notification: Fabricate(:notification), sample_accounts: [] }
|
let(:notification_group) { NotificationGroup.new pagination_data: { latest_notification_at: 3.days.ago }, notification: Fabricate(:notification), sample_accounts: [] }
|
||||||
|
|
||||||
context 'when latest_page_notification_at is populated' do
|
context 'when latest_page_notification_at is populated' do
|
||||||
@ -20,4 +26,28 @@ RSpec.describe REST::NotificationGroupSerializer do
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
shared_examples 'with fallback notifications' do |type, fabricators|
|
||||||
|
let(:activities) { fabricators.map { |fabricator| Fabricate(fabricator) } }
|
||||||
|
let(:notifications) { activities.map { |activity| Fabricate(:notification, type:, activity:, account: current_user.account) } }
|
||||||
|
let(:notification_group) { NotificationGroup.new(notification: notifications.last, sample_accounts: notifications.map(&:from_account)) }
|
||||||
|
|
||||||
|
it 'renders correctly' do
|
||||||
|
expect(subject)
|
||||||
|
.to include(
|
||||||
|
'type' => type,
|
||||||
|
'fallback' => include(
|
||||||
|
'title' => anything,
|
||||||
|
'summary' => anything
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'with fallback notifications', 'severed_relationships', [:account_relationship_severance_event]
|
||||||
|
it_behaves_like 'with fallback notifications', 'moderation_warning', [:account_warning]
|
||||||
|
it_behaves_like 'with fallback notifications', 'admin.report', [:report]
|
||||||
|
it_behaves_like 'with fallback notifications', 'admin.report', [:report, :report]
|
||||||
|
it_behaves_like 'with fallback notifications', 'added_to_collection', [:collection_item]
|
||||||
|
it_behaves_like 'with fallback notifications', 'collection_update', [:collection]
|
||||||
end
|
end
|
||||||
|
|||||||
@ -6,10 +6,16 @@ RSpec.describe REST::NotificationSerializer do
|
|||||||
subject do
|
subject do
|
||||||
serialized_record_json(
|
serialized_record_json(
|
||||||
notification,
|
notification,
|
||||||
described_class
|
described_class,
|
||||||
|
options: {
|
||||||
|
scope: current_user,
|
||||||
|
scope_name: :current_user,
|
||||||
|
supported_notification_types: [],
|
||||||
|
}
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
let(:current_user) { Fabricate(:user) }
|
||||||
let(:notification) { Fabricate :notification }
|
let(:notification) { Fabricate :notification }
|
||||||
|
|
||||||
context 'when created_at is populated' do
|
context 'when created_at is populated' do
|
||||||
@ -20,4 +26,25 @@ RSpec.describe REST::NotificationSerializer do
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
shared_examples 'with fallback notifications' do |type, fabricator|
|
||||||
|
let(:notification) { Fabricate(:notification, type:, activity: Fabricate(fabricator), account: current_user.account) }
|
||||||
|
|
||||||
|
it 'renders correctly' do
|
||||||
|
expect(subject)
|
||||||
|
.to include(
|
||||||
|
'type' => type,
|
||||||
|
'fallback' => include(
|
||||||
|
'title' => anything,
|
||||||
|
'summary' => anything
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'with fallback notifications', 'severed_relationships', :account_relationship_severance_event
|
||||||
|
it_behaves_like 'with fallback notifications', 'moderation_warning', :account_warning
|
||||||
|
it_behaves_like 'with fallback notifications', 'admin.report', :report
|
||||||
|
it_behaves_like 'with fallback notifications', 'added_to_collection', :collection_item
|
||||||
|
it_behaves_like 'with fallback notifications', 'collection_update', :collection
|
||||||
end
|
end
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user