Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge upstream changes up to e0648a916ab81925545504173bf4f43ec64d4f3c #2846

Merged
merged 19 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
4b40d13
Increase preview card image size limit from 2MB to 8MB when using lib…
ClearlyClaire Sep 14, 2024
0cc05fb
Update dependency i18n to v1.14.6 (#31925)
renovate[bot] Sep 16, 2024
2afaa3b
Update dependency husky to v9.1.6 (#31923)
renovate[bot] Sep 16, 2024
9ea2705
Update DefinitelyTyped types (non-major) (#31922)
renovate[bot] Sep 16, 2024
c24de04
Update dependency postcss to v8.4.47 (#31913)
renovate[bot] Sep 16, 2024
822e918
Add coverage for `Bookmark` validation and reblog/status check callba…
mjankowski Sep 16, 2024
abd2f56
Add validation coverage for `CustomEmoji` shortcode value (#31906)
mjankowski Sep 16, 2024
bf8b55c
Enable `Rails/Output` in `spec/` dir (#31905)
mjankowski Sep 16, 2024
1d58f93
Update dependencies omniauth-saml, ruby-xml and ruby-saml (#31926)
ClearlyClaire Sep 16, 2024
474abf3
New Crowdin Translations (automated) (#31911)
github-actions[bot] Sep 16, 2024
c54cbf7
Remove `follow` creation from `ListAccount` fabricator (#31902)
mjankowski Sep 16, 2024
d5cf27e
Add global Regexp timeout (#31928)
ClearlyClaire Sep 16, 2024
c0eda83
Convert notification requests actions and reducers to Typescript (#31…
ClearlyClaire Sep 16, 2024
efb0e2c
Fix horizontal scrollbar on who to follow carousel in web UI (#31912)
Gargron Sep 16, 2024
e0648a9
Rename `/api/v2_alpha/notifications*` to `/api/v2/notifications*` (#3…
ClearlyClaire Sep 16, 2024
665e912
Merge commit 'e0648a916ab81925545504173bf4f43ec64d4f3c' into glitch-s…
ClearlyClaire Sep 16, 2024
e25634c
[Glitch] Convert notification requests actions and reducers to Typesc…
ClearlyClaire Sep 16, 2024
b6398cf
[Glitch] Fix horizontal scrollbar on who to follow carousel in web UI
Gargron Sep 16, 2024
0740352
[Glitch] Rename `/api/v2_alpha/notifications*` to `/api/v2/notificati…
ClearlyClaire Sep 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading