From c73868cd78592780cb9e0be6985fe2f34b7c91cd Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 27 Aug 2024 16:55:51 +0200 Subject: [PATCH] Add ability for admins to force grouped notifications in web UI (#31610) --- app/javascript/mastodon/actions/markers.ts | 8 ++------ .../actions/notifications_migration.tsx | 10 +++------- app/javascript/mastodon/actions/streaming.js | 8 +++++--- .../components/column_settings.jsx | 19 +++++++++++-------- .../features/notifications_wrapper.jsx | 3 ++- .../ui/components/navigation_panel.jsx | 3 ++- app/javascript/mastodon/initial_state.js | 2 ++ app/javascript/mastodon/selectors/settings.ts | 5 +++++ app/serializers/initial_state_serializer.rb | 1 + 9 files changed, 33 insertions(+), 26 deletions(-) diff --git a/app/javascript/mastodon/actions/markers.ts b/app/javascript/mastodon/actions/markers.ts index 77d91d9b9c..521859f6c2 100644 --- a/app/javascript/mastodon/actions/markers.ts +++ b/app/javascript/mastodon/actions/markers.ts @@ -2,6 +2,7 @@ import { debounce } from 'lodash'; import type { MarkerJSON } from 'mastodon/api_types/markers'; import { getAccessToken } from 'mastodon/initial_state'; +import { selectUseGroupedNotifications } from 'mastodon/selectors/settings'; import type { AppDispatch, RootState } from 'mastodon/store'; import { createAppAsyncThunk } from 'mastodon/store/typed_functions'; @@ -75,13 +76,8 @@ interface MarkerParam { } function getLastNotificationId(state: RootState): string | undefined { - // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access - const enableBeta = state.settings.getIn( - ['notifications', 'groupingBeta'], - false, - ) as boolean; // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return enableBeta + return selectUseGroupedNotifications(state) ? state.notificationGroups.lastReadId : // @ts-expect-error state.notifications is not yet typed // eslint-disable-next-line @typescript-eslint/no-unsafe-call diff --git a/app/javascript/mastodon/actions/notifications_migration.tsx b/app/javascript/mastodon/actions/notifications_migration.tsx index c245dc7565..0d4da765ec 100644 --- a/app/javascript/mastodon/actions/notifications_migration.tsx +++ b/app/javascript/mastodon/actions/notifications_migration.tsx @@ -1,3 +1,4 @@ +import { selectUseGroupedNotifications } from 'mastodon/selectors/settings'; import { createAppAsyncThunk } from 'mastodon/store'; import { fetchNotifications } from './notification_groups'; @@ -6,13 +7,8 @@ import { expandNotifications } from './notifications'; export const initializeNotifications = createAppAsyncThunk( 'notifications/initialize', (_, { dispatch, getState }) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access - const enableBeta = getState().settings.getIn( - ['notifications', 'groupingBeta'], - false, - ) as boolean; - - if (enableBeta) void dispatch(fetchNotifications()); + if (selectUseGroupedNotifications(getState())) + void dispatch(fetchNotifications()); else void dispatch(expandNotifications({})); }, ); diff --git a/app/javascript/mastodon/actions/streaming.js b/app/javascript/mastodon/actions/streaming.js index 04f5e6b88c..bfdd894b81 100644 --- a/app/javascript/mastodon/actions/streaming.js +++ b/app/javascript/mastodon/actions/streaming.js @@ -1,5 +1,7 @@ // @ts-check +import { selectUseGroupedNotifications } from 'mastodon/selectors/settings'; + import { getLocale } from '../locales'; import { connectStream } from '../stream'; @@ -103,7 +105,7 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti const notificationJSON = JSON.parse(data.payload); dispatch(updateNotifications(notificationJSON, messages, locale)); // TODO: remove this once the groups feature replaces the previous one - if(getState().settings.getIn(['notifications', 'groupingBeta'], false)) { + if(selectUseGroupedNotifications(getState())) { dispatch(processNewNotificationForGroups(notificationJSON)); } break; @@ -112,7 +114,7 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti const state = getState(); if (state.notifications.top || !state.notifications.mounted) dispatch(expandNotifications({ forceLoad: true, maxId: undefined })); - if(state.settings.getIn(['notifications', 'groupingBeta'], false)) { + if (selectUseGroupedNotifications(state)) { dispatch(refreshStaleNotificationGroups()); } break; @@ -145,7 +147,7 @@ async function refreshHomeTimelineAndNotification(dispatch, getState) { await dispatch(expandHomeTimeline({ maxId: undefined })); // TODO: remove this once the groups feature replaces the previous one - if(getState().settings.getIn(['notifications', 'groupingBeta'], false)) { + if(selectUseGroupedNotifications(getState())) { // TODO: polling for merged notifications try { await dispatch(pollRecentGroupNotifications()); diff --git a/app/javascript/mastodon/features/notifications/components/column_settings.jsx b/app/javascript/mastodon/features/notifications/components/column_settings.jsx index 359f0fb126..ed2f4ada3a 100644 --- a/app/javascript/mastodon/features/notifications/components/column_settings.jsx +++ b/app/javascript/mastodon/features/notifications/components/column_settings.jsx @@ -6,6 +6,7 @@ import { FormattedMessage } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; +import { forceGroupedNotifications } from 'mastodon/initial_state'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_REPORTS } from 'mastodon/permissions'; import ClearColumnButton from './clear_column_button'; @@ -67,15 +68,17 @@ class ColumnSettings extends PureComponent { -
-

- -

+ {!forceGroupedNotifications && ( +
+

+ +

-
- -
-
+
+ +
+
+ )}

diff --git a/app/javascript/mastodon/features/notifications_wrapper.jsx b/app/javascript/mastodon/features/notifications_wrapper.jsx index 057ed1b395..4b3efeb54e 100644 --- a/app/javascript/mastodon/features/notifications_wrapper.jsx +++ b/app/javascript/mastodon/features/notifications_wrapper.jsx @@ -1,9 +1,10 @@ import Notifications from 'mastodon/features/notifications'; import Notifications_v2 from 'mastodon/features/notifications_v2'; +import { selectUseGroupedNotifications } from 'mastodon/selectors/settings'; import { useAppSelector } from 'mastodon/store'; export const NotificationsWrapper = (props) => { - const optedInGroupedNotifications = useAppSelector((state) => state.getIn(['settings', 'notifications', 'groupingBeta'], false)); + const optedInGroupedNotifications = useAppSelector(selectUseGroupedNotifications); return ( optedInGroupedNotifications ? : diff --git a/app/javascript/mastodon/features/ui/components/navigation_panel.jsx b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx index d30ea0ac5d..407276d126 100644 --- a/app/javascript/mastodon/features/ui/components/navigation_panel.jsx +++ b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx @@ -37,6 +37,7 @@ import { timelinePreview, trendsEnabled } from 'mastodon/initial_state'; import { transientSingleColumn } from 'mastodon/is_mobile'; import { canManageReports, canViewAdminDashboard } from 'mastodon/permissions'; import { selectUnreadNotificationGroupsCount } from 'mastodon/selectors/notifications'; +import { selectUseGroupedNotifications } from 'mastodon/selectors/settings'; import ColumnLink from './column_link'; import DisabledAccountBanner from './disabled_account_banner'; @@ -64,7 +65,7 @@ const messages = defineMessages({ }); const NotificationsLink = () => { - const optedInGroupedNotifications = useSelector((state) => state.getIn(['settings', 'notifications', 'groupingBeta'], false)); + const optedInGroupedNotifications = useSelector(selectUseGroupedNotifications); const count = useSelector(state => state.getIn(['notifications', 'unread'])); const intl = useIntl(); diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js index 60b35cb31a..cf33b12dd9 100644 --- a/app/javascript/mastodon/initial_state.js +++ b/app/javascript/mastodon/initial_state.js @@ -43,6 +43,7 @@ * @property {boolean=} use_pending_items * @property {string} version * @property {string} sso_redirect + * @property {boolean} force_grouped_notifications */ /** @@ -118,6 +119,7 @@ export const criticalUpdatesPending = initialState?.critical_updates_pending; // @ts-expect-error export const statusPageUrl = getMeta('status_page_url'); export const sso_redirect = getMeta('sso_redirect'); +export const forceGroupedNotifications = getMeta('force_grouped_notifications'); /** * @returns {string | undefined} diff --git a/app/javascript/mastodon/selectors/settings.ts b/app/javascript/mastodon/selectors/settings.ts index 93276c6692..22e7c13b93 100644 --- a/app/javascript/mastodon/selectors/settings.ts +++ b/app/javascript/mastodon/selectors/settings.ts @@ -1,3 +1,4 @@ +import { forceGroupedNotifications } from 'mastodon/initial_state'; import type { RootState } from 'mastodon/store'; /* eslint-disable @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */ @@ -25,6 +26,10 @@ export const selectSettingsNotificationsQuickFilterAdvanced = ( ) => state.settings.getIn(['notifications', 'quickFilter', 'advanced']) as boolean; +export const selectUseGroupedNotifications = (state: RootState) => + forceGroupedNotifications || + (state.settings.getIn(['notifications', 'groupingBeta']) as boolean); + export const selectSettingsNotificationsShowUnread = (state: RootState) => state.settings.getIn(['notifications', 'showUnread']) as boolean; diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index 13f332c95c..25a352806f 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -109,6 +109,7 @@ class InitialStateSerializer < ActiveModel::Serializer trends_as_landing_page: Setting.trends_as_landing_page, trends_enabled: Setting.trends, version: instance_presenter.version, + force_grouped_notifications: ENV['FORCE_GROUPED_NOTIFICATIONS'] == 'true', } end