From feac77a51cfd9dc2d3c300cdae6fe56244fdbce3 Mon Sep 17 00:00:00 2001 From: Kevin Schiffer Date: Tue, 6 Feb 2024 14:34:37 +0900 Subject: [PATCH] console: Fix periodical notification updates --- .../console/store/actions/notifications.js | 18 ++++- .../store/middleware/logics/notifications.js | 72 +++++++++++++++---- .../console/store/reducers/notifications.js | 19 +++-- pkg/webui/console/views/app/index.js | 13 ---- pkg/webui/styles/variables/tokens.styl | 1 + 5 files changed, 87 insertions(+), 36 deletions(-) diff --git a/pkg/webui/console/store/actions/notifications.js b/pkg/webui/console/store/actions/notifications.js index d0dfd8b8020..a35e364a3dc 100644 --- a/pkg/webui/console/store/actions/notifications.js +++ b/pkg/webui/console/store/actions/notifications.js @@ -62,7 +62,7 @@ export const [ success: getUnseenNotificationsPeriodicallySuccess, failure: getUnseenNotificationsPeriodicallyFailure, }, -] = createRequestActions(GET_UNSEEN_NOTIFICATIONS_PERIODICALLY_BASE, () => ({})) +] = createRequestActions(GET_UNSEEN_NOTIFICATIONS_PERIODICALLY_BASE) export const UPDATE_NOTIFICATION_STATUS_BASE = 'UPDATE_NOTIFICATION_STATUS' export const [ @@ -89,4 +89,18 @@ export const [ failure: MARK_ALL_AS_SEEN_FAILURE, }, { request: markAllAsSeen, success: markAllAsSeenSuccess, failure: markAllAsSeenFailure }, -] = createRequestActions(MARK_ALL_AS_SEEN_BASE, () => ({})) +] = createRequestActions(MARK_ALL_AS_SEEN_BASE) + +export const REFRESH_NOTIFICATIONS_BASE = 'REFRESH_NOTIFICATIONS' +export const [ + { + request: REFRESH_NOTIFICATIONS, + success: REFRESH_NOTIFICATIONS_SUCCESS, + failure: REFRESH_NOTIFICATIONS_FAILURE, + }, + { + request: refreshNotifications, + success: refreshNotificationsSuccess, + failure: refreshNotificationsFailure, + }, +] = createRequestActions(REFRESH_NOTIFICATIONS_BASE) diff --git a/pkg/webui/console/store/middleware/logics/notifications.js b/pkg/webui/console/store/middleware/logics/notifications.js index fb0302b7b2b..97501bb5aed 100644 --- a/pkg/webui/console/store/middleware/logics/notifications.js +++ b/pkg/webui/console/store/middleware/logics/notifications.js @@ -13,12 +13,15 @@ // limitations under the License. import { defineMessage } from 'react-intl' +import { createLogic } from 'redux-logic' import tts from '@console/api/tts' import toast from '@ttn-lw/components/toast' import createRequestLogic from '@ttn-lw/lib/store/logics/create-request-logic' +import attachPromise from '@ttn-lw/lib/store/actions/attach-promise' +import { selectIsOnlineStatus } from '@ttn-lw/lib/store/selectors/status' import * as notifications from '@console/store/actions/notifications' @@ -64,23 +67,58 @@ const getInboxNotificationsLogic = createRequestLogic({ type: notifications.GET_INBOX_NOTIFICATIONS, process: async ({ action, getState }) => { const { - payload: { page, limit }, + payload: { page = 1, limit = 1000 }, } = action const filter = ['NOTIFICATION_STATUS_UNSEEN', 'NOTIFICATION_STATUS_SEEN'] const userId = selectUserId(getState()) const result = await tts.Notifications.getAllNotifications(userId, filter, page, limit) - const unseen = result.notifications.filter(notification => !('status' in notification)).length return { notifications: result.notifications, totalCount: result.totalCount, - unseenTotalCount: unseen, page, limit, } }, }) +const refreshNotificationsLogic = createRequestLogic({ + type: notifications.REFRESH_NOTIFICATIONS, + debounce: 10000, // Set a debounce in case the interval clogs for some reason. + validate: ({ getState }, allow, reject) => { + // Avoid refreshing notifications while the Console is offline. + const isOnline = selectIsOnlineStatus(getState()) + if (isOnline) { + allow() + } else { + reject() + } + }, + process: async ({ getState }, dispatch) => { + const state = getState() + const userId = selectUserId(state) + const prevTotalUnseenCount = selectTotalUnseenCount(state) + + const unseen = await tts.Notifications.getAllNotifications( + userId, + ['NOTIFICATION_STATUS_UNSEEN'], + 1, + 1, + ) + + // If there are new unseen notifications, show a toast and fetch the notifications. + if (unseen && unseen.totalCount > prevTotalUnseenCount) { + toast({ + title: m.newNotifications, + type: toast.types.INFO, + }) + await dispatch(attachPromise(notifications.getInboxNotifications())) + } + + return { unseenTotalCount: unseen?.totalCount } + }, +}) + const getArchivedNotificationsLogic = createRequestLogic({ type: notifications.GET_ARCHIVED_NOTIFICATIONS, process: async ({ action, getState }) => { @@ -95,18 +133,23 @@ const getArchivedNotificationsLogic = createRequestLogic({ }, }) -const getUnseenNotificationsPeriodicallyLogic = createRequestLogic({ +const getUnseenNotificationsPeriodicallyLogic = createLogic({ type: notifications.GET_UNSEEN_NOTIFICATIONS_PERIODICALLY, - process: async ({ getState }) => { - const id = selectUserId(getState()) - const result = await tts.Notifications.getAllNotifications(id, ['NOTIFICATION_STATUS_UNSEEN']) - const totalCount = selectTotalUnseenCount(getState()) - - if (result.totalCount > totalCount) { - toast({ message: m.newNotifications, type: toast.types.INFO }) - } - - return { notifications: result.notifications, totalCount: result.totalCount } + processOptions: { + dispatchMultiple: true, + }, + warnTimeout: 0, + process: async (_, dispatch) => { + // Fetch once initially. + dispatch(notifications.refreshNotifications()) + + setInterval( + async () => { + dispatch(notifications.refreshNotifications()) + }, + // Refresh notifications every 15 minutes. + 1000 * 10 * 15, + ) }, }) @@ -135,6 +178,7 @@ const markAllAsSeenLogic = createRequestLogic({ export default [ getInboxNotificationsLogic, + refreshNotificationsLogic, getArchivedNotificationsLogic, getUnseenNotificationsPeriodicallyLogic, updateNotificationStatusLogic, diff --git a/pkg/webui/console/store/reducers/notifications.js b/pkg/webui/console/store/reducers/notifications.js index 19d77c2b017..e5b040a4474 100644 --- a/pkg/webui/console/store/reducers/notifications.js +++ b/pkg/webui/console/store/reducers/notifications.js @@ -15,8 +15,8 @@ import { GET_ARCHIVED_NOTIFICATIONS_SUCCESS, GET_INBOX_NOTIFICATIONS_SUCCESS, - GET_UNSEEN_NOTIFICATIONS_PERIODICALLY_SUCCESS, MARK_ALL_AS_SEEN_SUCCESS, + REFRESH_NOTIFICATIONS_SUCCESS, UPDATE_NOTIFICATION_STATUS_SUCCESS, } from '@console/store/actions/notifications' @@ -24,7 +24,6 @@ const defaultState = { notifications: { inbox: { entities: [], totalCount: 0 }, archived: { entities: [], totalCount: 0 }, - unseen: { entities: [], totalCount: 0 }, }, unseenTotalCount: undefined, } @@ -62,8 +61,19 @@ const notifications = (state = defaultState, { type, payload }) => { totalCount: payload.totalCount, }, }, + } + case REFRESH_NOTIFICATIONS_SUCCESS: + if ( + payload.unseenTotalCount === undefined || + payload.unseenTotalCount === state.unseenTotalCount + ) { + return state + } + return { + ...state, unseenTotalCount: payload.unseenTotalCount, } + case GET_ARCHIVED_NOTIFICATIONS_SUCCESS: return { ...state, @@ -80,11 +90,6 @@ const notifications = (state = defaultState, { type, payload }) => { }, }, } - case GET_UNSEEN_NOTIFICATIONS_PERIODICALLY_SUCCESS: - return { - ...state, - unseenTotalCount: payload.totalCount, - } case UPDATE_NOTIFICATION_STATUS_SUCCESS: return { ...state, diff --git a/pkg/webui/console/views/app/index.js b/pkg/webui/console/views/app/index.js index 9792dc97dfd..06ec93b0d88 100644 --- a/pkg/webui/console/views/app/index.js +++ b/pkg/webui/console/views/app/index.js @@ -54,8 +54,6 @@ import { } from '@ttn-lw/lib/selectors/env' import { uuid as uuidRegexp } from '@ttn-lw/lib/regexp' -import { getUnseenNotificationsPeriodically } from '@console/store/actions/notifications' - import { selectUser, selectUserFetching, @@ -125,17 +123,6 @@ const Layout = () => { }, [isDrawerOpen, openDrawer, closeDrawer]) // End of mobile side menu drawer functionality - // Fetch unseen notifications periodically, in order to update the state and - // and the new notifications dot in the header. - // Using this useEffect because the action to update the state is not dispatched within the - // setInterval() in the middleware logic. - useEffect(() => { - const timer = setInterval(() => { - dispatch(getUnseenNotificationsPeriodically()) - }, 1000 * 60 * 5) // 5 minutes - return () => clearInterval(timer) - }, [dispatch]) - return ( <> diff --git a/pkg/webui/styles/variables/tokens.styl b/pkg/webui/styles/variables/tokens.styl index 98010d0f6cb..cf1f0921759 100644 --- a/pkg/webui/styles/variables/tokens.styl +++ b/pkg/webui/styles/variables/tokens.styl @@ -119,6 +119,7 @@ $tokens = { // Neutral + 'border-neutral-min': $c.neutral-white // Border that needs to appear as background color. 'border-neutral-extralight': $c.neutral-050 // Border for subtle table raws border bottoms. 'border-neutral-light': $c.neutral-100 // Border for divider lines and default element borders. 'border-neutral-normal': $c.neutral-300 // Border for default input borders.