Skip to content

Commit

Permalink
Merge pull request #2846 from ClearlyClaire/glitch-soc/merge-upstream
Browse files Browse the repository at this point in the history
Merge upstream changes up to e0648a9
  • Loading branch information
ClearlyClaire authored Sep 16, 2024
2 parents 3469191 + 0740352 commit c8ef702
Show file tree
Hide file tree
Showing 95 changed files with 2,093 additions and 1,161 deletions.
5 changes: 5 additions & 0 deletions .rubocop/strict.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@ RSpec/Focus: # Require full spec run on CI
Exclude: []

Rails/Output: # Remove any `puts` debugging
inherit_mode:
merge:
- Include
Enabled: true
Exclude: []
Include:
- spec/**/*.rb

Rails/FindEach: # Using `each` could impact performance, use `find_each`
Enabled: true
Expand Down
10 changes: 5 additions & 5 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ GEM
httplog (1.7.0)
rack (>= 2.0)
rainbow (>= 2.0.0)
i18n (1.14.5)
i18n (1.14.6)
concurrent-ruby (~> 1.0)
i18n-tasks (1.0.14)
activesupport (>= 4.0.2)
Expand Down Expand Up @@ -472,9 +472,9 @@ GEM
omniauth-rails_csrf_protection (1.0.2)
actionpack (>= 4.2)
omniauth (~> 2.0)
omniauth-saml (2.1.0)
omniauth (~> 2.0)
ruby-saml (~> 1.12)
omniauth-saml (2.2.1)
omniauth (~> 2.1)
ruby-saml (~> 1.17)
omniauth_openid_connect (0.6.1)
omniauth (>= 1.9, < 3)
openid_connect (~> 1.1)
Expand Down Expand Up @@ -764,7 +764,7 @@ GEM
rubocop-rspec (~> 3, >= 3.0.1)
ruby-prof (1.7.0)
ruby-progressbar (1.13.0)
ruby-saml (1.16.0)
ruby-saml (1.17.0)
nokogiri (>= 1.13.10)
rexml
ruby-vips (2.2.2)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

class Api::V2Alpha::Notifications::AccountsController < Api::BaseController
class Api::V2::Notifications::AccountsController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:notifications' }
before_action :require_user!
before_action :set_notifications!
Expand Down Expand Up @@ -33,11 +33,11 @@ def set_notifications!
end

def next_path
api_v2_alpha_notification_accounts_url pagination_params(max_id: pagination_max_id) if records_continue?
api_v2_notification_accounts_url pagination_params(max_id: pagination_max_id) if records_continue?
end

def prev_path
api_v2_alpha_notification_accounts_url pagination_params(min_id: pagination_since_id) unless @paginated_notifications.empty?
api_v2_notification_accounts_url pagination_params(min_id: pagination_since_id) unless @paginated_notifications.empty?
end

def pagination_collection
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

class Api::V2Alpha::NotificationsController < Api::BaseController
class Api::V2::NotificationsController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:notifications' }, except: [:clear, :dismiss]
before_action -> { doorkeeper_authorize! :write, :'write:notifications' }, only: [:clear, :dismiss]
before_action :require_user!
Expand All @@ -21,7 +21,7 @@ def index
ActiveRecord::Associations::Preloader.new(records: @presenter.accounts, associations: [:account_stat, { user: :role }]).call
end

MastodonOTELTracer.in_span('Api::V2Alpha::NotificationsController#index rendering') do |span|
MastodonOTELTracer.in_span('Api::V2::NotificationsController#index rendering') do |span|
statuses = @grouped_notifications.filter_map { |group| group.target_status&.id }

