Skip to content

Commit 253ce38

Browse files
authored
Merge pull request #431
fix(18935): Fix the verification of connectivity limits on handles * chore(18935): update dependencies * fix(18935): fix deletion of connected edges * refactor(18935): improve rendering of dummy target * fix(18935): update connectable limits * refactor(18935): refactor connectable checks * refactor(18935): a bit of renaming * test(18935): add tests * chore(18935): eslint
1 parent 2136cbf commit 253ce38

File tree

9 files changed

+128
-57
lines changed

9 files changed

+128
-57
lines changed

hivemq-edge/src/frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
"react-i18next": "^14.1.1",
8282
"react-icons": "^5.0.1",
8383
"react-router-dom": "^6.11.2",
84-
"reactflow": "^11.10.2",
84+
"reactflow": "^11.11.3",
8585
"tippy.js": "^6.3.7",
8686
"uuid": "^9.0.1",
8787
"zustand": "^4.4.7"

hivemq-edge/src/frontend/pnpm-lock.yaml

Lines changed: 27 additions & 27 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

hivemq-edge/src/frontend/src/extensions/datahub/components/controls/DeleteListener.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { FC, useMemo } from 'react'
22
import { useTranslation } from 'react-i18next'
33
import { useHotkeys } from 'react-hotkeys-hook'
4-
import { NodeRemoveChange, EdgeRemoveChange } from 'reactflow'
4+
import { NodeRemoveChange, EdgeRemoveChange, getConnectedEdges } from 'reactflow'
55
import { ListItem, Text, UnorderedList, useDisclosure, useToast, VStack } from '@chakra-ui/react'
66

