Skip to content

Commit 1078ca1

Browse files
authored
feat(ui): allow EIP children to have a different namespace than parent node (#34)
1 parent fb783ab commit 1078ca1

37 files changed

+750
-464
lines changed

ui/genApiFromSchema.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import path from "node:path"
1010
// TODO: Use git tags instead of commit hashes
1111
// To use an updated source schema, change this to point to the desired version.
1212

13-
const COMMIT_HASH = "47b4b770e33f576da45ec1b5156d3b94d744d2ee"
13+
const COMMIT_HASH = "26f958d0d923b87bb3f7b598763c63493a42f770"
1414

1515
const SCHEMAS = ["eipComponentDef.schema.json", "eipFlow.schema.json"]
1616

ui/src/api/flow.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { BuiltInEdge, Edge, Node } from "@xyflow/react"
22
import { Attribute } from "./generated/eipComponentDef"
3-
import { Attributes } from "./generated/eipFlow"
3+
import { Attributes, EipId } from "./generated/eipFlow"
44

55
export interface Layout {
66
orientation: "horizontal" | "vertical"
@@ -40,7 +40,7 @@ export const isFollowerNode = (node?: Node): node is FollowerNode =>
4040
export type CustomNode = EipFlowNode | FollowerNode
4141

4242
export interface ChannelMapping {
43-
mapperName: string
43+
mapperId: EipId
4444
matcher: Attribute
4545
matcherValue?: string
4646
isDefaultMapping?: boolean
@@ -65,13 +65,13 @@ export const isDynamicEdge = (edge?: Edge): edge is DynamicEdge =>
6565
edge?.type === DYNAMIC_EDGE_TYPE
6666

6767
export interface RouterKeyDef {
68-
name: string
68+
eipId: EipId
6969
type: "attribute" | "child"
7070
attributesDef: Attribute[]
7171
}
7272

7373
export interface RouterKey {
74-
name: string
74+
eipId: EipId
7575
attributes?: Attributes
7676
}
7777

ui/src/api/generated/eipComponentDef.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,18 @@ export interface EipComponentDefinition {
4848
* The base schema for all EIP Elements (e.g. EipComponent, EipChildElement)
4949
*/
5050
export interface EipElement {
51-
name: string;
51+
eipId: EipId;
5252
description?: string;
5353
attributes?: Attribute[];
5454
childGroup?: EipChildGroup;
5555
}
56+
/**
57+
* A combination of fields uniquely identifying a component in an 'EipComponentDefinition'
58+
*/
59+
export interface EipId {
60+
namespace: string;
61+
name: string;
62+
}
5663
/**
5764
* Defines an EIP attribute
5865
*/

ui/src/api/generated/eipFlow.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export interface Attributes {
6868
* An instance of an 'EipChildElement'. Can recursively contain more child nodes as well as attributes of its own.
6969
*/
7070
export interface EipChildNode {
71-
name: string;
71+
eipId: EipId;
7272
attributes?: Attributes;
7373
children?: EipChildNode[];
7474
}

ui/src/components/config-panel/ChildManagementModal.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import {
1111
import { Close, SettingsEdit } from "@carbon/react/icons"
1212
import { ReactNode, useState } from "react"
1313
import { createPortal } from "react-dom"
14-
import { DEFAULT_NAMESPACE } from "../../api/flow"
1514
import {
1615
EipChildElement,
1716
EipComponent,
@@ -83,13 +82,9 @@ const ChildSelector = ({ parentId, childOptions }: ChildSelectorProps) => (
8382
id={"dropdown-child-selector"}
8483
label="Select child..."
8584
items={[null, ...childOptions]}
86-
itemToString={(child) => child?.name ?? ""}
85+
itemToString={(child) => child?.eipId.name ?? ""}
8786
onChange={({ selectedItem }) => {
88-
selectedItem &&
89-
enableChild(parentId, {
90-
namespace: DEFAULT_NAMESPACE,
91-
name: selectedItem.name,
92-
})
87+
selectedItem && enableChild(parentId, selectedItem.eipId)
9388
}}
9489
selectedItem={null}
9590
titleText={"Add a child"}

ui/src/components/config-panel/DynamicEdgeConfig.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,17 @@ const RouterKeyConfig = ({ routerNodeId, routerKeyDef }: RouterKeyProps) => {
9494
const handleUpdates = useMemo(
9595
() =>
9696
debounce((attrName: string, value: string) => {
97-
updateContentRouterKey(routerNodeId, routerKeyDef.name, attrName, value)
97+
updateContentRouterKey(
98+
routerNodeId,
99+
{
100+
namespace: routerKeyDef.eipId.namespace,
101+
name: routerKeyDef.eipId.name,
102+
},
103+
attrName,
104+
value
105+
)
98106
}, 300),
99-
[routerNodeId, routerKeyDef.name]
107+
[routerNodeId, routerKeyDef.eipId.name, routerKeyDef.eipId.namespace]
100108
)
101109

102110
const required = routerKeyDef.attributesDef
@@ -130,7 +138,7 @@ const RouterKeyConfig = ({ routerNodeId, routerKeyDef }: RouterKeyProps) => {
130138
title="Key"
131139
helperText={
132140
routerKeyDef.attributesDef.length !== 1
133-
? routerKeyDef.name
141+
? routerKeyDef.eipId.name
134142
: "Targeted by the matcher"
135143
}
136144
/>
@@ -142,7 +150,7 @@ const RouterKeyConfig = ({ routerNodeId, routerKeyDef }: RouterKeyProps) => {
142150

143151
// TODO: Allow mapping multiple values to the same channel
144152
const EdgeMatcher = ({ edgeId, mapping }: EdgeMatcherProps) => {
145-
const { mapperName, matcher, matcherValue } = mapping
153+
const { mapperId, matcher, matcherValue } = mapping
146154

147155
const handleUpdates = useMemo(
148156
() =>
@@ -155,7 +163,7 @@ const EdgeMatcher = ({ edgeId, mapping }: EdgeMatcherProps) => {
155163
return (
156164
<Section>
157165
<Stack gap={6}>
158-
<SectionHeading title="Matcher" helperText={mapperName} />
166+
<SectionHeading title="Matcher" helperText={mapperId.name} />
159167
<DescriptionTooltipWrapper
160168
key={matcher.name}
161169
id={matcher.name}

ui/src/components/config-panel/EipConfigSidePanel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const isDynamicRouterAttribute = (attribute: Attribute, eipId?: EipId) => {
3737
return false
3838
}
3939

40-
return keyDef.type === "attribute" && keyDef.name === attribute.name
40+
return keyDef.type === "attribute" && keyDef.eipId.name === attribute.name
4141
}
4242

4343
const filterConfigurableAttributes = (attrs?: Attribute[], eipId?: EipId) => {

ui/src/components/config-panel/childDefinitions.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import isDeepEqual from "fast-deep-equal"
12
import {
23
EipChildElement,
34
EipComponent,
@@ -17,8 +18,8 @@ export const findChildDefinition = (
1718
}
1819

1920
for (const id of childPath.slice(1)) {
20-
const name = getEipId(id)?.name
21-
child = children?.find((c) => c.name === name) ?? null
21+
const eipId = getEipId(id)
22+
child = children?.find((c) => isDeepEqual(c.eipId, eipId)) ?? null
2223
children = child?.childGroup?.children
2324
}
2425

ui/src/components/palette/EipComponentPanel.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ const EipNamespaceCollection = ({
6262
allExpanded,
6363
}: EipBlockCollectionProps) => {
6464
const eipItems = namespace.components.map((c) => (
65-
<EipItem key={c.name} eipId={{ namespace: namespace.name, name: c.name }} />
65+
<EipItem key={c.eipId.name} eipId={c.eipId} />
6666
))
6767

6868
return (
@@ -82,10 +82,12 @@ const namespacesToDisplay = (
8282
): Namespace[] => {
8383
const withFilteredComponents = entries.map(([namespace, components]) => {
8484
const filtered = searchTerm
85-
? components.filter((c) => c.name.toLowerCase().includes(searchTerm))
85+
? components.filter((c) =>
86+
c.eipId.name.toLowerCase().includes(searchTerm)
87+
)
8688
: components
8789

88-
filtered.sort((a, b) => a.name.localeCompare(b.name))
90+
filtered.sort((a, b) => a.eipId.name.localeCompare(b.eipId.name))
8991
return { name: namespace, components: filtered }
9092
})
9193

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { expect, test } from "vitest"
2+
import { DEFAULT_NAMESPACE } from "../../../api/flow"
3+
import { fuzzyEipMatch } from "./fuzzyEipIdMatch"
4+
5+
test("exact match", () => {
6+
const eipId = { namespace: DEFAULT_NAMESPACE, name: "filter" }
7+
const result = fuzzyEipMatch(eipId)
8+
expect(result).toEqual(eipId)
9+
})
10+
11+
test("partial match in the same namespace", () => {
12+
const eipId = { namespace: "jms", name: "test-adapter" }
13+
const result = fuzzyEipMatch(eipId)
14+
expect(result).toEqual({ namespace: "jms", name: "inbound-channel-adapter" })
15+
})
16+
17+
test("exact name match in default namespace", () => {
18+
const eipId = { namespace: "jms", name: "aggregator" }
19+
const result = fuzzyEipMatch(eipId)
20+
expect(result).toEqual({ namespace: DEFAULT_NAMESPACE, name: "aggregator" })
21+
})
22+
23+
test("no partial match in the same namespace -> fallback to default namespace", () => {
24+
const eipId = { namespace: "jms", name: "test-split-example" }
25+
const result = fuzzyEipMatch(eipId)
26+
expect(result).toEqual({ namespace: DEFAULT_NAMESPACE, name: "splitter" })
27+
})
28+
29+
test("no partial matches -> return same input EipId", () => {
30+
const eipId = { namespace: "jms", name: "unknown" }
31+
const result = fuzzyEipMatch(eipId)
32+
expect(result).toEqual({ namespace: "jms", name: "unknown" })
33+
})
34+
35+
test("unknown namespace -> return same input EipId", () => {
36+
const eipId = { namespace: "ns_unknown", name: "unknown" }
37+
const result = fuzzyEipMatch(eipId)
38+
expect(result).toEqual({ namespace: "ns_unknown", name: "unknown" })
39+
})

0 commit comments

Comments
 (0)