span.add_attributes(
Expand Down Expand Up @@ -64,7 +64,7 @@ def dismiss
private

def load_notifications
MastodonOTELTracer.in_span('Api::V2Alpha::NotificationsController#load_notifications') do
MastodonOTELTracer.in_span('Api::V2::NotificationsController#load_notifications') do
notifications = browserable_account_notifications.includes(from_account: [:account_stat, :user]).to_a_grouped_paginated_by_id(
limit_param(DEFAULT_NOTIFICATIONS_LIMIT),
params.slice(:max_id, :since_id, :min_id, :grouped_types).permit(:max_id, :since_id, :min_id, grouped_types: [])
Expand All @@ -79,7 +79,7 @@ def load_notifications
def load_grouped_notifications
return [] if @notifications.empty?

MastodonOTELTracer.in_span('Api::V2Alpha::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])
end
end
Expand All @@ -101,11 +101,11 @@ def target_statuses_from_notifications
end

def next_path
api_v2_alpha_notifications_url pagination_params(max_id: pagination_max_id) unless @notifications.empty?
api_v2_notifications_url pagination_params(max_id: pagination_max_id) unless @notifications.empty?
end

def prev_path
api_v2_alpha_notifications_url pagination_params(min_id: pagination_since_id) unless @notifications.empty?
api_v2_notifications_url pagination_params(min_id: pagination_since_id) unless @notifications.empty?
end

def pagination_collection
Expand Down
8 changes: 4 additions & 4 deletions app/javascript/flavours/glitch/actions/notification_groups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createAction } from '@reduxjs/toolkit';

import {
apiClearNotifications,
apiFetchNotifications,
apiFetchNotificationGroups,
} from 'flavours/glitch/api/notifications';
import type { ApiAccountJSON } from 'flavours/glitch/api_types/accounts';
import type {
Expand Down Expand Up @@ -71,7 +71,7 @@ function dispatchAssociatedRecords(
export const fetchNotifications = createDataLoadingThunk(
'notificationGroups/fetch',
async (_params, { getState }) =>
apiFetchNotifications({ exclude_types: getExcludedTypes(getState()) }),
apiFetchNotificationGroups({ exclude_types: getExcludedTypes(getState()) }),
({ notifications, accounts, statuses }, { dispatch }) => {
dispatch(importFetchedAccounts(accounts));
dispatch(importFetchedStatuses(statuses));
Expand All @@ -92,7 +92,7 @@ export const fetchNotifications = createDataLoadingThunk(
export const fetchNotificationsGap = createDataLoadingThunk(
'notificationGroups/fetchGap',
async (params: { gap: NotificationGap }, { getState }) =>
apiFetchNotifications({
apiFetchNotificationGroups({
max_id: params.gap.maxId,
exclude_types: getExcludedTypes(getState()),
}),
Expand All @@ -108,7 +108,7 @@ export const fetchNotificationsGap = createDataLoadingThunk(
export const pollRecentNotifications = createDataLoadingThunk(
'notificationGroups/pollRecentNotifications',
async (_params, { getState }) => {
return apiFetchNotifications({
return apiFetchNotificationGroups({
max_id: undefined,
exclude_types: getExcludedTypes(getState()),
// In slow mode, we don't want to include notifications that duplicate the already-displayed ones
Expand Down
234 changes: 234 additions & 0 deletions app/javascript/flavours/glitch/actions/notification_requests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
import {
apiFetchNotificationRequest,
apiFetchNotificationRequests,
apiFetchNotifications,
apiAcceptNotificationRequest,
apiDismissNotificationRequest,
apiAcceptNotificationRequests,
apiDismissNotificationRequests,
} from 'flavours/glitch/api/notifications';
import type { ApiAccountJSON } from 'flavours/glitch/api_types/accounts';
import type {
ApiNotificationGroupJSON,
ApiNotificationJSON,
} from 'flavours/glitch/api_types/notifications';
import type { ApiStatusJSON } from 'flavours/glitch/api_types/statuses';
import type { AppDispatch, RootState } from 'flavours/glitch/store';
import { createDataLoadingThunk } from 'flavours/glitch/store/typed_functions';

import { importFetchedAccounts, importFetchedStatuses } from './importer';
import { decreasePendingNotificationsCount } from './notification_policies';

// TODO: refactor with notification_groups
function dispatchAssociatedRecords(
dispatch: AppDispatch,
notifications: ApiNotificationGroupJSON[] | ApiNotificationJSON[],
) {
const fetchedAccounts: ApiAccountJSON[] = [];
const fetchedStatuses: ApiStatusJSON[] = [];

notifications.forEach((notification) => {
if (notification.type === 'admin.report') {
fetchedAccounts.push(notification.report.target_account);
}

if (notification.type === 'moderation_warning') {
fetchedAccounts.push(notification.moderation_warning.target_account);
}

if ('status' in notification && notification.status) {
fetchedStatuses.push(notification.status);
}
});

if (fetchedAccounts.length > 0)
dispatch(importFetchedAccounts(fetchedAccounts));

if (fetchedStatuses.length > 0)
dispatch(importFetchedStatuses(fetchedStatuses));
}

export const fetchNotificationRequests = createDataLoadingThunk(
'notificationRequests/fetch',
async (_params, { getState }) => {
let sinceId = undefined;

if (getState().notificationRequests.items.length > 0) {
sinceId = getState().notificationRequests.items[0]?.id;
}

return apiFetchNotificationRequests({
since_id: sinceId,
});
},
({ requests, links }, { dispatch }) => {
const next = links.refs.find((link) => link.rel === 'next');

dispatch(importFetchedAccounts(requests.map((request) => request.account)));

return { requests, next: next?.uri };
},
{
condition: (_params, { getState }) =>
!getState().notificationRequests.isLoading,
},
);

export const fetchNotificationRequest = createDataLoadingThunk(
'notificationRequest/fetch',
async ({ id }: { id: string }) => apiFetchNotificationRequest(id),
{
condition: ({ id }, { getState }) =>
!(
getState().notificationRequests.current.item?.id === id ||
getState().notificationRequests.current.isLoading
),
},
);

export const expandNotificationRequests = createDataLoadingThunk(
'notificationRequests/expand',
async (_, { getState }) => {
const nextUrl = getState().notificationRequests.next;
if (!nextUrl) throw new Error('missing URL');

return apiFetchNotificationRequests(undefined, nextUrl);
},
({ requests, links }, { dispatch }) => {
const next = links.refs.find((link) => link.rel === 'next');

dispatch(importFetchedAccounts(requests.map((request) => request.account)));

return { requests, next: next?.uri };
},
{
condition: (_, { getState }) =>
!!getState().notificationRequests.next &&
!getState().notificationRequests.isLoading,
},
);

export const fetchNotificationsForRequest = createDataLoadingThunk(
'notificationRequest/fetchNotifications',
async ({ accountId }: { accountId: string }, { getState }) => {
const sinceId =
// @ts-expect-error current.notifications.items is not yet typed
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
getState().notificationRequests.current.notifications.items[0]?.get(
'id',
) as string | undefined;

return apiFetchNotifications({
since_id: sinceId,
account_id: accountId,
});
},
({ notifications, links }, { dispatch }) => {
const next = links.refs.find((link) => link.rel === 'next');

dispatchAssociatedRecords(dispatch, notifications);

return { notifications, next: next?.uri };
},
{
condition: ({ accountId }, { getState }) => {
const current = getState().notificationRequests.current;
return !(
current.item?.account_id === accountId &&
current.notifications.isLoading
);
},
},
);

export const expandNotificationsForRequest = createDataLoadingThunk(
'notificationRequest/expandNotifications',
async (_, { getState }) => {
const nextUrl = getState().notificationRequests.current.notifications.next;
if (!nextUrl) throw new Error('missing URL');

return apiFetchNotifications(undefined, nextUrl);
},
({ notifications, links }, { dispatch }) => {
const next = links.refs.find((link) => link.rel === 'next');

dispatchAssociatedRecords(dispatch, notifications);

return { notifications, next: next?.uri };
},
{
condition: ({ accountId }: { accountId: string }, { getState }) => {
const url = getState().notificationRequests.current.notifications.next;

return (
!!url &&
!getState().notificationRequests.current.notifications.isLoading &&
getState().notificationRequests.current.item?.account_id === accountId
);
},
},
);

const selectNotificationCountForRequest = (state: RootState, id: string) => {
const requests = state.notificationRequests.items;
const thisRequest = requests.find((request) => request.id === id);
return thisRequest ? thisRequest.notifications_count : 0;
};

export const acceptNotificationRequest = createDataLoadingThunk(
'notificationRequest/accept',
({ id }: { id: string }) => apiAcceptNotificationRequest(id),
(_data, { dispatch, getState, discardLoadData, actionArg: { id } }) => {
const count = selectNotificationCountForRequest(getState(), id);

dispatch(decreasePendingNotificationsCount(count));

// The payload is not used in any functions
return discardLoadData;
},
);

export const dismissNotificationRequest = createDataLoadingThunk(
'notificationRequest/dismiss',
({ id }: { id: string }) => apiDismissNotificationRequest(id),
(_data, { dispatch, getState, discardLoadData, actionArg: { id } }) => {
const count = selectNotificationCountForRequest(getState(), id);

dispatch(decreasePendingNotificationsCount(count));

// The payload is not used in any functions
return discardLoadData;
},
);

export const acceptNotificationRequests = createDataLoadingThunk(
'notificationRequests/acceptBulk',
({ ids }: { ids: string[] }) => apiAcceptNotificationRequests(ids),
(_data, { dispatch, getState, discardLoadData, actionArg: { ids } }) => {
const count = ids.reduce(
(count, id) => count + selectNotificationCountForRequest(getState(), id),
0,
);

dispatch(decreasePendingNotificationsCount(count));

// The payload is not used in any functions
return discardLoadData;
},
);

export const dismissNotificationRequests = createDataLoadingThunk(
'notificationRequests/dismissBulk',
({ ids }: { ids: string[] }) => apiDismissNotificationRequests(ids),
(_data, { dispatch, getState, discardLoadData, actionArg: { ids } }) => {
const count = ids.reduce(
(count, id) => count + selectNotificationCountForRequest(getState(), id),
0,
);

dispatch(decreasePendingNotificationsCount(count));

// The payload is not used in any functions
return discardLoadData;
},
);
Loading

0 comments on commit c8ef702

Please sign in to comment.