diff --git a/redisinsight/ui/src/components/bulk-actions-config/BulkActionsConfig.spec.tsx b/redisinsight/ui/src/components/bulk-actions-config/BulkActionsConfig.spec.tsx
index 2d7742da38..5b3edaf369 100644
--- a/redisinsight/ui/src/components/bulk-actions-config/BulkActionsConfig.spec.tsx
+++ b/redisinsight/ui/src/components/bulk-actions-config/BulkActionsConfig.spec.tsx
@@ -4,13 +4,13 @@ import React from 'react'
import MockedSocket from 'socket.io-mock'
import socketIO from 'socket.io-client'
import { cleanup, mockedStore, render } from 'uiSrc/utils/test-utils'
-import { BulkActionsServerEvent, BulkActionsType, SocketEvent } from 'uiSrc/constants'
+import { BulkActionsServerEvent, BulkActionsStatus, BulkActionsType, SocketEvent } from 'uiSrc/constants'
import {
bulkActionsDeleteSelector,
bulkActionsSelector,
disconnectBulkDeleteAction,
setBulkActionConnected,
- setBulkDeleteLoading
+ setBulkDeleteLoading, setDeleteOverviewStatus
} from 'uiSrc/slices/browser/bulkActions'
import BulkActionsConfig from './BulkActionsConfig'
@@ -110,6 +110,7 @@ describe('BulkActionsConfig', () => {
const afterRenderActions = [
setBulkActionConnected(true),
setBulkDeleteLoading(true),
+ setDeleteOverviewStatus(BulkActionsStatus.Disconnected),
disconnectBulkDeleteAction(),
]
expect(store.getActions()).toEqual([...afterRenderActions])
diff --git a/redisinsight/ui/src/components/bulk-actions-config/BulkActionsConfig.tsx b/redisinsight/ui/src/components/bulk-actions-config/BulkActionsConfig.tsx
index 410bb5458a..772fb2f377 100644
--- a/redisinsight/ui/src/components/bulk-actions-config/BulkActionsConfig.tsx
+++ b/redisinsight/ui/src/components/bulk-actions-config/BulkActionsConfig.tsx
@@ -11,6 +11,7 @@ import {
setDeleteOverview,
setBulkActionsInitialState,
bulkActionsDeleteSelector,
+ setDeleteOverviewStatus,
} from 'uiSrc/slices/browser/bulkActions'
import { getBaseApiUrl, Nullable } from 'uiSrc/utils'
import { sessionStorageService } from 'uiSrc/services'
@@ -21,11 +22,7 @@ import { BrowserStorageItem, BulkActionsServerEvent, BulkActionsStatus, BulkActi
import { addErrorNotification } from 'uiSrc/slices/app/notifications'
import { CustomHeaders } from 'uiSrc/constants/api'
-interface IProps {
- retryDelay?: number
-}
-
-const BulkActionsConfig = ({ retryDelay = 5000 } : IProps) => {
+const BulkActionsConfig = () => {
const { id: instanceId = '', db } = useSelector(connectedInstanceSelector)
const { isConnected } = useSelector(bulkActionsSelector)
const { isActionTriggered: isDeleteTriggered } = useSelector(bulkActionsDeleteSelector)
@@ -60,11 +57,8 @@ const BulkActionsConfig = ({ retryDelay = 5000 } : IProps) => {
// Catch disconnect
socketRef.current?.on(SocketEvent.Disconnect, () => {
- if (retryDelay) {
- retryTimer = setTimeout(handleDisconnect, retryDelay)
- } else {
- handleDisconnect()
- }
+ dispatch(setDeleteOverviewStatus(BulkActionsStatus.Disconnected))
+ handleDisconnect()
})
}, [instanceId, isDeleteTriggered])
@@ -147,10 +141,8 @@ const BulkActionsConfig = ({ retryDelay = 5000 } : IProps) => {
const onBulkDeleteAborted = (data: any) => {
dispatch(setBulkDeleteLoading(false))
sessionStorageService.set(BrowserStorageItem.bulkActionDeleteId, '')
-
- if (data.status === 'aborted') {
- dispatch(setDeleteOverview(data))
- }
+ dispatch(setDeleteOverview(data))
+ handleDisconnect()
}
useEffect(() => {
diff --git a/redisinsight/ui/src/components/monitor-config/MonitorConfig.spec.tsx b/redisinsight/ui/src/components/monitor-config/MonitorConfig.spec.tsx
index 5d0f8cc246..0b4e0fcfd6 100644
--- a/redisinsight/ui/src/components/monitor-config/MonitorConfig.spec.tsx
+++ b/redisinsight/ui/src/components/monitor-config/MonitorConfig.spec.tsx
@@ -9,7 +9,7 @@ import {
pauseMonitor,
setSocket,
stopMonitor,
- lockResume
+ lockResume, setLogFileId, setStartTimestamp
} from 'uiSrc/slices/cli/monitor'
import { cleanup, mockedStore, render } from 'uiSrc/utils/test-utils'
import { MonitorEvent, SocketEvent } from 'uiSrc/constants'
@@ -69,26 +69,47 @@ describe('MonitorConfig', () => {
it(`should emit ${MonitorEvent.Monitor} event`, () => {
const monitorSelectorMock = jest.fn().mockReturnValue({
isRunning: true,
+ isSaveToFile: true
})
monitorSelector.mockImplementation(monitorSelectorMock)
const { unmount } = render()
- socket.on(MonitorEvent.MonitorData, (data: []) => {
- expect(data).toEqual(['message1', 'message2'])
+ socket.socketClient.on(MonitorEvent.Monitor, (data: any) => {
+ expect(data).toEqual({ logFileId: expect.any(String) })
})
- socket.socketClient.emit(MonitorEvent.MonitorData, ['message1', 'message2'])
+ socket.socketClient.emit(SocketEvent.Connect)
const afterRenderActions = [
setSocket(socket),
- setMonitorLoadingPause(true)
+ setMonitorLoadingPause(true),
+ setLogFileId(expect.any(String)),
+ setStartTimestamp(expect.any(Number))
]
expect(store.getActions()).toEqual([...afterRenderActions])
unmount()
})
+ it(`should not emit ${MonitorEvent.Monitor} event when paused`, () => {
+ const monitorSelectorMock = jest.fn().mockReturnValue({
+ isRunning: true,
+ isPaused: true
+ })
+ monitorSelector.mockImplementation(monitorSelectorMock)
+
+ const { unmount } = render()
+ const mockedMonitorEvent = jest.fn()
+
+ socket.socketClient.on(MonitorEvent.Monitor, mockedMonitorEvent)
+ socket.socketClient.emit(SocketEvent.Connect)
+
+ expect(mockedMonitorEvent).not.toBeCalled()
+
+ unmount()
+ })
+
it('monitor should catch Exception', () => {
const { unmount } = render()
diff --git a/redisinsight/ui/src/components/monitor-config/MonitorConfig.tsx b/redisinsight/ui/src/components/monitor-config/MonitorConfig.tsx
index f61fc6ad21..8649203560 100644
--- a/redisinsight/ui/src/components/monitor-config/MonitorConfig.tsx
+++ b/redisinsight/ui/src/components/monitor-config/MonitorConfig.tsx
@@ -1,7 +1,7 @@
-import { useEffect } from 'react'
+import { useEffect, useRef } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { debounce } from 'lodash'
-import { io } from 'socket.io-client'
+import { io, Socket } from 'socket.io-client'
import { v4 as uuidv4 } from 'uuid'
import {
@@ -16,7 +16,7 @@ import {
setLogFileId,
pauseMonitor, lockResume
} from 'uiSrc/slices/cli/monitor'
-import { getBaseApiUrl } from 'uiSrc/utils'
+import { getBaseApiUrl, Nullable } from 'uiSrc/utils'
import { MonitorErrorMessages, MonitorEvent, SocketErrors, SocketEvent } from 'uiSrc/constants'
import { IMonitorDataPayload } from 'uiSrc/slices/interfaces'
import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances'
@@ -26,12 +26,18 @@ import { IMonitorData } from 'apiSrc/modules/profiler/interfaces/monitor-data.in
import ApiStatusCode from '../../constants/apiStatusCode'
interface IProps {
- retryDelay?: number;
+ retryDelay?: number
}
const MonitorConfig = ({ retryDelay = 15000 } : IProps) => {
const { id: instanceId = '' } = useSelector(connectedInstanceSelector)
const { socket, isRunning, isPaused, isSaveToFile, isMinimizedMonitor, isShowMonitor } = useSelector(monitorSelector)
+ const socketRef = useRef>(null)
+ const logFileIdRef = useRef()
+ const timestampRef = useRef()
+ const retryTimerRef = useRef()
+ const payloadsRef = useRef([])
+
const dispatch = useDispatch()
const setNewItems = debounce((items, onSuccess?) => {
@@ -52,56 +58,28 @@ const MonitorConfig = ({ retryDelay = 15000 } : IProps) => {
if (!isRunning || !instanceId || socket?.connected) {
return
}
- const logFileId = `_redis_${uuidv4()}`
- const timestamp = Date.now()
- let retryTimer: NodeJS.Timer
+
+ logFileIdRef.current = `_redis_${uuidv4()}`
+ timestampRef.current = Date.now()
// Create SocketIO connection to instance by instanceId
- const newSocket = io(`${getBaseApiUrl()}/monitor`, {
+ socketRef.current = io(`${getBaseApiUrl()}/monitor`, {
forceNew: true,
query: { instanceId },
extraHeaders: { [CustomHeaders.WindowId]: window.windowId || '' },
rejectUnauthorized: false,
})
- dispatch(setSocket(newSocket))
- let payloads: IMonitorDataPayload[] = []
-
- const handleMonitorEvents = () => {
- dispatch(setMonitorLoadingPause(false))
- newSocket.on(MonitorEvent.MonitorData, (payload: IMonitorData[]) => {
- payloads = payloads.concat(payload)
-
- // set batch of payloads and then clear batch
- setNewItems(payloads, () => {
- payloads.length = 0
- // reset all timings after items were changed
- setNewItems.cancel()
- })
- })
- }
+ dispatch(setSocket(socketRef.current))
const handleDisconnect = () => {
- newSocket.removeAllListeners()
+ socketRef.current?.removeAllListeners()
dispatch(pauseMonitor())
dispatch(stopMonitor())
dispatch(lockResume())
}
- newSocket.on(SocketEvent.Connect, () => {
- // Trigger Monitor event
- clearTimeout(retryTimer)
-
- dispatch(setLogFileId(logFileId))
- dispatch(setStartTimestamp(timestamp))
- newSocket.emit(
- MonitorEvent.Monitor,
- { logFileId: isSaveToFile ? logFileId : null },
- handleMonitorEvents
- )
- })
-
// Catch exceptions
- newSocket.on(MonitorEvent.Exception, (payload) => {
+ socketRef.current?.on(MonitorEvent.Exception, (payload) => {
if (payload.status === ApiStatusCode.Forbidden) {
handleDisconnect()
dispatch(setError(MonitorErrorMessages.NoPerm))
@@ -109,26 +87,51 @@ const MonitorConfig = ({ retryDelay = 15000 } : IProps) => {
return
}
- payloads.push({ isError: true, time: `${Date.now()}`, ...payload })
- setNewItems(payloads, () => { payloads.length = 0 })
+ payloadsRef.current.push({ isError: true, time: `${Date.now()}`, ...payload })
+ setNewItems(payloadsRef.current, () => { payloads.length = 0 })
dispatch(pauseMonitor())
})
// Catch disconnect
- newSocket.on(SocketEvent.Disconnect, () => {
+ socketRef.current?.on(SocketEvent.Disconnect, () => {
if (retryDelay) {
- retryTimer = setTimeout(handleDisconnect, retryDelay)
+ retryTimerRef.current = setTimeout(handleDisconnect, retryDelay)
} else {
handleDisconnect()
}
})
// Catch connect error
- newSocket.on(SocketEvent.ConnectionError, (error) => {
- payloads.push({ isError: true, time: `${Date.now()}`, message: getErrorMessage(error) })
- setNewItems(payloads, () => { payloads.length = 0 })
+ socketRef.current?.on(SocketEvent.ConnectionError, (error) => {
+ payloadsRef.current.push({ isError: true, time: `${Date.now()}`, message: getErrorMessage(error) })
+ setNewItems(payloadsRef.current, () => { payloadsRef.current.length = 0 })
+ })
+ }, [instanceId, isRunning, isPaused])
+
+ useEffect(() => {
+ if (!isRunning) {
+ return
+ }
+
+ socketRef.current?.removeAllListeners(SocketEvent.Connect)
+ socketRef.current?.on(SocketEvent.Connect, () => {
+ // Trigger Monitor event
+ clearTimeout(retryTimerRef.current!)
+ dispatch(setLogFileId(logFileIdRef.current))
+ dispatch(setStartTimestamp(timestampRef.current))
+ if (!isPaused) {
+ subscribeMonitorEvents()
+ }
})
- }, [instanceId, isRunning, isSaveToFile])
+ }, [isRunning, isPaused])
+
+ useEffect(() => {
+ if (!isRunning || isPaused || !socketRef.current?.connected) {
+ return
+ }
+
+ subscribeMonitorEvents()
+ }, [isRunning, isPaused])
useEffect(() => {
if (!isRunning) return
@@ -150,6 +153,29 @@ const MonitorConfig = ({ retryDelay = 15000 } : IProps) => {
}
}, [socket, isRunning, isShowMonitor, isMinimizedMonitor])
+ const subscribeMonitorEvents = () => {
+ socketRef.current?.removeAllListeners(MonitorEvent.MonitorData)
+ socketRef.current?.emit(
+ MonitorEvent.Monitor,
+ { logFileId: isSaveToFile ? logFileIdRef.current : null },
+ handleMonitorEvents
+ )
+ }
+
+ const handleMonitorEvents = () => {
+ dispatch(setMonitorLoadingPause(false))
+ socketRef.current?.on(MonitorEvent.MonitorData, (payload: IMonitorData[]) => {
+ payloadsRef.current = payloadsRef.current.concat(payload)
+
+ // set batch of payloads and then clear batch
+ setNewItems(payloadsRef.current, () => {
+ payloadsRef.current.length = 0
+ // reset all timings after items were changed
+ setNewItems.cancel()
+ })
+ })
+ }
+
return null
}
diff --git a/redisinsight/ui/src/constants/bulkActions.ts b/redisinsight/ui/src/constants/bulkActions.ts
index 5a2d6b3945..7a8cea7403 100644
--- a/redisinsight/ui/src/constants/bulkActions.ts
+++ b/redisinsight/ui/src/constants/bulkActions.ts
@@ -20,6 +20,7 @@ export enum BulkActionsStatus {
Completed = 'completed',
Failed = 'failed',
Aborted = 'aborted',
+ Disconnected = 'disconnected'
}
export const MAX_BULK_ACTION_ERRORS_LENGTH = 500
diff --git a/redisinsight/ui/src/pages/browser/components/bulk-actions/BulkActionsInfo/BulkActionsInfo.spec.tsx b/redisinsight/ui/src/pages/browser/components/bulk-actions/BulkActionsInfo/BulkActionsInfo.spec.tsx
index ab9f512d99..6fd69b43e7 100644
--- a/redisinsight/ui/src/pages/browser/components/bulk-actions/BulkActionsInfo/BulkActionsInfo.spec.tsx
+++ b/redisinsight/ui/src/pages/browser/components/bulk-actions/BulkActionsInfo/BulkActionsInfo.spec.tsx
@@ -1,6 +1,6 @@
import React from 'react'
import { mock } from 'ts-mockito'
-import { KeyTypes } from 'uiSrc/constants'
+import { BulkActionsStatus, KeyTypes } from 'uiSrc/constants'
import { render, screen } from 'uiSrc/utils/test-utils'
import BulkActionsInfo, { Props } from './BulkActionsInfo'
@@ -25,4 +25,10 @@ describe('BulkActionsInfo', () => {
expect(screen.queryByTestId('bulk-actions-info-filter')).not.toBeInTheDocument()
})
+
+ it('should show connection lost when status is disconnect', () => {
+ render()
+
+ expect(screen.getByTestId('bulk-status-disconnected')).toHaveTextContent('Connection Lost')
+ })
})
diff --git a/redisinsight/ui/src/pages/browser/components/bulk-actions/BulkActionsInfo/BulkActionsInfo.tsx b/redisinsight/ui/src/pages/browser/components/bulk-actions/BulkActionsInfo/BulkActionsInfo.tsx
index c46b95c658..141ca2e9e8 100644
--- a/redisinsight/ui/src/pages/browser/components/bulk-actions/BulkActionsInfo/BulkActionsInfo.tsx
+++ b/redisinsight/ui/src/pages/browser/components/bulk-actions/BulkActionsInfo/BulkActionsInfo.tsx
@@ -7,6 +7,7 @@ import { getApproximatePercentage, Maybe, Nullable } from 'uiSrc/utils'
import Divider from 'uiSrc/components/divider/Divider'
import { BulkActionsStatus, KeyTypes } from 'uiSrc/constants'
import GroupBadge from 'uiSrc/components/group-badge/GroupBadge'
+import { isProcessedBulkAction } from 'uiSrc/pages/browser/components/bulk-actions/utils'
import styles from './styles.module.scss'
export interface Props {
@@ -46,7 +47,7 @@ const BulkActionsInfo = (props: Props) => {
)}
- {!isUndefined(status) && status !== BulkActionsStatus.Completed && status !== BulkActionsStatus.Aborted && (
+ {!isUndefined(status) && !isProcessedBulkAction(status) && (
In progress:
{` ${getApproximatePercentage(total, scanned)}`}
@@ -62,6 +63,11 @@ const BulkActionsInfo = (props: Props) => {
Action completed
)}
+ {status === BulkActionsStatus.Disconnected && (
+
+ Connection Lost: {getApproximatePercentage(total, scanned)}
+
+ )}
{loading && (
diff --git a/redisinsight/ui/src/pages/browser/components/bulk-actions/utils.ts b/redisinsight/ui/src/pages/browser/components/bulk-actions/utils.ts
index 04ee7430d0..74bca9c106 100644
--- a/redisinsight/ui/src/pages/browser/components/bulk-actions/utils.ts
+++ b/redisinsight/ui/src/pages/browser/components/bulk-actions/utils.ts
@@ -9,3 +9,4 @@ export const isProcessedBulkAction = (status?: BulkActionsStatus) =>
status === BulkActionsStatus.Completed
|| status === BulkActionsStatus.Aborted
|| status === BulkActionsStatus.Failed
+ || status === BulkActionsStatus.Disconnected
diff --git a/redisinsight/ui/src/slices/browser/bulkActions.ts b/redisinsight/ui/src/slices/browser/bulkActions.ts
index 6678bac398..d79db8cdca 100644
--- a/redisinsight/ui/src/slices/browser/bulkActions.ts
+++ b/redisinsight/ui/src/slices/browser/bulkActions.ts
@@ -86,6 +86,12 @@ const bulkActionsSlice = createSlice({
}
},
+ setDeleteOverviewStatus: (state, { payload }) => {
+ if (state.bulkDelete.overview) {
+ state.bulkDelete.overview.status = payload
+ }
+ },
+
disconnectBulkDeleteAction: (state) => {
state.bulkDelete.loading = false
state.bulkDelete.isActionTriggered = false
@@ -127,6 +133,7 @@ export const {
disconnectBulkDeleteAction,
toggleBulkDeleteActionTriggered,
setDeleteOverview,
+ setDeleteOverviewStatus,
setBulkActionsInitialState,
bulkDeleteSuccess,
bulkUpload,
diff --git a/redisinsight/ui/src/slices/tests/browser/bulkActions.spec.ts b/redisinsight/ui/src/slices/tests/browser/bulkActions.spec.ts
index 645de24d71..80bd4e4155 100644
--- a/redisinsight/ui/src/slices/tests/browser/bulkActions.spec.ts
+++ b/redisinsight/ui/src/slices/tests/browser/bulkActions.spec.ts
@@ -1,6 +1,6 @@
import { cloneDeep } from 'lodash'
import { AxiosError } from 'axios'
-import { BulkActionsType } from 'uiSrc/constants'
+import { BulkActionsStatus, BulkActionsType } from 'uiSrc/constants'
import reducer, {
bulkActionsSelector,
initialState,
@@ -19,7 +19,7 @@ import reducer, {
bulkUpload,
bulkUploadSuccess,
bulkUploadFailed,
- bulkUploadDataAction,
+ bulkUploadDataAction, setDeleteOverviewStatus,
} from 'uiSrc/slices/browser/bulkActions'
import { cleanup, initialStateDefault, mockedStore } from 'uiSrc/utils/test-utils'
import { apiService } from 'uiSrc/services'
@@ -271,6 +271,40 @@ describe('bulkActions slice', () => {
})
})
+ describe('setDeleteOverviewStatus', () => {
+ it('should properly set state', () => {
+ // Arrange
+ const currentState = {
+ ...initialState,
+ bulkDelete: {
+ ...initialState.bulkDelete,
+ overview: {
+ id: 1,
+ databaseId: '1',
+ duration: 300,
+ status: 'inprogress',
+ type: BulkActionsType.Delete,
+ summary: { processed: 1, succeed: 1, failed: 0, errors: [] },
+ }
+ }
+ }
+
+ const overviewState = {
+ ...currentState.bulkDelete.overview,
+ status: BulkActionsStatus.Disconnected
+ }
+
+ // Act
+ const nextState = reducer(currentState, setDeleteOverviewStatus(BulkActionsStatus.Disconnected))
+
+ // Assert
+ const rootState = Object.assign(initialStateDefault, {
+ browser: { bulkActions: nextState },
+ })
+ expect(bulkActionsDeleteOverviewSelector(rootState)).toEqual(overviewState)
+ })
+ })
+
describe('disconnectBulkDeleteAction', () => {
it('should properly set state', () => {
// Arrange