ISSUE-825 Improve refresh without WS (#826)
This commit is contained in:
@@ -60,6 +60,7 @@ describe('websocket messages storm', () => {
|
|||||||
;(getDisplayedCalendarRange as jest.Mock).mockReturnValue(mockRange)
|
;(getDisplayedCalendarRange as jest.Mock).mockReturnValue(mockRange)
|
||||||
;(store.getState as jest.Mock).mockReturnValue(mockState)
|
;(store.getState as jest.Mock).mockReturnValue(mockState)
|
||||||
window.WS_DEBOUNCE_PERIOD_MS = 500
|
window.WS_DEBOUNCE_PERIOD_MS = 500
|
||||||
|
window.WS_SKIP_DELAY_MS = 0
|
||||||
mockAccumulators.calendarsToRefresh = new Map<string, any>()
|
mockAccumulators.calendarsToRefresh = new Map<string, any>()
|
||||||
mockAccumulators.calendarsToHide = new Set()
|
mockAccumulators.calendarsToHide = new Set()
|
||||||
mockAccumulators.shouldRefreshCalendarListRef.current = false
|
mockAccumulators.shouldRefreshCalendarListRef.current = false
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ describe('updateCalendars', () => {
|
|||||||
mockAccumulators.currentDebouncePeriod = 0
|
mockAccumulators.currentDebouncePeriod = 0
|
||||||
mockAccumulators.shouldRefreshCalendarListRef.current = false
|
mockAccumulators.shouldRefreshCalendarListRef.current = false
|
||||||
mockAccumulators.debouncedUpdateFn = jest.fn()
|
mockAccumulators.debouncedUpdateFn = jest.fn()
|
||||||
|
window.WS_SKIP_DELAY_MS = 0
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not dispatch for non-object messages', () => {
|
it('should not dispatch for non-object messages', () => {
|
||||||
|
|||||||
+4
-1
@@ -1,4 +1,5 @@
|
|||||||
import eventsCalendar from '@/features/Calendars/CalendarSlice'
|
import eventsCalendar from '@/features/Calendars/CalendarSlice'
|
||||||
|
import { postMutationRefreshMiddleware } from '@/features/Calendars/listeners/postMutationRefresh'
|
||||||
import searchResultReducer from '@/features/Search/SearchSlice'
|
import searchResultReducer from '@/features/Search/SearchSlice'
|
||||||
import settingsReducer from '@/features/Settings/SettingsSlice'
|
import settingsReducer from '@/features/Settings/SettingsSlice'
|
||||||
import userReducer from '@/features/User/userSlice'
|
import userReducer from '@/features/User/userSlice'
|
||||||
@@ -24,7 +25,9 @@ export const setupStore = (preloadedState?: Partial<RootState>) => {
|
|||||||
reducer: rootReducer,
|
reducer: rootReducer,
|
||||||
preloadedState,
|
preloadedState,
|
||||||
middleware: getDefaultMiddleware =>
|
middleware: getDefaultMiddleware =>
|
||||||
getDefaultMiddleware().concat(routerMiddleware)
|
getDefaultMiddleware()
|
||||||
|
.concat(routerMiddleware)
|
||||||
|
.prepend(postMutationRefreshMiddleware.middleware)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
import type { AppDispatch, RootState } from '@/app/store'
|
||||||
|
import { findCalendarById, getDisplayedCalendarRange } from '@/utils'
|
||||||
|
import { createListenerMiddleware } from '@reduxjs/toolkit'
|
||||||
|
import {
|
||||||
|
deleteEventAsync,
|
||||||
|
deleteEventInstanceAsync,
|
||||||
|
moveEventAsync,
|
||||||
|
putEventAsync,
|
||||||
|
refreshCalendarWithSyncToken,
|
||||||
|
updateEventInstanceAsync,
|
||||||
|
updateSeriesAsync
|
||||||
|
} from '../services'
|
||||||
|
|
||||||
|
export const postMutationRefreshMiddleware = createListenerMiddleware()
|
||||||
|
|
||||||
|
const startListening = postMutationRefreshMiddleware.startListening.withTypes<
|
||||||
|
RootState,
|
||||||
|
AppDispatch
|
||||||
|
>()
|
||||||
|
|
||||||
|
function triggerRefresh(
|
||||||
|
dispatch: AppDispatch,
|
||||||
|
getState: () => RootState,
|
||||||
|
calId: string
|
||||||
|
): void {
|
||||||
|
const found = findCalendarById(getState(), calId)
|
||||||
|
if (!found) return
|
||||||
|
void dispatch(
|
||||||
|
refreshCalendarWithSyncToken({
|
||||||
|
calendar: found.calendar,
|
||||||
|
calType: found.type,
|
||||||
|
calendarRange: getDisplayedCalendarRange()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
startListening({
|
||||||
|
actionCreator: putEventAsync.fulfilled,
|
||||||
|
effect: (action, listenerApi) => {
|
||||||
|
triggerRefresh(
|
||||||
|
listenerApi.dispatch,
|
||||||
|
() => listenerApi.getState(),
|
||||||
|
action.payload.calId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
startListening({
|
||||||
|
actionCreator: deleteEventAsync.fulfilled,
|
||||||
|
effect: (action, listenerApi) => {
|
||||||
|
triggerRefresh(
|
||||||
|
listenerApi.dispatch,
|
||||||
|
() => listenerApi.getState(),
|
||||||
|
action.payload.calId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
startListening({
|
||||||
|
actionCreator: deleteEventInstanceAsync.fulfilled,
|
||||||
|
effect: (action, listenerApi) => {
|
||||||
|
triggerRefresh(
|
||||||
|
listenerApi.dispatch,
|
||||||
|
() => listenerApi.getState(),
|
||||||
|
action.payload.calId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
startListening({
|
||||||
|
actionCreator: updateEventInstanceAsync.fulfilled,
|
||||||
|
effect: (action, listenerApi) => {
|
||||||
|
triggerRefresh(
|
||||||
|
listenerApi.dispatch,
|
||||||
|
() => listenerApi.getState(),
|
||||||
|
action.payload.calId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
startListening({
|
||||||
|
actionCreator: moveEventAsync.fulfilled,
|
||||||
|
effect: (action, listenerApi) => {
|
||||||
|
triggerRefresh(
|
||||||
|
listenerApi.dispatch,
|
||||||
|
() => listenerApi.getState(),
|
||||||
|
action.payload.calId
|
||||||
|
)
|
||||||
|
const sourceCalId = action.meta.arg.newEvent.calId
|
||||||
|
if (sourceCalId && sourceCalId !== action.payload.calId) {
|
||||||
|
triggerRefresh(
|
||||||
|
listenerApi.dispatch,
|
||||||
|
() => listenerApi.getState(),
|
||||||
|
sourceCalId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
startListening({
|
||||||
|
actionCreator: updateSeriesAsync.fulfilled,
|
||||||
|
effect: (action, listenerApi) => {
|
||||||
|
triggerRefresh(
|
||||||
|
listenerApi.dispatch,
|
||||||
|
() => listenerApi.getState(),
|
||||||
|
action.meta.arg.cal.id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -13,6 +13,7 @@ import { parseMessage } from './parseMessage'
|
|||||||
import { UpdateCalendarsAccumulators } from './type/UpdateCalendarsAccumulators'
|
import { UpdateCalendarsAccumulators } from './type/UpdateCalendarsAccumulators'
|
||||||
|
|
||||||
const DEFAULT_DEBOUNCE_MS = 0
|
const DEFAULT_DEBOUNCE_MS = 0
|
||||||
|
const DEFAULT_WS_SKIP_DELAY_MS = 2000
|
||||||
|
|
||||||
function createDebouncedUpdate(
|
function createDebouncedUpdate(
|
||||||
debouncePeriodMs: number,
|
debouncePeriodMs: number,
|
||||||
@@ -39,7 +40,7 @@ function createDebouncedUpdate(
|
|||||||
resetShouldRefreshCalendarList()
|
resetShouldRefreshCalendarList()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
processCalendarsToRefresh(dispatch, currentRange, calendarsToProcess)
|
scheduleCalendarsRefresh(dispatch, currentRange, calendarsToProcess)
|
||||||
processCalendarsToHide(calendarsToHideSnapshot)
|
processCalendarsToHide(calendarsToHideSnapshot)
|
||||||
|
|
||||||
if (shouldRefresh) {
|
if (shouldRefresh) {
|
||||||
@@ -106,7 +107,7 @@ export function updateCalendars(
|
|||||||
accumulators.shouldRefreshCalendarListRef.current = false
|
accumulators.shouldRefreshCalendarListRef.current = false
|
||||||
|
|
||||||
try {
|
try {
|
||||||
processCalendarsToRefresh(dispatch, currentRange, calendarsToProcess)
|
scheduleCalendarsRefresh(dispatch, currentRange, calendarsToProcess)
|
||||||
processCalendarsToHide(calendarsToHideSnapshot)
|
processCalendarsToHide(calendarsToHideSnapshot)
|
||||||
if (shouldRefreshCalendarList) {
|
if (shouldRefreshCalendarList) {
|
||||||
dispatch(getCalendarsListAsync())
|
dispatch(getCalendarsListAsync())
|
||||||
@@ -117,6 +118,52 @@ export function updateCalendars(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- Helpers ---
|
// --- Helpers ---
|
||||||
|
|
||||||
|
function scheduleCalendarsRefresh(
|
||||||
|
dispatch: AppDispatch,
|
||||||
|
currentRange: { start: Date; end: Date },
|
||||||
|
calendarsMap: Map<string, { calendar: Calendar; type?: 'temp' }>
|
||||||
|
) {
|
||||||
|
const skipDelayMs = window.WS_SKIP_DELAY_MS ?? DEFAULT_WS_SKIP_DELAY_MS
|
||||||
|
|
||||||
|
if (skipDelayMs === 0) {
|
||||||
|
dispatchCalendarsRefresh(dispatch, currentRange, calendarsMap)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
const stateAfterDelay = store.getState()
|
||||||
|
const rangeAfterDelay = getDisplayedCalendarRange()
|
||||||
|
calendarsMap.forEach(({ calendar, type }, calId) => {
|
||||||
|
const current = findCalendarById(stateAfterDelay, calId)
|
||||||
|
if (current && current.calendar.syncToken !== calendar.syncToken) return
|
||||||
|
dispatch(
|
||||||
|
refreshCalendarWithSyncToken({
|
||||||
|
calendar: current?.calendar ?? calendar,
|
||||||
|
calType: type,
|
||||||
|
calendarRange: rangeAfterDelay
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}, skipDelayMs)
|
||||||
|
}
|
||||||
|
|
||||||
|
function dispatchCalendarsRefresh(
|
||||||
|
dispatch: AppDispatch,
|
||||||
|
currentRange: { start: Date; end: Date },
|
||||||
|
calendarsMap: Map<string, { calendar: Calendar; type?: 'temp' }>
|
||||||
|
) {
|
||||||
|
calendarsMap.forEach(({ calendar, type }) => {
|
||||||
|
dispatch(
|
||||||
|
refreshCalendarWithSyncToken({
|
||||||
|
calendar,
|
||||||
|
calType: type,
|
||||||
|
calendarRange: currentRange
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function accumulateCalendarsToRefresh(
|
function accumulateCalendarsToRefresh(
|
||||||
state: ReturnType<typeof store.getState>,
|
state: ReturnType<typeof store.getState>,
|
||||||
calendarPaths: Set<string>,
|
calendarPaths: Set<string>,
|
||||||
@@ -149,22 +196,6 @@ function accumulateCalendarsToHide(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function processCalendarsToRefresh(
|
|
||||||
dispatch: AppDispatch,
|
|
||||||
currentRange: { start: Date; end: Date },
|
|
||||||
calendarsMap: Map<string, { calendar: Calendar; type?: 'temp' }>
|
|
||||||
) {
|
|
||||||
calendarsMap.forEach(calendar => {
|
|
||||||
dispatch(
|
|
||||||
refreshCalendarWithSyncToken({
|
|
||||||
calendar: calendar.calendar,
|
|
||||||
calType: calendar.type,
|
|
||||||
calendarRange: currentRange
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function processCalendarsToHide(calendarsToHideSnapshot: Set<string>) {
|
function processCalendarsToHide(calendarsToHideSnapshot: Set<string>) {
|
||||||
if (calendarsToHideSnapshot.size === 0) return
|
if (calendarsToHideSnapshot.size === 0) return
|
||||||
|
|
||||||
|
|||||||
Vendored
+1
@@ -27,6 +27,7 @@ declare global {
|
|||||||
|
|
||||||
WEBSOCKET_URL: string
|
WEBSOCKET_URL: string
|
||||||
WS_DEBOUNCE_PERIOD_MS: number
|
WS_DEBOUNCE_PERIOD_MS: number
|
||||||
|
WS_SKIP_DELAY_MS: number
|
||||||
WS_PING_PERIOD_MS: number
|
WS_PING_PERIOD_MS: number
|
||||||
WS_PING_TIMEOUT_PERIOD_MS: number
|
WS_PING_TIMEOUT_PERIOD_MS: number
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user