Skip to content

Commit

Permalink
console: Fix periodical notification updates
Browse files Browse the repository at this point in the history
  • Loading branch information
kschiffer committed Feb 6, 2024
1 parent aa6187a commit feac77a
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 36 deletions.
18 changes: 16 additions & 2 deletions pkg/webui/console/store/actions/notifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 [
Expand All @@ -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)
72 changes: 58 additions & 14 deletions pkg/webui/console/store/middleware/logics/notifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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 }) => {
Expand All @@ -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,
)
},
})

Expand Down Expand Up @@ -135,6 +178,7 @@ const markAllAsSeenLogic = createRequestLogic({

export default [
getInboxNotificationsLogic,
refreshNotificationsLogic,
getArchivedNotificationsLogic,
getUnseenNotificationsPeriodicallyLogic,
updateNotificationStatusLogic,
Expand Down
19 changes: 12 additions & 7 deletions pkg/webui/console/store/reducers/notifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,15 @@
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'

const defaultState = {
notifications: {
inbox: { entities: [], totalCount: 0 },
archived: { entities: [], totalCount: 0 },
unseen: { entities: [], totalCount: 0 },
},
unseenTotalCount: undefined,
}
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down
13 changes: 0 additions & 13 deletions pkg/webui/console/views/app/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 (
<>
<ScrollRestoration getKey={getScrollRestorationKey} />
Expand Down
1 change: 1 addition & 0 deletions pkg/webui/styles/variables/tokens.styl
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit feac77a

Please sign in to comment.