Skip to content

Commit

Permalink
Merge pull request #98 from hivemq/fix/94/workspace-connection-status
Browse files Browse the repository at this point in the history
Fix(94): Connection Status are now updated on the workspace
  • Loading branch information
simon622 authored Sep 11, 2023
2 parents 08eda88 + c505b98 commit b71372e
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -1,33 +1,18 @@
import { useEffect, useMemo } from 'react'
import ReactFlow, { Background, ReactFlowState, useStore } from 'reactflow'
import { Outlet, useLocation } from 'react-router-dom'
import { useMemo } from 'react'
import ReactFlow, { Background } from 'reactflow'
import { Outlet } from 'react-router-dom'

import 'reactflow/dist/style.css'

import { EdgeTypes, NodeTypes } from '../types.ts'
import useGetFlowElements from '../hooks/useGetFlowElements.tsx'

import StatusListener from './controls/StatusListener.tsx'
import CanvasControls from './controls/CanvasControls.tsx'
import SelectionListener from './controls/SelectionListener.tsx'
import { NodeListener, NodeAdapter, NodeGroup, NodeBridge, NodeEdge } from './nodes/'
import MonitoringEdge from './edges/MonitoringEdge.tsx'

const addSelectedNodesState = (state: ReactFlowState) => (nodeIds: string[]) => state.addSelectedNodes(nodeIds)

const SelectionListener = () => {
const { state } = useLocation()
const addSelectedNodes = useStore(addSelectedNodesState)

useEffect(() => {
const { selectedAdapter } = state || {}
const { adapterId, type } = selectedAdapter || {}
if (!adapterId || !type) return

addSelectedNodes([`adapter@${adapterId}`])
}, [addSelectedNodes, state])

return null
}

