mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-10-24 12:13:33 +08:00
Handle channel settings depending on server config
External notification channels (like email) are processed in Celery tasks. When Celery is not enabled in the server, we cannot show those channels to the user and we have to indicate in the API that those will be ignored.
This commit is contained in:
@@ -1,11 +1,18 @@
|
||||
import { type components, fetcher } from "@/api/schema";
|
||||
|
||||
export type UserNotificationPreferences = components["schemas"]["UserNotificationPreferences"];
|
||||
type UserNotificationPreferences = components["schemas"]["UserNotificationPreferences"];
|
||||
|
||||
export interface UserNotificationPreferencesExtended extends UserNotificationPreferences {
|
||||
supportedChannels: string[];
|
||||
}
|
||||
|
||||
const getNotificationsPreferences = fetcher.path("/api/notifications/preferences").method("get").create();
|
||||
export async function getNotificationsPreferencesFromServer() {
|
||||
const { data } = await getNotificationsPreferences({});
|
||||
return data;
|
||||
export async function getNotificationsPreferencesFromServer(): Promise<UserNotificationPreferencesExtended> {
|
||||
const { data, headers } = await getNotificationsPreferences({});
|
||||
return {
|
||||
...data,
|
||||
supportedChannels: headers.get("supported-channels")?.split(",") ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
type UpdateUserNotificationPreferencesRequest = components["schemas"]["UpdateUserNotificationPreferencesRequest"];
|
||||
|
||||
@@ -1255,6 +1255,9 @@ export interface paths {
|
||||
/**
|
||||
* Returns the current user's preferences for notifications.
|
||||
* @description Anonymous users cannot have notification preferences. They will receive only broadcasted notifications.
|
||||
*
|
||||
* - The settings will contain all possible channels, but the client should only show the ones that are really supported by the server.
|
||||
* The supported channels are returned in the `supported-channels` header.
|
||||
*/
|
||||
get: operations["get_notification_preferences_api_notifications_preferences_get"];
|
||||
/**
|
||||
@@ -20160,6 +20163,9 @@ export interface operations {
|
||||
/**
|
||||
* Returns the current user's preferences for notifications.
|
||||
* @description Anonymous users cannot have notification preferences. They will receive only broadcasted notifications.
|
||||
*
|
||||
* - The settings will contain all possible channels, but the client should only show the ones that are really supported by the server.
|
||||
* The supported channels are returned in the `supported-channels` header.
|
||||
*/
|
||||
parameters?: {
|
||||
/** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */
|
||||
|
||||
@@ -8,7 +8,7 @@ import { computed, ref, watch } from "vue";
|
||||
import {
|
||||
getNotificationsPreferencesFromServer,
|
||||
updateNotificationsPreferencesOnServer,
|
||||
UserNotificationPreferences,
|
||||
UserNotificationPreferencesExtended,
|
||||
} from "@/api/notifications.preferences";
|
||||
import { useConfig } from "@/composables/config";
|
||||
import { Toast } from "@/composables/toast";
|
||||
@@ -39,14 +39,15 @@ const { config } = useConfig(true);
|
||||
const loading = ref(false);
|
||||
const errorMessage = ref<string | null>(null);
|
||||
const pushNotificationsGranted = ref(pushNotificationsEnabled());
|
||||
const notificationsPreferences = ref<UserNotificationPreferences["preferences"]>({});
|
||||
const notificationsPreferences = ref<UserNotificationPreferencesExtended["preferences"]>({});
|
||||
const supportedChannels = ref<string[]>([]);
|
||||
|
||||
const categories = computed(() => Object.keys(notificationsPreferences.value));
|
||||
const showPreferences = computed(() => {
|
||||
return !loading.value && config.value.enable_notification_system && notificationsPreferences.value;
|
||||
});
|
||||
|
||||
const categoryDescriptionMap = {
|
||||
const categoryDescriptionMap: Record<string, string> = {
|
||||
message: "You will receive notifications when someone sends you a message.",
|
||||
new_shared_item: "You will receive notifications when someone shares an item with you.",
|
||||
};
|
||||
@@ -55,6 +56,7 @@ async function getNotificationsPreferences() {
|
||||
loading.value = true;
|
||||
await getNotificationsPreferencesFromServer()
|
||||
.then((data) => {
|
||||
supportedChannels.value = data.supportedChannels;
|
||||
notificationsPreferences.value = data.preferences;
|
||||
})
|
||||
.catch((error: any) => {
|
||||
@@ -148,10 +150,7 @@ watch(
|
||||
switch />
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="channel in Object.keys(notificationsPreferences[category].channels)"
|
||||
:key="channel"
|
||||
class="category-channel">
|
||||
<div v-for="channel in supportedChannels" :key="channel" class="category-channel">
|
||||
<BFormCheckbox
|
||||
v-model="notificationsPreferences[category].channels[channel]"
|
||||
v-localize
|
||||
|
||||
@@ -82,6 +82,21 @@ class CleanupResultSummary(NamedTuple):
|
||||
deleted_associations_count: int
|
||||
|
||||
|
||||
class NotificationRecipientResolverStrategy(Protocol):
|
||||
def resolve_users(self, recipients: NotificationRecipients) -> List[User]:
|
||||
pass
|
||||
|
||||
|
||||
class NotificationChannelPlugin(Protocol):
|
||||
config: GalaxyAppConfiguration
|
||||
|
||||
def __init__(self, config: GalaxyAppConfiguration):
|
||||
self.config = config
|
||||
|
||||
def send(self, notification: Notification, user: User):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class NotificationManager:
|
||||
"""Manager class to interact with the database models related with Notifications."""
|
||||
|
||||
@@ -113,11 +128,7 @@ class NotificationManager:
|
||||
Notification.expiration_time,
|
||||
Notification.content,
|
||||
]
|
||||
# Register the supported notification channels here
|
||||
self.channel_plugins: Dict[str, NotificationChannelPlugin] = {
|
||||
"push": NoOpNotificationChannelPlugin(self.config), # Push notifications are handled by the client
|
||||
"email": EmailNotificationChannelPlugin(self.config),
|
||||
}
|
||||
self.channel_plugins = self._register_supported_channels()
|
||||
|
||||
@property
|
||||
def notifications_enabled(self):
|
||||
@@ -407,12 +418,30 @@ class NotificationManager:
|
||||
self, user: User, request: UpdateUserNotificationPreferencesRequest
|
||||
) -> UserNotificationPreferences:
|
||||
"""Updates the user's notification preferences with the requested changes."""
|
||||
notification_preferences = self.get_user_notification_preferences(user)
|
||||
notification_preferences.update(request.preferences)
|
||||
user.preferences[NOTIFICATION_PREFERENCES_SECTION_NAME] = notification_preferences.model_dump_json()
|
||||
preferences = self.get_user_notification_preferences(user)
|
||||
preferences.update(request.preferences)
|
||||
user.preferences[NOTIFICATION_PREFERENCES_SECTION_NAME] = preferences.model_dump_json()
|
||||
with transaction(self.sa_session):
|
||||
self.sa_session.commit()
|
||||
return notification_preferences
|
||||
return preferences
|
||||
|
||||
def _register_supported_channels(self) -> Dict[str, NotificationChannelPlugin]:
|
||||
"""Registers the supported notification channels in this server."""
|
||||
supported_channels: Dict[str, NotificationChannelPlugin] = {
|
||||
# Push notifications are handled client-side so no real plugin is needed
|
||||
"push": NoOpNotificationChannelPlugin(self.config),
|
||||
}
|
||||
|
||||
if self.can_send_notifications_async:
|
||||
# Most additional channels require asynchronous processing and will be
|
||||
# handled by Celery tasks. Add their plugins here.
|
||||
supported_channels["email"] = EmailNotificationChannelPlugin(self.config)
|
||||
|
||||
return supported_channels
|
||||
|
||||
def get_supported_channels(self) -> Set[str]:
|
||||
"""Returns the set of supported notification channels in this server."""
|
||||
return set(self.channel_plugins.keys())
|
||||
|
||||
def cleanup_expired_notifications(self) -> CleanupResultSummary:
|
||||
"""
|
||||
@@ -490,9 +519,8 @@ class NotificationManager:
|
||||
return stmt
|
||||
|
||||
|
||||
class NotificationRecipientResolverStrategy(Protocol):
|
||||
def resolve_users(self, recipients: NotificationRecipients) -> List[User]:
|
||||
pass
|
||||
# --------------------------------------
|
||||
# Notification Recipients Resolver Implementations
|
||||
|
||||
|
||||
class NotificationRecipientResolver:
|
||||
@@ -601,17 +629,7 @@ class RecursiveCTEStrategy(NotificationRecipientResolverStrategy):
|
||||
|
||||
|
||||
# --------------------------------------
|
||||
# Notification Channel Plugins
|
||||
|
||||
|
||||
class NotificationChannelPlugin(Protocol):
|
||||
config: GalaxyAppConfiguration
|
||||
|
||||
def __init__(self, config: GalaxyAppConfiguration):
|
||||
self.config = config
|
||||
|
||||
def send(self, notification: Notification, user: User):
|
||||
raise NotImplementedError
|
||||
# Notification Channel Plugins Implementations
|
||||
|
||||
|
||||
class NoOpNotificationChannelPlugin(NotificationChannelPlugin):
|
||||
|
||||
@@ -421,7 +421,10 @@ class NotificationChannelSettings(Model):
|
||||
email: bool = Field(
|
||||
default=True,
|
||||
title="Email",
|
||||
description="Whether the user wants to receive email notifications for this category.",
|
||||
description=(
|
||||
"Whether the user wants to receive email notifications for this category. "
|
||||
"This setting will be ignored unless the server supports asynchronous tasks."
|
||||
),
|
||||
)
|
||||
# TODO: Add more channels here and implement the corresponding plugin in lib/galaxy/managers/notification.py
|
||||
# matrix: bool # Possible future Matrix.org integration?
|
||||
|
||||
@@ -70,10 +70,20 @@ class FastAPINotifications:
|
||||
)
|
||||
def get_notification_preferences(
|
||||
self,
|
||||
response: Response,
|
||||
trans: ProvidesUserContext = DependsOnTrans,
|
||||
) -> UserNotificationPreferences:
|
||||
"""Anonymous users cannot have notification preferences. They will receive only broadcasted notifications."""
|
||||
return self.service.get_user_notification_preferences(trans)
|
||||
"""Anonymous users cannot have notification preferences. They will receive only broadcasted notifications.
|
||||
|
||||
- The settings will contain all possible channels, but the client should only show the ones that are really supported by the server.
|
||||
The supported channels are returned in the `supported-channels` header.
|
||||
"""
|
||||
result = self.service.get_user_notification_preferences(trans)
|
||||
# Inform the client which channels are really supported by the server since the settings will contain all possible channels.
|
||||
response.headers["supported-channels"] = str.join(
|
||||
",", self.service.notification_manager.get_supported_channels()
|
||||
)
|
||||
return result
|
||||
|
||||
@router.put(
|
||||
"/api/notifications/preferences",
|
||||
|
||||
Reference in New Issue
Block a user