This is an automated email from the ASF dual-hosted git repository.

marat pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-karavan.git

commit 6e2b259a55637ad13637f901fa1494337c3ab70d
Author: Marat Gubaidullin <[email protected]>
AuthorDate: Mon Jun 3 17:11:23 2024 -0400

    Fix #1309
---
 karavan-core/src/core/api/TopologyUtils.ts         |  50 +++-
 karavan-core/src/core/model/TopologyDefinition.ts  |  18 +-
 karavan-core/test/topology.spec.ts                 |   2 +-
 karavan-designer/public/example/demo.camel.yaml    | 258 ++++++++++++++++++++-
 .../src/designer/route/DslConnections.tsx          |   2 +-
 karavan-designer/src/topology/CustomNode.tsx       |   2 +-
 karavan-designer/src/topology/TopologyApi.tsx      |  84 +++++--
 .../src/topology/TopologyPropertiesPanel.tsx       |  21 +-
 karavan-designer/src/topology/TopologyStore.ts     |  15 +-
 karavan-designer/src/topology/TopologyTab.tsx      |  14 +-
 karavan-designer/src/topology/TopologyToolbar.tsx  |  22 +-
 karavan-designer/src/topology/topology.css         |  17 +-
 12 files changed, 455 insertions(+), 50 deletions(-)

diff --git a/karavan-core/src/core/api/TopologyUtils.ts 
b/karavan-core/src/core/api/TopologyUtils.ts
index db76667e..a3bd8a3d 100644
--- a/karavan-core/src/core/api/TopologyUtils.ts
+++ b/karavan-core/src/core/api/TopologyUtils.ts
@@ -22,7 +22,7 @@ import {
     PatchDefinition,
     PostDefinition,
     PutDefinition,
-    RestDefinition, SagaDefinition,
+    RestDefinition, RouteConfigurationDefinition, SagaDefinition,
 } from '../model/CamelDefinition';
 import {
     CamelElement,
@@ -31,7 +31,7 @@ import {
 import {
     TopologyIncomingNode,
     TopologyOutgoingNode,
-    TopologyRestNode,
+    TopologyRestNode, TopologyRouteConfigurationNode,
     TopologyRouteNode,
 } from '../model/TopologyDefinition';
 import { ComponentApi } from './ComponentApi';
@@ -211,7 +211,22 @@ export class TopologyUtils {
         return result;
     }
 
-    static findTopologyOutgoingNodes = (integrations: Integration[]): 
TopologyOutgoingNode[] => {
+    static findTopologyRouteConfigurationNodes = (integration: Integration[]): 
TopologyRouteConfigurationNode[] => {
+        const result:TopologyRouteConfigurationNode[] = [];
+        integration.forEach(i => {
+            const filename = i.metadata.name;
+            const routes = i.spec.flows?.filter(flow => flow.dslName === 
'RouteConfigurationDefinition');
+            const routeElements = routes?.map(r => {
+                const id = 'route-' + r.id;
+                const title = '' + (r.description ? r.description : r.id)
+                return new TopologyRouteConfigurationNode(id, r.id, title, 
filename, r);
+            }) || [];
+            result.push(...routeElements)
+        })
+        return result;
+    }
+
+    static findTopologyRouteOutgoingNodes = (integrations: Integration[]): 
TopologyOutgoingNode[] => {
         const result:TopologyOutgoingNode[] = [];
         integrations.forEach(i => {
             const filename = i.metadata.name;
@@ -233,6 +248,35 @@ export class TopologyUtils {
         return result;
     }
 
+    static findTopologyRouteConfigurationOutgoingNodes = (integrations: 
Integration[]): TopologyOutgoingNode[] => {
+        const result:TopologyOutgoingNode[] = [];
+        integrations.forEach(i => {
+            const filename = i.metadata.name;
+            const rcs = i.spec.flows?.filter(flow => flow.dslName === 
'RouteConfigurationDefinition');
+            rcs?.forEach((rc: RouteConfigurationDefinition) => {
+                const children: CamelElement[] = [];
+                children.push(...rc.intercept || []);
+                children.push(...rc.interceptFrom || []);
+                children.push(...rc.interceptSendToEndpoint || []);
+                children.push(...rc.onCompletion || []);
+                children.push(...rc.onException || []);
+                children.forEach(child => {
+                    const elements = TopologyUtils.findOutgoingInStep(child, 
[]);
+                    elements.forEach((e: any) => {
+                        const id = 'outgoing-' + rc.id + '-' + e.id;
+                        const title = CamelDisplayUtil.getStepDescription(e);
+                        const type = 
TopologyUtils.isElementInternalComponent(e) ? 'internal' : 'external';
+                        const connectorType = 
TopologyUtils.getConnectorType(e);
+                        const uniqueUri = TopologyUtils.getUniqueUri(e);
+                        result.push(new TopologyOutgoingNode(id, type, 
connectorType, rc.id || 'default', title, filename, e, uniqueUri));
+                    })
+                })
+            })
+
+        })
+        return result;
+    }
+
     static findOutgoingInStep = (step: CamelElement, result: CamelElement[]): 
CamelElement[] => {
         if (step !== undefined) {
             const el = (step as any);
diff --git a/karavan-core/src/core/model/TopologyDefinition.ts 
b/karavan-core/src/core/model/TopologyDefinition.ts
index f238f5b9..c4ad5f2f 100644
--- a/karavan-core/src/core/model/TopologyDefinition.ts
+++ b/karavan-core/src/core/model/TopologyDefinition.ts
@@ -16,7 +16,7 @@
  */
 
 import { CamelElement } from './IntegrationDefinition';
-import { FromDefinition, RestDefinition, RouteDefinition } from 
'./CamelDefinition';
+import { FromDefinition, RestDefinition, RouteConfigurationDefinition, 
RouteDefinition } from './CamelDefinition';
 
 export class TopologyRestNode {
     path: string;
@@ -77,6 +77,22 @@ export class TopologyRouteNode {
     }
 }
 
+export class TopologyRouteConfigurationNode {
+    id: string;
+    routeConfigurationId: string;
+    title: string;
+    fileName: string;
+    routeConfiguration: RouteConfigurationDefinition
+
+    constructor(id: string, routeConfigurationId: string, title: string, 
fileName: string, routeConfiguration: RouteConfigurationDefinition) {
+        this.id = id;
+        this.routeConfigurationId = routeConfigurationId;
+        this.title = title;
+        this.fileName = fileName;
+        this.routeConfiguration = routeConfiguration;
+    }
+}
+
 export class TopologyOutgoingNode {
     id: string;
     type: 'internal' | 'external';
diff --git a/karavan-core/test/topology.spec.ts 
b/karavan-core/test/topology.spec.ts
index db689f21..3f57e19e 100644
--- a/karavan-core/test/topology.spec.ts
+++ b/karavan-core/test/topology.spec.ts
@@ -29,7 +29,7 @@ describe('Topology functions', () => {
         const i2 = CamelDefinitionYaml.yamlToIntegration("test1.yaml", yaml2);
         const tin = TopologyUtils.findTopologyIncomingNodes([i1, i2]);
         const trn = TopologyUtils.findTopologyRestNodes([i1, i2]);
-        const ton = TopologyUtils.findTopologyOutgoingNodes([i1, i2]);
+        const ton = TopologyUtils.findTopologyRouteOutgoingNodes([i1, i2]);
     });
 
 });
diff --git a/karavan-designer/public/example/demo.camel.yaml 
b/karavan-designer/public/example/demo.camel.yaml
index 2c843d16..72b56e9b 100644
--- a/karavan-designer/public/example/demo.camel.yaml
+++ b/karavan-designer/public/example/demo.camel.yaml
@@ -1,9 +1,257 @@
+- rest:
+    id: rest-328e
+    get:
+      - id: get-5ab7
+        to: direct:hello
+- beans:
+    - name: RebateDatabase
+      type: "#class:org.apache.commons.dbcp2.BasicDataSource"
+      properties:
+        username: "{{secret:database-secret/username}}"
+        password: "{{secret:database-secret/password}}"
+        url: "{{secret:database-secret/url}}"
+        driverClassName: org.postgresql.Driver
 - route:
-    id: route-d1dc
-    nodePrefixId: route-0f1
+    id: route-0dc7
+    description: Audit Start
+    nodePrefixId: route-972
     from:
-      id: from-852d
+      id: from-846a
+      description: Audit Start
       uri: direct
+      parameters:
+        name: start
       steps:
-        - loadBalance:
-            id: loadBalance-d8f0
+        - to:
+            id: to-3597
+            uri: kafka
+            parameters:
+              topic: audit
+- route:
+    id: route-a54e
+    description: Audit Finish
+    nodePrefixId: route-1d1
+    from:
+      id: from-47d5
+      description: Audit Finish
+      uri: direct
+      parameters:
+        name: finish
+      steps:
+        - to:
+            id: to-3cf2
+            uri: kafka
+            parameters:
+              topic: audit
+- route:
+    id: route-07ed
+    description: Audit Step
+    nodePrefixId: route-833
+    from:
+      id: from-e007
+      uri: direct
+      parameters:
+        name: step
+      steps:
+        - to:
+            id: to-fa9e
+            uri: kafka
+            parameters:
+              topic: audit
+- route:
+    id: route-715c
+    description: Consume Orders
+    nodePrefixId: route-4f3
+    routeConfigurationId: auditedRoute
+    from:
+      id: from-7adf
+      description: From Kafka
+      uri: kamelet:kafka-scram-source
+      variableReceive: orderJson
+      parameters:
+        bootstrapServers: "{{secret:kafka-secret/bootstrapServers}}"
+        securityProtocol: "{{secret:kafka-secret/securityProtocol}}"
+        saslMechanism: "{{secret:kafka-secret/saslMechanism}}"
+        user: "{{secret:kafka-secret/userName}}"
+        password: "{{secret:kafka-secret/password}}"
+        topic: orders
+        autoCommitEnable: true
+        autoOffsetReset: earliest
+        consumerGroup: ordrer-consumer-1
+      steps:
+        - removeHeaders:
+            id: removeHeaders-c67b
+            pattern: "*"
+        - unmarshal:
+            id: unmarshal-9c0d
+            variableSend: orderJson
+            variableReceive: order
+            json:
+              id: json-0b83
+        - choice:
+            id: choice-f872
+            when:
+              - id: when-c007
+                description: Special Orders
+                expression:
+                  groovy:
+                    id: groovy-15e2
+                    expression: variables.order.type == 'SPECIAL'
+                steps:
+                  - log:
+                      id: log-735e
+                      message: "SPECIAL:  ${variable.order}"
+                  - to:
+                      id: to-7a02
+                      uri: direct
+                      parameters:
+                        name: special
+            otherwise:
+              id: otherwise-d9a2
+              steps:
+                - log:
+                    id: log-f600
+                    message: "ORDINAL: ${variable.order}"
+                - to:
+                    id: to-c45b
+                    uri: direct
+                    parameters:
+                      name: ordinal
+- route:
+    id: route-1695
+    description: Enrich Ordinal Order
+    nodePrefixId: route-f1b
+    from:
+      id: from-471d
+      uri: direct
+      parameters:
+        name: ordinal
+      steps:
+        - log:
+            id: log-202b
+            message: ${body}
+        - to:
+            id: to-f1b3
+            uri: direct
+            parameters:
+              name: send
+- route:
+    id: route-9a71
+    description: Send to HTTP
+    nodePrefixId: route-fdf
+    from:
+      id: from-b83d
+      uri: direct
+      parameters:
+        name: send
+      steps:
+        - log:
+            id: log-ca7f
+            message: "send: ${variable.order}"
+        - marshal:
+            id: marshal-a102
+            variableSend: order
+            variableReceive: orderOut
+            json:
+              id: json-d9be
+        - setHeaders:
+            id: setHeaders-2a6c
+            headers:
+              - id: setHeader-7dd5
+                name: X-API-Key
+                expression:
+                  simple:
+                    id: simple-e2a5
+                    expression: "{{secret:http-secret/X-API-Key}}"
+        - to:
+            id: to-fb94
+            description: Send to HTTP Service
+            variableSend: orderOut
+            uri: http
+            parameters:
+              httpUri: http-to-database.talisman/order
+              httpMethod: POST
+- route:
+    id: route-e9e4
+    description: Enrich Special Order
+    nodePrefixId: route-3ca
+    from:
+      id: from-b883
+      uri: direct
+      parameters:
+        name: special
+      steps:
+        - to:
+            id: to-6000
+            description: Get Rebate
+            variableReceive: rebate
+            uri: sql
+            parameters:
+              dataSource: "#bean:RebateDatabase"
+              query: SELECT * FROM REBATES WHERE NAME = 'SPECIAL'
+              outputType: SelectOne
+        - log:
+            id: log-8558
+            message: ${variable.rebate}
+        - setVariable:
+            id: setVariable-d9a6
+            description: Update Order
+            name: order
+            expression:
+              groovy:
+                id: groovy-a753
+                expression: >-
+                  def updatedOrder = variables.order;
+
+                  updatedOrder.amount = updatedOrder.amount * (1 -
+                  variables.rebate.rebate);
+
+                  return updatedOrder;
+        - to:
+            id: to-20bb
+            uri: direct
+            parameters:
+              name: send
+- route:
+    id: hello
+    from:
+      id: from-3f49
+      uri: direct
+      parameters:
+        name: hello
+      steps:
+        - to:
+            id: to-428d
+            uri: activemq
+        - to:
+            id: to-fed7
+            uri: kafka
+- routeConfiguration:
+    id: auditedRoute
+    intercept:
+      - intercept:
+          id: intercept-0deb
+          steps:
+            - to:
+                id: to-b470
+                uri: direct
+                parameters:
+                  name: step
+    interceptFrom:
+      - interceptFrom:
+          id: interceptFrom-4041
+          steps:
+            - to:
+                id: to-6861
+                uri: direct
+                parameters:
+                  name: start
+    onCompletion:
+      - onCompletion:
+          id: onCompletion-3dab
+          steps:
+            - to:
+                id: to-dd4e
+                uri: direct
+                parameters:
+                  name: finish
diff --git a/karavan-designer/src/designer/route/DslConnections.tsx 
b/karavan-designer/src/designer/route/DslConnections.tsx
index 718f60be..f0b0ff7a 100644
--- a/karavan-designer/src/designer/route/DslConnections.tsx
+++ b/karavan-designer/src/designer/route/DslConnections.tsx
@@ -46,7 +46,7 @@ export function DslConnections() {
         const integrations = getIntegrations(files);
         setTons(prevState => {
             const data = new Map<string, string[]>();
-            TopologyUtils.findTopologyOutgoingNodes(integrations).forEach(t => 
{
+            
TopologyUtils.findTopologyRouteOutgoingNodes(integrations).forEach(t => {
                 const key = (t.step as any)?.uri + ':' + (t.step as 
any)?.parameters?.name;
                 if (data.has(key)) {
                     const list = data.get(key) || [];
diff --git a/karavan-designer/src/topology/CustomNode.tsx 
b/karavan-designer/src/topology/CustomNode.tsx
index d683d04f..88263966 100644
--- a/karavan-designer/src/topology/CustomNode.tsx
+++ b/karavan-designer/src/topology/CustomNode.tsx
@@ -24,7 +24,7 @@ import {CamelUi} from "../designer/utils/CamelUi";
 import './topology.css';
 
 function getIcon(data: any) {
-    if (['route', 'rest'].includes(data.icon)) {
+    if (['route', 'rest', 'routeConfiguration'].includes(data.icon)) {
         return (
             <g transform={`translate(14, 14)`}>
                 {getDesignerIcon(data.icon)}
diff --git a/karavan-designer/src/topology/TopologyApi.tsx 
b/karavan-designer/src/topology/TopologyApi.tsx
index a640d508..d24b38f8 100644
--- a/karavan-designer/src/topology/TopologyApi.tsx
+++ b/karavan-designer/src/topology/TopologyApi.tsx
@@ -26,20 +26,21 @@ import {
     NodeModel,
     NodeShape,
     NodeStatus,
-    withPanZoom, withSelection
+    withPanZoom,
+    withSelection
 } from '@patternfly/react-topology';
 import CustomNode from "./CustomNode";
-import {Integration} from "karavan-core/lib/model/IntegrationDefinition";
+import {Integration, IntegrationFile} from 
"karavan-core/lib/model/IntegrationDefinition";
 import {CamelDefinitionYaml} from "karavan-core/lib/api/CamelDefinitionYaml";
 import {TopologyUtils} from "karavan-core/lib/api/TopologyUtils";
 import {
     TopologyIncomingNode,
     TopologyOutgoingNode,
     TopologyRestNode,
+    TopologyRouteConfigurationNode,
     TopologyRouteNode
 } from "karavan-core/lib/model/TopologyDefinition";
 import CustomEdge from "./CustomEdge";
-import {IntegrationFile} from "karavan-core/lib/model/IntegrationDefinition";
 import CustomGroup from "./CustomGroup";
 
 const NODE_DIAMETER = 60;
@@ -94,6 +95,28 @@ export function getRoutes(tins: TopologyRouteNode[]): 
NodeModel[] {
         return node;
     });
 }
+export function getRouteConfigurations(trcs: 
TopologyRouteConfigurationNode[]): NodeModel[] {
+    return trcs.map(tin => {
+        const node: NodeModel = {
+            id: tin.id,
+            type: 'node',
+            label: tin.title,
+            width: NODE_DIAMETER,
+            height: NODE_DIAMETER,
+            shape: NodeShape.rect,
+            status: NodeStatus.default,
+            data: {
+                isAlternate: false,
+                type: 'routeConfiguration',
+                icon: 'routeConfiguration',
+                step: tin.routeConfiguration,
+                routeConfigurationId: tin.routeConfigurationId,
+                fileName: tin.fileName,
+            }
+        }
+        return node;
+    });
+}
 
 export function getOutgoingNodes(tons: TopologyOutgoingNode[]): NodeModel[] {
     return tons.filter(tin => tin.type === 'external').map(tin => {
@@ -227,41 +250,64 @@ export function getInternalEdges(tons: 
TopologyOutgoingNode[], tins: TopologyInc
     return result;
 }
 
-export function getModel(files: IntegrationFile[]): Model {
+export function getModel(files: IntegrationFile[], grouping?: boolean): Model {
     const integrations = getIntegrations(files);
     const tins = TopologyUtils.findTopologyIncomingNodes(integrations);
     const troutes = TopologyUtils.findTopologyRouteNodes(integrations);
-    const tons = TopologyUtils.findTopologyOutgoingNodes(integrations);
+    const tons = TopologyUtils.findTopologyRouteOutgoingNodes(integrations);
     const trestns = TopologyUtils.findTopologyRestNodes(integrations);
 
+    const trcs = 
TopologyUtils.findTopologyRouteConfigurationNodes(integrations);
+    const trcons = 
TopologyUtils.findTopologyRouteConfigurationOutgoingNodes(integrations);
+
     const nodes: NodeModel[] = [];
-    const groups: NodeModel[] = troutes.map(r => {
-        const children = [r.id]
-        children.push(...tins.filter(i => i.routeId === r.routeId && i.type 
=== 'external').map(i => i.id));
-        children.push(...tons.filter(i => i.routeId === r.routeId && i.type 
=== 'external').map(i => i.id));
-        return   {
-            id: 'group-' + r.routeId,
-            children: children,
-            type: 'group',
-            group: true,
-            label: r.title,
-            style: {
-                padding: 40
-            }
+    const groups: NodeModel[] = [];
+
+    const children1 = []
+    children1.push(...tins.filter(i => i.type === 'external').map(i => i.id));
+    children1.push(...trestns.map(i => i.id));
+    groups.push({
+        id: 'consumer-group',
+        children: children1,
+        type: 'group',
+        group: true,
+        label: 'Consumer group',
+        style: {
+            padding: 10,
+            strokeWidth: "2px",
+        }
+    })
+
+    const children2 = [...tons.filter(i => i.type === 'external').map(i => 
i.id)];
+    groups.push({
+        id: 'producer-group',
+        children: children2,
+        type: 'group',
+        group: true,
+        label: 'Producer group',
+        style: {
+            padding: 10,
+            strokeWidth: "2px"
         }
     })
 
     nodes.push(...getRestNodes(trestns))
     nodes.push(...getIncomingNodes(tins))
     nodes.push(...getRoutes(troutes))
+    nodes.push(...getRouteConfigurations(trcs))
     nodes.push(...getOutgoingNodes(tons))
-    // nodes.push(...groups)
+    nodes.push(...getOutgoingNodes(trcons))
+
+    if (grouping === true) {
+        nodes.push(...groups)
+    }
 
     const edges: EdgeModel[] = [];
     edges.push(...getIncomingEdges(tins));
     edges.push(...getOutgoingEdges(tons));
     edges.push(...getRestEdges(trestns, tins));
     edges.push(...getInternalEdges(tons, tins));
+    edges.push(...getInternalEdges(trcons, tins));
     edges.push(...getExternalEdges(tons,tins));
 
     return {nodes: nodes, edges: edges, graph: {id: 'g1', type: 'graph', 
layout: 'Dagre'}};
diff --git a/karavan-designer/src/topology/TopologyPropertiesPanel.tsx 
b/karavan-designer/src/topology/TopologyPropertiesPanel.tsx
index 27ef71b5..a32d6969 100644
--- a/karavan-designer/src/topology/TopologyPropertiesPanel.tsx
+++ b/karavan-designer/src/topology/TopologyPropertiesPanel.tsx
@@ -48,6 +48,14 @@ export function TopologyPropertiesPanel(props: Props) {
         return false;
     }
 
+    function isRouteConfiguration() {
+        return (nodeData && nodeData.type === 'routeConfiguration');
+    }
+
+    function isRest() {
+        return (nodeData && nodeData.type === 'rest');
+    }
+
     function isKamelet() {
         if (nodeData && nodeData.type === 'step') {
             const uri: string = nodeData?.step?.uri || '';
@@ -70,7 +78,16 @@ export function TopologyPropertiesPanel(props: Props) {
     }
 
     function getTitle () {
-        return isRoute() ? "Route" : (isKamelet() ? "Kamelet" : "Component");
+        if (isRoute()) {
+            return "Route";
+        } else if (isKamelet()) {
+            return "Kamelet";
+        } else if (isRouteConfiguration()) {
+            return "Route Configuration";
+        } else if (isRest()) {
+            return "REST";
+        }
+        return "Component";
     }
 
     function getHeader() {
@@ -118,7 +135,7 @@ export function TopologyPropertiesPanel(props: Props) {
     return (
         <TopologySideBar
             className="topology-sidebar"
-            show={selectedIds.length > 0}
+            show={selectedIds.length > 0 && nodeData}
             header={getHeader()}
         >
             <DslProperties designerType={'routes'}/>
diff --git a/karavan-designer/src/topology/TopologyStore.ts 
b/karavan-designer/src/topology/TopologyStore.ts
index 517e2794..514f1b37 100644
--- a/karavan-designer/src/topology/TopologyStore.ts
+++ b/karavan-designer/src/topology/TopologyStore.ts
@@ -28,6 +28,8 @@ interface TopologyState {
     setRanker: (ranker: string) => void
     nodeData: any
     setNodeData: (nodeData: any) => void
+    showGroups?: boolean
+    setShowGroups: (showGroups: boolean) => void
 }
 
 export const useTopologyStore = createWithEqualityFn<TopologyState>((set) => ({
@@ -50,8 +52,13 @@ export const useTopologyStore = 
createWithEqualityFn<TopologyState>((set) => ({
     },
     nodeData: undefined,
     setNodeData: (nodeData: any) => {
-    set((state: TopologyState) => {
-        return {nodeData: nodeData};
-    });
-},
+        set((state: TopologyState) => {
+            return {nodeData: nodeData};
+        });
+    },
+    setShowGroups: (showGroups: boolean) => {
+        set((state: TopologyState) => {
+            return {showGroups: showGroups};
+        });
+    },
 }), shallow)
diff --git a/karavan-designer/src/topology/TopologyTab.tsx 
b/karavan-designer/src/topology/TopologyTab.tsx
index e9587444..cc189e29 100644
--- a/karavan-designer/src/topology/TopologyTab.tsx
+++ b/karavan-designer/src/topology/TopologyTab.tsx
@@ -49,8 +49,8 @@ interface Props {
 
 export function TopologyTab(props: Props) {
 
-    const [selectedIds, setSelectedIds, setFileName, ranker, setRanker, 
setNodeData] = useTopologyStore((s) =>
-        [s.selectedIds, s.setSelectedIds, s.setFileName, s.ranker, 
s.setRanker, s.setNodeData], shallow);
+    const [selectedIds, setSelectedIds, setFileName, ranker, setRanker, 
setNodeData, showGroups] = useTopologyStore((s) =>
+        [s.selectedIds, s.setSelectedIds, s.setFileName, s.ranker, 
s.setRanker, s.setNodeData, s.showGroups], shallow);
     const [setSelectedStep] = useDesignerStore((s) => [s.setSelectedStep], 
shallow)
 
     function setTopologySelected(model: Model, ids: string []) {
@@ -60,8 +60,8 @@ export function TopologyTab(props: Props) {
             if (node && node.length > 0) {
                 const data = node[0].data;
                 setNodeData(data);
-                setFileName(data.fileName)
-                if (data.step) {
+                if (data && data.step) {
+                    setFileName(data.fileName)
                     setSelectedStep(data.step)
                 } else {
                     setSelectedStep(undefined);
@@ -72,7 +72,7 @@ export function TopologyTab(props: Props) {
     }
 
     const controller = React.useMemo(() => {
-        const model = getModel(props.files);
+        const model = getModel(props.files, showGroups);
         const newController = new Visualization();
         newController.registerLayoutFactory((_, graph) =>
             new DagreLayout(graph, {
@@ -99,9 +99,9 @@ export function TopologyTab(props: Props) {
 
     React.useEffect(() => {
         setSelectedIds([])
-        const model = getModel(props.files);
+        const model = getModel(props.files, showGroups);
         controller.fromModel(model, false);
-    }, [ranker, controller, setSelectedIds, props.files]);
+    }, [ranker, controller, setSelectedIds, props.files, showGroups]);
 
     const controlButtons = React.useMemo(() => {
         // const customButtons = [
diff --git a/karavan-designer/src/topology/TopologyToolbar.tsx 
b/karavan-designer/src/topology/TopologyToolbar.tsx
index 5dbe4949..0de61fee 100644
--- a/karavan-designer/src/topology/TopologyToolbar.tsx
+++ b/karavan-designer/src/topology/TopologyToolbar.tsx
@@ -17,10 +17,12 @@
 
 import * as React from 'react';
 import {
-    Button, ToolbarContent,
+    Button, Switch, ToolbarContent,
     ToolbarItem, Tooltip
 } from '@patternfly/react-core';
 import PlusIcon from "@patternfly/react-icons/dist/esm/icons/plus-icon";
+import {useTopologyStore} from "./TopologyStore";
+import {shallow} from "zustand/shallow";
 
 interface Props {
     onClickAddRoute: () => void
@@ -31,8 +33,22 @@ interface Props {
 
 export function TopologyToolbar (props: Props) {
 
+    const [showGroups, setShowGroups] = useTopologyStore((s) =>
+        [s.showGroups, s.setShowGroups], shallow);
+
     return (
-        <ToolbarContent>
+        <div className='topology-toolbar'>
+            <ToolbarItem className="group-switch">
+                <Tooltip content={"Show Consumer and Producer Groups"} 
position={"bottom-start"}>
+                    <Switch
+                        id="reversed-switch"
+                        label="Groups"
+                        isChecked={showGroups}
+                        onChange={(_, checked) => setShowGroups(checked)}
+                        isReversed
+                    />
+                </Tooltip>
+            </ToolbarItem>
             <ToolbarItem align={{default:"alignRight"}}>
                 <Tooltip content={"Add Integration Route"} position={"bottom"}>
                     <Button className="dev-action-button" size="sm"
@@ -77,6 +93,6 @@ export function TopologyToolbar (props: Props) {
                     </Button>
                 </Tooltip>
             </ToolbarItem>
-        </ToolbarContent>
+        </div>
     )
 }
\ No newline at end of file
diff --git a/karavan-designer/src/topology/topology.css 
b/karavan-designer/src/topology/topology.css
index 24fe7679..7d21c7b2 100644
--- a/karavan-designer/src/topology/topology.css
+++ b/karavan-designer/src/topology/topology.css
@@ -15,17 +15,28 @@
  * limitations under the License.
  */
 
+.karavan .topology-panel .topology-toolbar {
+    width: 100%;
+    display: flex;
+    flex-direction: row;
+}
+
+.karavan .topology-panel .topology-toolbar .group-switch {
+    flex-grow: 4;
+    margin-top: auto;
+    margin-bottom: auto;
+    margin-left: 6px;
+}
+
 .karavan .topology-panel .pf-v5-c-toolbar {
     padding: 0;
-    display: flex;
-    flex-direction: column;
-    align-items: flex-end;
     height: fit-content;
     row-gap: 0;
 }
 
 .karavan .topology-panel .pf-v5-c-toolbar .pf-v5-c-toolbar__content {
     padding: 0 6px 0 0;
+    width: 100%;
 }
 
 .karavan .topology-panel .pf-v5-c-toolbar .pf-v5-c-toolbar__content-section {

Reply via email to