const ReactFlowWrapper = () => {
const { nodes, edges, onNodesChange, onEdgesChange } = useGetFlowElements()
const nodeTypes = useMemo(
Expand Down Expand Up @@ -61,6 +46,7 @@ const ReactFlowWrapper = () => {
fitView
>
<SelectionListener />
<StatusListener />
<Background />
<CanvasControls />
<Outlet />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useEffect } from 'react'
import { useLocation } from 'react-router-dom'
import { ReactFlowState, useStore } from 'reactflow'

const addSelectedNodesState = (state: ReactFlowState) => (nodeIds: string[]) => state.addSelectedNodes(nodeIds)

const SelectionListener = () => {
const { state } = useLocation()
const addSelectedNodes = useStore(addSelectedNodesState)

useEffect(() => {
const { selectedAdapter } = state || {}
const { adapterId, type } = selectedAdapter || {}
if (!adapterId || !type) return

addSelectedNodes([`adapter@${adapterId}`])
}, [addSelectedNodes, state])

return null
}

export default SelectionListener
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useEffect } from 'react'
import { useReactFlow } from 'reactflow'

import { useGetAdaptersStatus } from '@/api/hooks/useConnection/useGetAdaptersStatus.tsx'
import { useGetBridgesStatus } from '@/api/hooks/useConnection/useGetBridgesStatus.tsx'

import { updateNodeStatus } from '../../utils/status-utils.ts'

const StatusListener = () => {
const { data: adapterConnections } = useGetAdaptersStatus()
const { data: bridgeConnections } = useGetBridgesStatus()
const { setNodes } = useReactFlow()

useEffect(() => {
if (adapterConnections?.items && bridgeConnections?.items) {
const updates = [...adapterConnections.items, ...bridgeConnections.items]
setNodes((currentNodes) => updateNodeStatus(currentNodes, updates))
}
}, [adapterConnections, bridgeConnections, setNodes])

return null
}

export default StatusListener
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { expect } from 'vitest'
import { Node, NodeProps } from 'reactflow'

import { Adapter, ConnectionStatus } from '@/api/__generated__'

import { MOCK_NODE_ADAPTER, MOCK_NODE_BRIDGE, MOCK_NODE_LISTENER } from '@/__test-utils__/react-flow/nodes.ts'
import { updateNodeStatus } from '@/modules/EdgeVisualisation/utils/status-utils.ts'
import { NodeTypes } from '@/modules/EdgeVisualisation/types.ts'
import { mockBridgeId } from '@/api/hooks/useGetBridges/__handlers__'
import { MOCK_ADAPTER_ID } from '@/__test-utils__/mocks.ts'

const disconnectedBridge: NodeProps = {
...MOCK_NODE_BRIDGE,
data: {
...MOCK_NODE_BRIDGE.data,
bridgeRuntimeInformation: {
connectionStatus: {
status: ConnectionStatus.status.DISCONNECTED,
},
},
},
}
const disconnectedAdapter: NodeProps<Adapter> = {
...MOCK_NODE_ADAPTER,
data: {
...MOCK_NODE_ADAPTER.data,
adapterRuntimeInformation: {
connectionStatus: {
status: ConnectionStatus.status.DISCONNECTED,
},
},
},
}

interface Suite {
nodes: Node[]
status: ConnectionStatus[]
expected: Node[]
}
const validationSuite: Suite[] = [
{
nodes: [],
status: [],
expected: [],
},
{
nodes: [],
status: [
{ status: ConnectionStatus.status.DISCONNECTED, type: NodeTypes.BRIDGE_NODE, id: 'one' },
{ status: ConnectionStatus.status.DISCONNECTED, type: NodeTypes.ADAPTER_NODE, id: 'two' },
],
expected: [],
},
{
// @ts-ignore
nodes: [disconnectedBridge, disconnectedAdapter],
status: [{ status: ConnectionStatus.status.CONNECTED, type: NodeTypes.BRIDGE_NODE, id: 'non-existing-bridge' }],
// @ts-ignore
expected: [disconnectedBridge, disconnectedAdapter],
},
{
// @ts-ignore
nodes: [MOCK_NODE_LISTENER],
status: [{ status: ConnectionStatus.status.CONNECTED, type: NodeTypes.BRIDGE_NODE, id: 'non-existing-bridge' }],
// @ts-ignore
expected: [MOCK_NODE_LISTENER],
},
{
// @ts-ignore
nodes: [disconnectedBridge, disconnectedAdapter],
status: [
{ status: ConnectionStatus.status.DISCONNECTED, type: NodeTypes.BRIDGE_NODE, id: mockBridgeId },
{ status: ConnectionStatus.status.DISCONNECTED, type: NodeTypes.ADAPTER_NODE, id: MOCK_ADAPTER_ID },
],
// @ts-ignore
expected: [disconnectedBridge, disconnectedAdapter],
},
{
// @ts-ignore
nodes: [disconnectedBridge, disconnectedAdapter],
status: [
{ status: ConnectionStatus.status.CONNECTING, type: NodeTypes.BRIDGE_NODE, id: mockBridgeId },
{ status: ConnectionStatus.status.CONNECTING, type: NodeTypes.ADAPTER_NODE, id: MOCK_ADAPTER_ID },
],
expected: expect.arrayContaining([
expect.objectContaining({
data: expect.objectContaining({
bridgeRuntimeInformation: expect.objectContaining({
connectionStatus: expect.objectContaining({ status: ConnectionStatus.status.CONNECTING }),
}),
}),
}),
]),
},
]

describe('updateNodeStatus', () => {
it.each<Suite>(validationSuite)('should work', ({ nodes, status, expected }) => {
const updatedNodes = updateNodeStatus(nodes, status)
expect(updatedNodes.length).toBe(nodes.length)
expect(updatedNodes).toStrictEqual(expected)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Node } from 'reactflow'
import { Adapter, Bridge, ConnectionStatus } from '@/api/__generated__'
import { NodeTypes } from '@/modules/EdgeVisualisation/types.ts'

export const updateNodeStatus = (currentNodes: Node[], updates: ConnectionStatus[]) => {
return currentNodes.map((n) => {
if (n.type === NodeTypes.BRIDGE_NODE) {
const newData = { ...n.data } as Bridge
const newStatus = updates.find((s) => s.id === newData.id)
if (!newStatus) return n
if (newStatus.status === newData.bridgeRuntimeInformation?.connectionStatus?.status) return n

n.data = {
...newData,
bridgeRuntimeInformation: {
connectionStatus: newStatus,
},
}
return n
}
if (n.type === NodeTypes.ADAPTER_NODE) {
const newData = { ...n.data } as Adapter
const newStatus = updates.find((s) => s.id === newData.id)
if (!newStatus) return n
if (newStatus.status === newData.adapterRuntimeInformation?.connectionStatus?.status) return n

n.data = {
...newData,
adapterRuntimeInformation: {
connectionStatus: newStatus,
},
}
return n
}
return n
})
}

0 comments on commit b71372e

Please sign in to comment.