77
import ConfirmationDialog from '@/components/Modal/ConfirmationDialog.tsx'
@@ -74,7 +74,10 @@ const DeleteListener: FC = () => {
7474

7575
const handleConfirmOnSubmit = () => {
7676
const { selectedNodes, selectedEdges } = selectedElements
77-
onEdgesChange(selectedEdges.map<EdgeRemoveChange>((edge) => ({ id: edge.id, type: 'remove' })))
77+
const allConnectedEdges = getConnectedEdges(selectedNodes, edges)
78+
onEdgesChange(
79+
[...selectedEdges, ...allConnectedEdges].map<EdgeRemoveChange>((edge) => ({ id: edge.id, type: 'remove' }))
80+
)
7881
onNodesChange(selectedNodes.map<NodeRemoveChange>((node) => ({ id: node.id, type: 'remove' })))
7982
setStatus(status === DesignerStatus.DRAFT ? DesignerStatus.DRAFT : DesignerStatus.MODIFIED)
8083
}

hivemq-edge/src/frontend/src/extensions/datahub/components/nodes/ConnectionLine.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import { Tag } from '@chakra-ui/react'
55
import { getConnectedNodeFrom } from '@datahub/utils/node.utils.ts'
66
import { NodeIcon } from '@datahub/components/helpers'
77

8+
const ICON_SIZE = 50
9+
const ICON_OFFSET = 20
10+
811
const ConnectionLine: FC<ConnectionLineComponentProps> = ({
912
fromHandle,
1013
fromNode,
@@ -33,7 +36,12 @@ const ConnectionLine: FC<ConnectionLineComponentProps> = ({
3336
<g>
3437
<path fill="none" stroke="grey" strokeWidth={1.5} className="animated" d={d} />
3538
{props.connectionStatus === null && (
36-
<foreignObject x={toX} y={toY - 20} width="50px" height="50px">
39+
<foreignObject
40+
x={toX - (props.fromPosition === 'left' ? ICON_SIZE : 0)}
41+
y={toY - ICON_OFFSET}
42+
width={`${ICON_SIZE}px`}
43+
height={`${ICON_SIZE}px`}
44+
>
3745
<Tag size="lg" colorScheme="gray" borderRadius="full" variant="outline">
3846
<NodeIcon type={droppedNode?.type} />
3947
</Tag>

hivemq-edge/src/frontend/src/extensions/datahub/components/nodes/CustomHandle.tsx

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { CSSProperties, FC, HTMLAttributes, useMemo } from 'react'
2-
import { getConnectedEdges, Handle, HandleProps, useNodeId } from 'reactflow'
2+
import { Handle, HandleProps, useNodeId } from 'reactflow'
33

44
import useDataHubDraftStore from '../../hooks/useDataHubDraftStore.ts'
5+
import { isNodeHandleConnectable } from '@datahub/utils/node.utils.ts'
56

6-
interface CustomHandleProps extends Omit<HandleProps & Omit<HTMLAttributes<HTMLDivElement>, 'id'>, 'isConnectable'> {
7+
interface CustomHandleProps
8+
extends Omit<HandleProps & Pick<HTMLAttributes<HTMLDivElement>, 'style' | 'className'>, 'isConnectable'> {
79
isConnectable?: boolean | number
810
}
911

@@ -12,22 +14,11 @@ export const CustomHandle: FC<CustomHandleProps> = (props) => {
1214
const nodeId = useNodeId()
1315

1416
const isHandleConnectable = useMemo(() => {
15-
if (typeof props.isConnectable === 'number') {
16-
const node = nodes.find((node) => node.id === nodeId)
17-
if (node) {
18-
const connectedEdges = getConnectedEdges([node], edges)
19-
20-
const toHandle = connectedEdges.filter((edge) => {
21-
const otherEnd = props.type === 'source' ? edge.sourceHandle : edge.targetHandle
22-
return otherEnd === props.id
23-
})
24-
25-
return toHandle.length < props.isConnectable
26-
}
27-
return false
28-
}
29-
return true
30-
}, [edges, nodeId, nodes, props.id, props.isConnectable, props.type])
17+
const node = nodes.find((node) => node.id === nodeId)
18+
if (!node) return false
19+
return isNodeHandleConnectable(props, node, edges)
20+
// eslint-disable-next-line react-hooks/exhaustive-deps
21+
}, [nodes.length, props, edges, nodeId])
3122

3223
let transform: CSSProperties = {
3324
width: '12px',

hivemq-edge/src/frontend/src/extensions/datahub/designer/operation/OperationNode.tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,7 @@ export const OperationNode: FC<NodeProps<OperationData>> = (props) => {
5454
</NodeWrapper>
5555
<CustomHandle type="target" position={Position.Left} id={OperationData.Handle.INPUT} />
5656
{!metadata?.isTerminal && (
57-
<CustomHandle
58-
type="source"
59-
position={Position.Right}
60-
id={OperationData.Handle.OUTPUT}
61-
// TODO[18935] bug with the isConnectable routine
62-
// isConnectable={1}
63-
/>
57+
<CustomHandle type="source" position={Position.Right} id={OperationData.Handle.OUTPUT} isConnectable={1} />
6458
)}
6559
{isSerialiser && <CustomHandle type="target" position={Position.Top} id={OperationData.Handle.SCHEMA} />}
6660
{isTransform && (

hivemq-edge/src/frontend/src/extensions/datahub/designer/topic_filter/TopicFilterNode.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export const TopicFilterNode: FC<NodeProps<TopicFilterData>> = (props) => {
3030
{data.topics?.map((t, index) => (
3131
<CustomHandle
3232
type="source"
33+
isConnectable={1}
3334
position={Position.Right}
3435
id={`${t}-${index}`}
3536
key={`${id}-${t}-${index}`}

hivemq-edge/src/frontend/src/extensions/datahub/utils/node.utils.spec.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import { MOCK_DEFAULT_NODE } from '@/__test-utils__/react-flow/nodes.ts'
55
import { DataPolicyValidator } from '@/api/__generated__'
66
import { MOCK_JSONSCHEMA_SCHEMA } from '@datahub/__test-utils__/schema.mocks.ts'
77
import {
8+
ConnectableHandleProps,
89
getAllParents,
910
getNodeId,
1011
getNodePayload,
12+
isNodeHandleConnectable,
1113
isValidPolicyConnection,
1214
reduceIdsFrom,
1315
} from '@datahub/utils/node.utils.ts'
@@ -337,3 +339,58 @@ describe('reduceIdsFrom', () => {
337339
)
338340
})
339341
})
342+
343+
describe('isNodeHandleConnectable', () => {
344+
const nodes: Node[] = [
345+
{
346+
id: '1',
347+
data: {},
348+
position: { x: 0, y: 0 },
349+
},
350+
{
351+
id: '2',
352+
data: {},
353+
position: { x: 0, y: 0 },
354+
},
355+
{
356+
id: '3',
357+
data: {},
358+
position: { x: 0, y: 0 },
359+
},
360+
{
361+
id: '3',
362+
data: {},
363+
position: { x: 0, y: 0 },
364+
},
365+
]
366+
const edges: Edge[] = [
367+
{ id: '1', source: '1', target: '3', sourceHandle: 'source1', targetHandle: 'target3' },
368+
{ id: '2', source: '2', target: '3', sourceHandle: 'source2', targetHandle: 'target3' },
369+
{ id: '3', source: '3', target: '4', sourceHandle: 'source3', targetHandle: 'target4' },
370+
]
371+
372+
it('should detect connectivity', async () => {
373+
const handle: ConnectableHandleProps = { id: 'source', type: 'source', isConnectable: false }
374+
expect(isNodeHandleConnectable(handle, nodes[0], edges)).toBeFalsy()
375+
expect(isNodeHandleConnectable({ ...handle, isConnectable: undefined }, nodes[0], edges)).toBeFalsy()
376+
expect(isNodeHandleConnectable({ ...handle, isConnectable: 1 }, nodes[0], edges)).toBeTruthy()
377+
})
378+
379+
it('should detect connectivity', async () => {
380+
const handle: ConnectableHandleProps = { id: 'target1', type: 'target', isConnectable: false }
381+
expect(isNodeHandleConnectable({ ...handle, isConnectable: 1 }, nodes[0], edges)).toBeTruthy()
382+
})
383+
384+
it('should detect connectivity', async () => {
385+
const handle: ConnectableHandleProps = { id: 'source1', type: 'source', isConnectable: false }
386+
expect(isNodeHandleConnectable({ ...handle, isConnectable: 1 }, nodes[0], edges)).toBeFalsy()
387+
expect(isNodeHandleConnectable({ ...handle, isConnectable: 2 }, nodes[0], edges)).toBeTruthy()
388+
})
389+
390+
it('should detect connectivity', async () => {
391+
const handle: ConnectableHandleProps = { id: 'target3', type: 'target', isConnectable: false }
392+
expect(isNodeHandleConnectable({ ...handle, isConnectable: 1 }, nodes[2], edges)).toBeFalsy()
393+
expect(isNodeHandleConnectable({ ...handle, isConnectable: 2 }, nodes[2], edges)).toBeFalsy()
394+
expect(isNodeHandleConnectable({ ...handle, isConnectable: 3 }, nodes[2], edges)).toBeTruthy()
395+
})
396+
})

hivemq-edge/src/frontend/src/extensions/datahub/utils/node.utils.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Connection, Edge, getIncomers, getOutgoers, Node } from 'reactflow'
1+
import { Connection, Edge, getConnectedEdges, getIncomers, getOutgoers, HandleProps, Node } from 'reactflow'
22
import { v4 as uuidv4 } from 'uuid'
33
import { MOCK_JSONSCHEMA_SCHEMA } from '../__test-utils__/schema.mocks.ts'
44
import i18n from '@/config/i18n.config.ts'
@@ -349,3 +349,20 @@ export const reduceIdsFrom =
349349
}
350350
return acc
351351
}
352+
353+
export interface ConnectableHandleProps extends Pick<HandleProps, 'id' | 'type'> {
354+
isConnectable?: boolean | number
355+
}
356+
357+
export const isNodeHandleConnectable = (handle: ConnectableHandleProps, node: Node, edges: Edge[]) => {
358+
if (typeof handle.isConnectable === 'number') {
359+
const connectedEdges = getConnectedEdges([node], edges)
360+
const toHandle = connectedEdges.filter((edge) => {
361+
return handle.type === 'source'
362+
? edge.source === node.id && edge.sourceHandle === handle.id
363+
: edge.target === node.id && edge.targetHandle === handle.id
364+
})
365+
return toHandle.length < handle.isConnectable
366+
}
367+
return handle.isConnectable
368+
}

0 commit comments

Comments
 (0)