From 5526ebf4db2e250a3c74f0275bd6912e5d33bda0 Mon Sep 17 00:00:00 2001 From: "Ricardo M." Date: Sun, 18 Aug 2024 01:30:31 +0000 Subject: [PATCH] feat(Canvas): Add Node mapper Currently, EIPs with children are rendered as containers, as opposed to in the past, where they were rendered as nodes. The goal for this ticket is to be able to customize how each EIP is rendered. This is an intermediate step between having a single class holding the entire route vs having individual classes per EIP, each holding the underlying code section they represent. fix: https://github.com/KaotoIO/kaoto/issues/1329 --- .../flows/abstract-camel-visual-entity.ts | 4 +- .../camel-error-handler-visual-entity.ts | 4 +- .../camel-intercept-from-visual-entity.ts | 4 +- ...ntercept-send-to-endpoint-visual-entity.ts | 4 +- .../flows/camel-intercept-visual-entity.ts | 4 +- .../camel-on-completion-visual-entity.ts | 4 +- .../flows/camel-on-exception-visual-entity.ts | 4 +- .../camel-rest-configuration-visual-entity.ts | 4 +- ...camel-route-configuration-visual-entity.ts | 4 +- .../mappers/base-node-mapper.test.ts} | 39 +++--- .../flows/nodes/mappers/base-node-mapper.ts | 117 ++++++++++++++++++ .../nodes/mappers/choice-node-mapper.test.ts | 69 +++++++++++ .../flows/nodes/mappers/choice-node-mapper.ts | 37 ++++++ .../mappers/otherwise-node-mapper.test.ts | 40 ++++++ .../nodes/mappers/otherwise-node-mapper.ts | 32 +++++ .../nodes/mappers/testing/noop-node-mapper.ts | 8 ++ .../nodes/mappers/when-node-mapper.test.ts | 43 +++++++ .../flows/nodes/mappers/when-node-mapper.ts | 32 +++++ .../flows/nodes/node-mapper.service.test.ts | 20 +++ .../flows/nodes/node-mapper.service.ts | 36 ++++++ .../visualization/flows/nodes/node-mapper.ts | 10 ++ .../flows/nodes/root-node-mapper.test.ts | 52 ++++++++ .../flows/nodes/root-node-mapper.ts | 31 +++++ .../camel-component-schema.service.test.ts | 32 ++--- .../support/camel-component-schema.service.ts | 14 +-- .../flows/support/camel-component-types.ts | 4 +- .../flows/support/camel-steps.service.ts | 104 ---------------- .../__snapshots__/nodes-edges.test.ts.snap | 42 ------- 28 files changed, 593 insertions(+), 205 deletions(-) rename packages/ui/src/models/visualization/flows/{support/camel-steps.service.test.ts => nodes/mappers/base-node-mapper.test.ts} (54%) create mode 100644 packages/ui/src/models/visualization/flows/nodes/mappers/base-node-mapper.ts create mode 100644 packages/ui/src/models/visualization/flows/nodes/mappers/choice-node-mapper.test.ts create mode 100644 packages/ui/src/models/visualization/flows/nodes/mappers/choice-node-mapper.ts create mode 100644 packages/ui/src/models/visualization/flows/nodes/mappers/otherwise-node-mapper.test.ts create mode 100644 packages/ui/src/models/visualization/flows/nodes/mappers/otherwise-node-mapper.ts create mode 100644 packages/ui/src/models/visualization/flows/nodes/mappers/testing/noop-node-mapper.ts create mode 100644 packages/ui/src/models/visualization/flows/nodes/mappers/when-node-mapper.test.ts create mode 100644 packages/ui/src/models/visualization/flows/nodes/mappers/when-node-mapper.ts create mode 100644 packages/ui/src/models/visualization/flows/nodes/node-mapper.service.test.ts create mode 100644 packages/ui/src/models/visualization/flows/nodes/node-mapper.service.ts create mode 100644 packages/ui/src/models/visualization/flows/nodes/node-mapper.ts create mode 100644 packages/ui/src/models/visualization/flows/nodes/root-node-mapper.test.ts create mode 100644 packages/ui/src/models/visualization/flows/nodes/root-node-mapper.ts delete mode 100644 packages/ui/src/models/visualization/flows/support/camel-steps.service.ts diff --git a/packages/ui/src/models/visualization/flows/abstract-camel-visual-entity.ts b/packages/ui/src/models/visualization/flows/abstract-camel-visual-entity.ts index e010275d8..b27c69430 100644 --- a/packages/ui/src/models/visualization/flows/abstract-camel-visual-entity.ts +++ b/packages/ui/src/models/visualization/flows/abstract-camel-visual-entity.ts @@ -18,7 +18,7 @@ import { createVisualizationNode } from '../visualization-node'; import { CamelComponentDefaultService } from './support/camel-component-default.service'; import { CamelComponentSchemaService } from './support/camel-component-schema.service'; import { CamelProcessorStepsProperties, CamelRouteVisualEntityData } from './support/camel-component-types'; -import { CamelStepsService } from './support/camel-steps.service'; +import { NodeMapperService } from './nodes/node-mapper.service'; import { ModelValidationService } from './support/validators/model-validation.service'; export abstract class AbstractCamelVisualEntity implements BaseVisualCamelEntity { @@ -222,7 +222,7 @@ export abstract class AbstractCamelVisualEntity implements Bas processorName: 'route', }); - const fromNode = CamelStepsService.getVizNodeFromProcessor( + const fromNode = NodeMapperService.getVizNode( 'from', { processorName: 'from' as keyof ProcessorDefinition, diff --git a/packages/ui/src/models/visualization/flows/camel-error-handler-visual-entity.ts b/packages/ui/src/models/visualization/flows/camel-error-handler-visual-entity.ts index 406650415..a11d2ec35 100644 --- a/packages/ui/src/models/visualization/flows/camel-error-handler-visual-entity.ts +++ b/packages/ui/src/models/visualization/flows/camel-error-handler-visual-entity.ts @@ -11,7 +11,7 @@ import { NodeInteraction, VisualComponentSchema, } from '../base-visual-entity'; -import { CamelStepsService } from './support/camel-steps.service'; +import { NodeMapperService } from './nodes/node-mapper.service'; export class CamelErrorHandlerVisualEntity implements BaseVisualCamelEntity { id: string; @@ -125,7 +125,7 @@ export class CamelErrorHandlerVisualEntity implements BaseVisualCamelEntity { } toVizNode(): IVisualizationNode { - const errorHandlerGroupNode = CamelStepsService.getVizNodeFromProcessor( + const errorHandlerGroupNode = NodeMapperService.getVizNode( 'errorHandler', { processorName: 'errorHandler' as keyof ProcessorDefinition }, this.errorHandlerDef, diff --git a/packages/ui/src/models/visualization/flows/camel-intercept-from-visual-entity.ts b/packages/ui/src/models/visualization/flows/camel-intercept-from-visual-entity.ts index e0220f930..79e538504 100644 --- a/packages/ui/src/models/visualization/flows/camel-intercept-from-visual-entity.ts +++ b/packages/ui/src/models/visualization/flows/camel-intercept-from-visual-entity.ts @@ -11,7 +11,7 @@ import { import { AbstractCamelVisualEntity } from './abstract-camel-visual-entity'; import { CamelComponentSchemaService } from './support/camel-component-schema.service'; import { CamelRouteVisualEntityData } from './support/camel-component-types'; -import { CamelStepsService } from './support/camel-steps.service'; +import { NodeMapperService } from './nodes/node-mapper.service'; import { ModelValidationService } from './support/validators/model-validation.service'; export class CamelInterceptFromVisualEntity @@ -98,7 +98,7 @@ export class CamelInterceptFromVisualEntity } toVizNode(): IVisualizationNode { - const interceptFromGroupNode = CamelStepsService.getVizNodeFromProcessor( + const interceptFromGroupNode = NodeMapperService.getVizNode( CamelInterceptFromVisualEntity.ROOT_PATH, { processorName: CamelInterceptFromVisualEntity.ROOT_PATH as keyof ProcessorDefinition }, this.interceptFromDef, diff --git a/packages/ui/src/models/visualization/flows/camel-intercept-send-to-endpoint-visual-entity.ts b/packages/ui/src/models/visualization/flows/camel-intercept-send-to-endpoint-visual-entity.ts index e5ebbfb21..50749e6f7 100644 --- a/packages/ui/src/models/visualization/flows/camel-intercept-send-to-endpoint-visual-entity.ts +++ b/packages/ui/src/models/visualization/flows/camel-intercept-send-to-endpoint-visual-entity.ts @@ -11,7 +11,7 @@ import { import { AbstractCamelVisualEntity } from './abstract-camel-visual-entity'; import { CamelComponentSchemaService } from './support/camel-component-schema.service'; import { CamelRouteVisualEntityData } from './support/camel-component-types'; -import { CamelStepsService } from './support/camel-steps.service'; +import { NodeMapperService } from './nodes/node-mapper.service'; import { ModelValidationService } from './support/validators/model-validation.service'; export class CamelInterceptSendToEndpointVisualEntity @@ -110,7 +110,7 @@ export class CamelInterceptSendToEndpointVisualEntity } toVizNode(): IVisualizationNode { - const interceptSendToEndpointGroupNode = CamelStepsService.getVizNodeFromProcessor( + const interceptSendToEndpointGroupNode = NodeMapperService.getVizNode( CamelInterceptSendToEndpointVisualEntity.ROOT_PATH, { processorName: CamelInterceptSendToEndpointVisualEntity.ROOT_PATH as keyof ProcessorDefinition }, this.interceptSendToEndpointDef, diff --git a/packages/ui/src/models/visualization/flows/camel-intercept-visual-entity.ts b/packages/ui/src/models/visualization/flows/camel-intercept-visual-entity.ts index 57310f461..b55b228ba 100644 --- a/packages/ui/src/models/visualization/flows/camel-intercept-visual-entity.ts +++ b/packages/ui/src/models/visualization/flows/camel-intercept-visual-entity.ts @@ -11,7 +11,7 @@ import { import { AbstractCamelVisualEntity } from './abstract-camel-visual-entity'; import { CamelComponentSchemaService } from './support/camel-component-schema.service'; import { CamelRouteVisualEntityData } from './support/camel-component-types'; -import { CamelStepsService } from './support/camel-steps.service'; +import { NodeMapperService } from './nodes/node-mapper.service'; import { ModelValidationService } from './support/validators/model-validation.service'; export class CamelInterceptVisualEntity @@ -81,7 +81,7 @@ export class CamelInterceptVisualEntity } toVizNode(): IVisualizationNode { - const interceptGroupNode = CamelStepsService.getVizNodeFromProcessor( + const interceptGroupNode = NodeMapperService.getVizNode( CamelInterceptVisualEntity.ROOT_PATH, { processorName: CamelInterceptVisualEntity.ROOT_PATH as keyof ProcessorDefinition }, this.interceptDef, diff --git a/packages/ui/src/models/visualization/flows/camel-on-completion-visual-entity.ts b/packages/ui/src/models/visualization/flows/camel-on-completion-visual-entity.ts index 7d0be41f7..da490c664 100644 --- a/packages/ui/src/models/visualization/flows/camel-on-completion-visual-entity.ts +++ b/packages/ui/src/models/visualization/flows/camel-on-completion-visual-entity.ts @@ -11,7 +11,7 @@ import { import { AbstractCamelVisualEntity } from './abstract-camel-visual-entity'; import { CamelComponentSchemaService } from './support/camel-component-schema.service'; import { CamelRouteVisualEntityData } from './support/camel-component-types'; -import { CamelStepsService } from './support/camel-steps.service'; +import { NodeMapperService } from './nodes/node-mapper.service'; import { ModelValidationService } from './support/validators/model-validation.service'; export class CamelOnCompletionVisualEntity @@ -83,7 +83,7 @@ export class CamelOnCompletionVisualEntity } toVizNode(): IVisualizationNode { - const onCompletionGroupNode = CamelStepsService.getVizNodeFromProcessor( + const onCompletionGroupNode = NodeMapperService.getVizNode( CamelOnCompletionVisualEntity.ROOT_PATH, { processorName: CamelOnCompletionVisualEntity.ROOT_PATH as keyof ProcessorDefinition }, this.onCompletionDef, diff --git a/packages/ui/src/models/visualization/flows/camel-on-exception-visual-entity.ts b/packages/ui/src/models/visualization/flows/camel-on-exception-visual-entity.ts index 2417d7d3d..b3a6ac452 100644 --- a/packages/ui/src/models/visualization/flows/camel-on-exception-visual-entity.ts +++ b/packages/ui/src/models/visualization/flows/camel-on-exception-visual-entity.ts @@ -11,7 +11,7 @@ import { import { AbstractCamelVisualEntity } from './abstract-camel-visual-entity'; import { CamelComponentSchemaService } from './support/camel-component-schema.service'; import { CamelRouteVisualEntityData } from './support/camel-component-types'; -import { CamelStepsService } from './support/camel-steps.service'; +import { NodeMapperService } from './nodes/node-mapper.service'; import { ModelValidationService } from './support/validators/model-validation.service'; export class CamelOnExceptionVisualEntity @@ -83,7 +83,7 @@ export class CamelOnExceptionVisualEntity } toVizNode(): IVisualizationNode { - const onExceptionGroupNode = CamelStepsService.getVizNodeFromProcessor( + const onExceptionGroupNode = NodeMapperService.getVizNode( CamelOnExceptionVisualEntity.ROOT_PATH, { processorName: CamelOnExceptionVisualEntity.ROOT_PATH as keyof ProcessorDefinition }, this.onExceptionDef, diff --git a/packages/ui/src/models/visualization/flows/camel-rest-configuration-visual-entity.ts b/packages/ui/src/models/visualization/flows/camel-rest-configuration-visual-entity.ts index dc434089e..3adbbe428 100644 --- a/packages/ui/src/models/visualization/flows/camel-rest-configuration-visual-entity.ts +++ b/packages/ui/src/models/visualization/flows/camel-rest-configuration-visual-entity.ts @@ -14,7 +14,7 @@ import { VisualComponentSchema, } from '../base-visual-entity'; import { CamelCatalogService } from './camel-catalog.service'; -import { CamelStepsService } from './support/camel-steps.service'; +import { NodeMapperService } from './nodes/node-mapper.service'; export class CamelRestConfigurationVisualEntity implements BaseVisualCamelEntity { id: string; @@ -119,7 +119,7 @@ export class CamelRestConfigurationVisualEntity implements BaseVisualCamelEntity } toVizNode(): IVisualizationNode { - const restConfigurationGroupNode = CamelStepsService.getVizNodeFromProcessor( + const restConfigurationGroupNode = NodeMapperService.getVizNode( 'restConfiguration', { processorName: 'restConfiguration' as keyof ProcessorDefinition }, this.restConfigurationDef, diff --git a/packages/ui/src/models/visualization/flows/camel-route-configuration-visual-entity.ts b/packages/ui/src/models/visualization/flows/camel-route-configuration-visual-entity.ts index 54ab3cbfc..1436ee893 100644 --- a/packages/ui/src/models/visualization/flows/camel-route-configuration-visual-entity.ts +++ b/packages/ui/src/models/visualization/flows/camel-route-configuration-visual-entity.ts @@ -18,7 +18,7 @@ import { createVisualizationNode } from '../visualization-node'; import { AbstractCamelVisualEntity } from './abstract-camel-visual-entity'; import { CamelCatalogService } from './camel-catalog.service'; import { CamelComponentSchemaService } from './support/camel-component-schema.service'; -import { CamelStepsService } from './support/camel-steps.service'; +import { NodeMapperService } from './nodes/node-mapper.service'; export class CamelRouteConfigurationVisualEntity extends AbstractCamelVisualEntity<{ routeConfiguration: RouteConfigurationDefinition }> @@ -156,7 +156,7 @@ export class CamelRouteConfigurationVisualEntity if (!Array.isArray(childEntities)) return; childEntities.forEach((childEntity, index) => { - const childNode = CamelStepsService.getVizNodeFromProcessor( + const childNode = NodeMapperService.getVizNode( `${CamelRouteConfigurationVisualEntity.ROOT_PATH}.${stepsProperty.name}.${index}.${ Object.keys(childEntity)[0] }`, diff --git a/packages/ui/src/models/visualization/flows/support/camel-steps.service.test.ts b/packages/ui/src/models/visualization/flows/nodes/mappers/base-node-mapper.test.ts similarity index 54% rename from packages/ui/src/models/visualization/flows/support/camel-steps.service.test.ts rename to packages/ui/src/models/visualization/flows/nodes/mappers/base-node-mapper.test.ts index 8462095d4..ca11ac6ed 100644 --- a/packages/ui/src/models/visualization/flows/support/camel-steps.service.test.ts +++ b/packages/ui/src/models/visualization/flows/nodes/mappers/base-node-mapper.test.ts @@ -1,13 +1,19 @@ import { ProcessorDefinition, RouteDefinition } from '@kaoto/camel-catalog/types'; -import { ICamelElementLookupResult } from './camel-component-types'; -import { CamelStepsService } from './camel-steps.service'; +import { ICamelElementLookupResult } from '../../support/camel-component-types'; +import { RootNodeMapper } from '../root-node-mapper'; +import { BaseNodeMapper } from './base-node-mapper'; -describe('CamelStepsService', () => { +describe('BaseNodeMapper', () => { + let mapper: BaseNodeMapper; let path: string; let componentLookup: ICamelElementLookupResult; let entityDefinition: unknown; beforeEach(() => { + const rootNodeMapper = new RootNodeMapper(); + mapper = new BaseNodeMapper(rootNodeMapper); + rootNodeMapper.registerDefaultMapper(mapper); + path = 'from'; componentLookup = { processorName: 'from' as keyof ProcessorDefinition, @@ -18,7 +24,7 @@ describe('CamelStepsService', () => { describe('getVizNodeFromProcessor', () => { it('should return a VisualizationNode', () => { - const vizNode = CamelStepsService.getVizNodeFromProcessor(path, componentLookup, entityDefinition); + const vizNode = mapper.getVizNodeFromProcessor(path, componentLookup, entityDefinition); expect(vizNode).toBeDefined(); expect(vizNode.data).toMatchObject({ @@ -37,7 +43,7 @@ describe('CamelStepsService', () => { }, }; - const vizNode = CamelStepsService.getVizNodeFromProcessor(path, componentLookup, routeDefinition); + const vizNode = mapper.getVizNodeFromProcessor(path, componentLookup, routeDefinition); expect(vizNode.getChildren()).toHaveLength(2); expect(vizNode.getChildren()?.[0].data.path).toBe('from.steps.0.log'); expect(vizNode.getChildren()?.[1].data.path).toBe('from.steps.1.to'); @@ -49,27 +55,24 @@ describe('CamelStepsService', () => { uri: 'timer:timerName', steps: [ { - choice: { - when: [ - { expression: { simple: { expression: '${body} == 1' } } }, - { expression: { simple: { expression: '${body} == 2' } } }, - ], - otherwise: { steps: [{ log: 'logName' }] }, + doTry: { + doCatch: [{ exception: ['java.lang.RuntimeException'] }, { exception: ['java.lang.RuntimeException'] }], + doFinally: { steps: [{ log: 'logName' }] }, }, }, ], }, }; - const vizNode = CamelStepsService.getVizNodeFromProcessor(path, componentLookup, routeDefinition); + const vizNode = mapper.getVizNodeFromProcessor(path, componentLookup, routeDefinition); expect(vizNode.getChildren()).toHaveLength(1); - expect(vizNode.getChildren()?.[0].data.path).toBe('from.steps.0.choice'); + expect(vizNode.getChildren()?.[0].data.path).toBe('from.steps.0.doTry'); - const choiceNode = vizNode.getChildren()?.[0]; - expect(choiceNode?.getChildren()).toHaveLength(3); - expect(choiceNode?.getChildren()?.[0].data.path).toBe('from.steps.0.choice.when.0'); - expect(choiceNode?.getChildren()?.[1].data.path).toBe('from.steps.0.choice.when.1'); - expect(choiceNode?.getChildren()?.[2].data.path).toBe('from.steps.0.choice.otherwise'); + const doTryNode = vizNode.getChildren()?.[0]; + expect(doTryNode?.getChildren()).toHaveLength(3); + expect(doTryNode?.getChildren()?.[0].data.path).toBe('from.steps.0.doTry.doCatch.0'); + expect(doTryNode?.getChildren()?.[1].data.path).toBe('from.steps.0.doTry.doCatch.1'); + expect(doTryNode?.getChildren()?.[2].data.path).toBe('from.steps.0.doTry.doFinally'); }); }); }); diff --git a/packages/ui/src/models/visualization/flows/nodes/mappers/base-node-mapper.ts b/packages/ui/src/models/visualization/flows/nodes/mappers/base-node-mapper.ts new file mode 100644 index 000000000..b63d789d9 --- /dev/null +++ b/packages/ui/src/models/visualization/flows/nodes/mappers/base-node-mapper.ts @@ -0,0 +1,117 @@ +import { DoCatch, ProcessorDefinition, When1 } from '@kaoto/camel-catalog/types'; +import { getValue } from '../../../../../utils'; +import { NodeIconResolver, NodeIconType } from '../../../../../utils/node-icon-resolver'; +import { IVisualizationNode } from '../../../base-visual-entity'; +import { createVisualizationNode } from '../../../visualization-node'; +import { CamelComponentSchemaService } from '../../support/camel-component-schema.service'; +import { + CamelProcessorStepsProperties, + CamelRouteVisualEntityData, + ICamelElementLookupResult, +} from '../../support/camel-component-types'; +import { INodeMapper } from '../node-mapper'; + +export class BaseNodeMapper implements INodeMapper { + constructor(private readonly rootNodeMapper: INodeMapper) {} + + getVizNodeFromProcessor( + path: string, + componentLookup: ICamelElementLookupResult, + entityDefinition: unknown, + ): IVisualizationNode { + const nodeIconType = componentLookup.componentName ? NodeIconType.Component : NodeIconType.EIP; + const data: CamelRouteVisualEntityData = { + path, + icon: NodeIconResolver.getIcon(CamelComponentSchemaService.getIconName(componentLookup), nodeIconType), + processorName: componentLookup.processorName, + componentName: componentLookup.componentName, + }; + + const vizNode = createVisualizationNode(componentLookup.componentName ?? componentLookup.processorName, data); + + const childrenStepsProperties = CamelComponentSchemaService.getProcessorStepsProperties( + componentLookup.processorName, + ); + + if (childrenStepsProperties.length > 0) { + vizNode.data.isGroup = true; + } + + childrenStepsProperties.forEach((stepsProperty) => { + const childrenVizNodes = this.getVizNodesFromChildren(path, stepsProperty, entityDefinition); + + childrenVizNodes.forEach((childVizNode) => { + vizNode.addChild(childVizNode); + }); + }); + + return vizNode; + } + + protected getVizNodesFromChildren( + path: string, + stepsProperty: CamelProcessorStepsProperties, + entityDefinition: unknown, + ): IVisualizationNode[] { + const subpath = `${path}.${stepsProperty.name}`; + + switch (stepsProperty.type) { + case 'branch': + return this.getChildrenFromBranch(subpath, entityDefinition); + + case 'single-clause': + return this.getChildrenFromSingleClause(subpath, entityDefinition); + + case 'array-clause': + return this.getChildrenFromArrayClause(subpath, entityDefinition); + + default: + return []; + } + } + + protected getChildrenFromBranch(path: string, entityDefinition: unknown): IVisualizationNode[] { + const stepsList = getValue(entityDefinition, path, []) as ProcessorDefinition[]; + + return stepsList.reduce((accStepsNodes, step, index) => { + const singlePropertyName = Object.keys(step)[0]; + const childPath = `${path}.${index}.${singlePropertyName}`; + const childComponentLookup = CamelComponentSchemaService.getCamelComponentLookup( + childPath, + getValue(step, singlePropertyName), + ); + + const vizNode = this.rootNodeMapper.getVizNodeFromProcessor(childPath, childComponentLookup, entityDefinition); + + const previousVizNode = accStepsNodes[accStepsNodes.length - 1]; + if (previousVizNode !== undefined) { + previousVizNode.setNextNode(vizNode); + vizNode.setPreviousNode(previousVizNode); + } + + accStepsNodes.push(vizNode); + return accStepsNodes; + }, [] as IVisualizationNode[]); + } + + protected getChildrenFromSingleClause(path: string, entityDefinition: unknown): IVisualizationNode[] { + const childComponentLookup = CamelComponentSchemaService.getCamelComponentLookup(path, entityDefinition); + + /** If the single-clause property is not defined, we don't create a IVisualizationNode for it */ + if (getValue(entityDefinition, path) === undefined) return []; + + return [this.rootNodeMapper.getVizNodeFromProcessor(path, childComponentLookup, entityDefinition)]; + } + + protected getChildrenFromArrayClause(path: string, entityDefinition: unknown): IVisualizationNode[] { + const expressionList = getValue(entityDefinition, path, []) as When1[] | DoCatch[]; + + return expressionList.map((_step, index) => { + const childPath = `${path}.${index}`; + const processorName = path.split('.').pop() as keyof ProcessorDefinition; + const childComponentLookup = { processorName }; // when, doCatch + + return this.rootNodeMapper.getVizNodeFromProcessor(childPath, childComponentLookup, entityDefinition); + }); + } +} diff --git a/packages/ui/src/models/visualization/flows/nodes/mappers/choice-node-mapper.test.ts b/packages/ui/src/models/visualization/flows/nodes/mappers/choice-node-mapper.test.ts new file mode 100644 index 000000000..6669d2557 --- /dev/null +++ b/packages/ui/src/models/visualization/flows/nodes/mappers/choice-node-mapper.test.ts @@ -0,0 +1,69 @@ +import { RouteDefinition } from '@kaoto/camel-catalog/types'; +import { RootNodeMapper } from '../root-node-mapper'; +import { ChoiceNodeMapper } from './choice-node-mapper'; +import { OtherwiseNodeMapper } from './otherwise-node-mapper'; +import { WhenNodeMapper } from './when-node-mapper'; +import { noopNodeMapper } from './testing/noop-node-mapper'; + +describe('ChoiceNodeMapper', () => { + let mapper: ChoiceNodeMapper; + let routeDefinition: RouteDefinition; + const path = 'from.steps.0.choice'; + + beforeEach(() => { + const rootNodeMapper = new RootNodeMapper(); + const whenNodeMapper = new WhenNodeMapper(rootNodeMapper); + const otherwiseNodeMapper = new OtherwiseNodeMapper(rootNodeMapper); + rootNodeMapper.registerDefaultMapper(mapper); + rootNodeMapper.registerMapper('when', whenNodeMapper); + rootNodeMapper.registerMapper('otherwise', otherwiseNodeMapper); + rootNodeMapper.registerMapper('log', noopNodeMapper); + + mapper = new ChoiceNodeMapper(rootNodeMapper); + + routeDefinition = { + from: { + uri: 'timer:timerName', + steps: [ + { + choice: { + when: [{ simple: "${header.foo} == 'bar'" }, { simple: "${header.foo} == 'baz'" }], + otherwise: { + steps: [{ log: 'logName' }], + }, + }, + }, + ], + }, + }; + }); + + it('should return children', () => { + const vizNode = mapper.getVizNodeFromProcessor(path, { processorName: 'choice' }, routeDefinition); + + expect(vizNode.getChildren()).toHaveLength(3); + }); + + it('should return `when` nodes as children', () => { + const vizNode = mapper.getVizNodeFromProcessor(path, { processorName: 'choice' }, routeDefinition); + + expect(vizNode.getChildren()?.[0].data.path).toBe('from.steps.0.choice.when.0'); + expect(vizNode.getChildren()?.[1].data.path).toBe('from.steps.0.choice.when.1'); + }); + + it('should return an `otherwise` node if defined', () => { + const vizNode = mapper.getVizNodeFromProcessor(path, { processorName: 'choice' }, routeDefinition); + + expect(vizNode.getChildren()?.[2].data.path).toBe('from.steps.0.choice.otherwise'); + }); + + it('should not return an `otherwise` node if not defined', () => { + routeDefinition.from.steps[0].choice!.otherwise = undefined; + + const vizNode = mapper.getVizNodeFromProcessor(path, { processorName: 'choice' }, routeDefinition); + + expect(vizNode.getChildren()).toHaveLength(2); + expect(vizNode.getChildren()?.[0].data.path).toBe('from.steps.0.choice.when.0'); + expect(vizNode.getChildren()?.[1].data.path).toBe('from.steps.0.choice.when.1'); + }); +}); diff --git a/packages/ui/src/models/visualization/flows/nodes/mappers/choice-node-mapper.ts b/packages/ui/src/models/visualization/flows/nodes/mappers/choice-node-mapper.ts new file mode 100644 index 000000000..41d2185a3 --- /dev/null +++ b/packages/ui/src/models/visualization/flows/nodes/mappers/choice-node-mapper.ts @@ -0,0 +1,37 @@ +import { ProcessorDefinition } from '@kaoto/camel-catalog/types'; +import { NodeIconResolver, NodeIconType } from '../../../../../utils/node-icon-resolver'; +import { IVisualizationNode } from '../../../base-visual-entity'; +import { createVisualizationNode } from '../../../visualization-node'; +import { CamelRouteVisualEntityData, ICamelElementLookupResult } from '../../support/camel-component-types'; +import { BaseNodeMapper } from './base-node-mapper'; + +export class ChoiceNodeMapper extends BaseNodeMapper { + getVizNodeFromProcessor( + path: string, + _componentLookup: ICamelElementLookupResult, + entityDefinition: unknown, + ): IVisualizationNode { + const processorName: keyof ProcessorDefinition = 'choice'; + + const data: CamelRouteVisualEntityData = { + path, + icon: NodeIconResolver.getIcon(processorName, NodeIconType.EIP), + processorName, + isGroup: true, + }; + + const vizNode = createVisualizationNode(processorName, data); + + const whenNodes = this.getChildrenFromArrayClause(`${path}.when`, entityDefinition); + whenNodes.forEach((whenNode) => { + vizNode.addChild(whenNode); + }); + + const otherwiseNode = this.getChildrenFromSingleClause(`${path}.otherwise`, entityDefinition); + if (otherwiseNode.length > 0) { + vizNode.addChild(otherwiseNode[0]); + } + + return vizNode; + } +} diff --git a/packages/ui/src/models/visualization/flows/nodes/mappers/otherwise-node-mapper.test.ts b/packages/ui/src/models/visualization/flows/nodes/mappers/otherwise-node-mapper.test.ts new file mode 100644 index 000000000..5a16e9685 --- /dev/null +++ b/packages/ui/src/models/visualization/flows/nodes/mappers/otherwise-node-mapper.test.ts @@ -0,0 +1,40 @@ +import { RouteDefinition } from '@kaoto/camel-catalog/types'; +import { RootNodeMapper } from '../root-node-mapper'; +import { OtherwiseNodeMapper } from './otherwise-node-mapper'; +import { noopNodeMapper } from './testing/noop-node-mapper'; + +describe('OtherwiseNodeMapper', () => { + let mapper: OtherwiseNodeMapper; + let routeDefinition: RouteDefinition; + const path = 'from.steps.0.choice.otherwise'; + + beforeEach(() => { + const rootNodeMapper = new RootNodeMapper(); + rootNodeMapper.registerDefaultMapper(mapper); + rootNodeMapper.registerMapper('log', noopNodeMapper); + + mapper = new OtherwiseNodeMapper(rootNodeMapper); + + routeDefinition = { + from: { + uri: 'timer:timerName', + steps: [ + { + choice: { + when: [{ simple: "${header.foo} == 'bar'" }, { simple: "${header.foo} == 'baz'" }], + otherwise: { + steps: [{ log: 'logName' }], + }, + }, + }, + ], + }, + }; + }); + + it('should return children', () => { + const vizNode = mapper.getVizNodeFromProcessor(path, { processorName: 'otherwise' }, routeDefinition); + + expect(vizNode.getChildren()).toHaveLength(1); + }); +}); diff --git a/packages/ui/src/models/visualization/flows/nodes/mappers/otherwise-node-mapper.ts b/packages/ui/src/models/visualization/flows/nodes/mappers/otherwise-node-mapper.ts new file mode 100644 index 000000000..0a299e467 --- /dev/null +++ b/packages/ui/src/models/visualization/flows/nodes/mappers/otherwise-node-mapper.ts @@ -0,0 +1,32 @@ +import { ProcessorDefinition } from '@kaoto/camel-catalog/types'; +import { NodeIconResolver, NodeIconType } from '../../../../../utils/node-icon-resolver'; +import { IVisualizationNode } from '../../../base-visual-entity'; +import { createVisualizationNode } from '../../../visualization-node'; +import { CamelRouteVisualEntityData, ICamelElementLookupResult } from '../../support/camel-component-types'; +import { BaseNodeMapper } from './base-node-mapper'; + +export class OtherwiseNodeMapper extends BaseNodeMapper { + getVizNodeFromProcessor( + path: string, + _componentLookup: ICamelElementLookupResult, + entityDefinition: unknown, + ): IVisualizationNode { + const processorName: keyof ProcessorDefinition = 'otherwise'; + + const data: CamelRouteVisualEntityData = { + path, + icon: NodeIconResolver.getIcon(processorName, NodeIconType.EIP), + processorName, + isGroup: true, + }; + + const vizNode = createVisualizationNode(processorName, data); + + const children = this.getChildrenFromBranch(`${path}.steps`, entityDefinition); + children.forEach((child) => { + vizNode.addChild(child); + }); + + return vizNode; + } +} diff --git a/packages/ui/src/models/visualization/flows/nodes/mappers/testing/noop-node-mapper.ts b/packages/ui/src/models/visualization/flows/nodes/mappers/testing/noop-node-mapper.ts new file mode 100644 index 000000000..8cc2bf700 --- /dev/null +++ b/packages/ui/src/models/visualization/flows/nodes/mappers/testing/noop-node-mapper.ts @@ -0,0 +1,8 @@ +import { createVisualizationNode } from '../../../../visualization-node'; +import { INodeMapper } from '../../node-mapper'; + +export const noopNodeMapper: INodeMapper = { + getVizNodeFromProcessor: () => { + return createVisualizationNode('noop', {}); + }, +}; diff --git a/packages/ui/src/models/visualization/flows/nodes/mappers/when-node-mapper.test.ts b/packages/ui/src/models/visualization/flows/nodes/mappers/when-node-mapper.test.ts new file mode 100644 index 000000000..dfd2487a0 --- /dev/null +++ b/packages/ui/src/models/visualization/flows/nodes/mappers/when-node-mapper.test.ts @@ -0,0 +1,43 @@ +import { RouteDefinition } from '@kaoto/camel-catalog/types'; +import { RootNodeMapper } from '../root-node-mapper'; +import { noopNodeMapper } from './testing/noop-node-mapper'; +import { WhenNodeMapper } from './when-node-mapper'; + +describe('WhenNodeMapper', () => { + let mapper: WhenNodeMapper; + let routeDefinition: RouteDefinition; + const path = 'from.steps.0.choice.when.0'; + + beforeEach(() => { + const rootNodeMapper = new RootNodeMapper(); + rootNodeMapper.registerDefaultMapper(mapper); + rootNodeMapper.registerMapper('log', noopNodeMapper); + + mapper = new WhenNodeMapper(rootNodeMapper); + + routeDefinition = { + from: { + uri: 'timer:timerName', + steps: [ + { + choice: { + when: [ + { expression: { simple: { expression: "${header.foo} == 'bar'" } }, steps: [{ log: 'logName' }] }, + { expression: { simple: { expression: "${header.foo} == 'baz'" } }, steps: [{ log: 'logName' }] }, + ], + otherwise: { + steps: [{ log: 'logName' }], + }, + }, + }, + ], + }, + }; + }); + + it('should return children', () => { + const vizNode = mapper.getVizNodeFromProcessor(path, { processorName: 'when' }, routeDefinition); + + expect(vizNode.getChildren()).toHaveLength(1); + }); +}); diff --git a/packages/ui/src/models/visualization/flows/nodes/mappers/when-node-mapper.ts b/packages/ui/src/models/visualization/flows/nodes/mappers/when-node-mapper.ts new file mode 100644 index 000000000..b2d5a9135 --- /dev/null +++ b/packages/ui/src/models/visualization/flows/nodes/mappers/when-node-mapper.ts @@ -0,0 +1,32 @@ +import { ProcessorDefinition } from '@kaoto/camel-catalog/types'; +import { NodeIconResolver, NodeIconType } from '../../../../../utils/node-icon-resolver'; +import { IVisualizationNode } from '../../../base-visual-entity'; +import { createVisualizationNode } from '../../../visualization-node'; +import { CamelRouteVisualEntityData, ICamelElementLookupResult } from '../../support/camel-component-types'; +import { BaseNodeMapper } from './base-node-mapper'; + +export class WhenNodeMapper extends BaseNodeMapper { + getVizNodeFromProcessor( + path: string, + _componentLookup: ICamelElementLookupResult, + entityDefinition: unknown, + ): IVisualizationNode { + const processorName: keyof ProcessorDefinition = 'when'; + + const data: CamelRouteVisualEntityData = { + path, + icon: NodeIconResolver.getIcon(processorName, NodeIconType.EIP), + processorName, + isGroup: true, + }; + + const vizNode = createVisualizationNode(processorName, data); + + const children = this.getChildrenFromBranch(`${path}.steps`, entityDefinition); + children.forEach((child) => { + vizNode.addChild(child); + }); + + return vizNode; + } +} diff --git a/packages/ui/src/models/visualization/flows/nodes/node-mapper.service.test.ts b/packages/ui/src/models/visualization/flows/nodes/node-mapper.service.test.ts new file mode 100644 index 000000000..3ea9ec016 --- /dev/null +++ b/packages/ui/src/models/visualization/flows/nodes/node-mapper.service.test.ts @@ -0,0 +1,20 @@ +import { BaseNodeMapper } from './mappers/base-node-mapper'; +import { ChoiceNodeMapper } from './mappers/choice-node-mapper'; +import { OtherwiseNodeMapper } from './mappers/otherwise-node-mapper'; +import { WhenNodeMapper } from './mappers/when-node-mapper'; +import { NodeMapperService } from './node-mapper.service'; +import { RootNodeMapper } from './root-node-mapper'; + +describe('NodeMapperService', () => { + it('should initialize the root node mapper', () => { + const registerDefaultMapperSpy = jest.spyOn(RootNodeMapper.prototype, 'registerDefaultMapper'); + const registerMapperSpy = jest.spyOn(RootNodeMapper.prototype, 'registerMapper'); + + NodeMapperService.getVizNode('path', { processorName: 'log' }, {}); + + expect(registerDefaultMapperSpy).toHaveBeenCalledWith(expect.any(BaseNodeMapper)); + expect(registerMapperSpy).toHaveBeenCalledWith('choice', expect.any(ChoiceNodeMapper)); + expect(registerMapperSpy).toHaveBeenCalledWith('when', expect.any(WhenNodeMapper)); + expect(registerMapperSpy).toHaveBeenCalledWith('otherwise', expect.any(OtherwiseNodeMapper)); + }); +}); diff --git a/packages/ui/src/models/visualization/flows/nodes/node-mapper.service.ts b/packages/ui/src/models/visualization/flows/nodes/node-mapper.service.ts new file mode 100644 index 000000000..4d896699b --- /dev/null +++ b/packages/ui/src/models/visualization/flows/nodes/node-mapper.service.ts @@ -0,0 +1,36 @@ +import { IVisualizationNode } from '../../base-visual-entity'; +import { ICamelElementLookupResult } from '../support/camel-component-types'; +import { BaseNodeMapper } from './mappers/base-node-mapper'; +import { ChoiceNodeMapper } from './mappers/choice-node-mapper'; +import { OtherwiseNodeMapper } from './mappers/otherwise-node-mapper'; +import { WhenNodeMapper } from './mappers/when-node-mapper'; +import { INodeMapper } from './node-mapper'; +import { RootNodeMapper } from './root-node-mapper'; + +export class NodeMapperService { + private static rootNodeMapper: RootNodeMapper; + + static getVizNode( + path: string, + componentLookup: ICamelElementLookupResult, + entityDefinition: unknown, + ): IVisualizationNode { + return this.getInstance().getVizNodeFromProcessor(path, componentLookup, entityDefinition); + } + + private static getInstance(): INodeMapper { + if (!this.rootNodeMapper) { + NodeMapperService.initializeRootNodeMapper(); + } + + return this.rootNodeMapper; + } + + private static initializeRootNodeMapper() { + this.rootNodeMapper = new RootNodeMapper(); + this.rootNodeMapper.registerDefaultMapper(new BaseNodeMapper(this.rootNodeMapper)); + this.rootNodeMapper.registerMapper('choice', new ChoiceNodeMapper(this.rootNodeMapper)); + this.rootNodeMapper.registerMapper('when', new WhenNodeMapper(this.rootNodeMapper)); + this.rootNodeMapper.registerMapper('otherwise', new OtherwiseNodeMapper(this.rootNodeMapper)); + } +} diff --git a/packages/ui/src/models/visualization/flows/nodes/node-mapper.ts b/packages/ui/src/models/visualization/flows/nodes/node-mapper.ts new file mode 100644 index 000000000..5a3338cb6 --- /dev/null +++ b/packages/ui/src/models/visualization/flows/nodes/node-mapper.ts @@ -0,0 +1,10 @@ +import { IVisualizationNode } from '../../base-visual-entity'; +import { ICamelElementLookupResult } from '../support/camel-component-types'; + +export interface INodeMapper { + getVizNodeFromProcessor( + path: string, + componentLookup: ICamelElementLookupResult, + entityDefinition: unknown, + ): IVisualizationNode; +} diff --git a/packages/ui/src/models/visualization/flows/nodes/root-node-mapper.test.ts b/packages/ui/src/models/visualization/flows/nodes/root-node-mapper.test.ts new file mode 100644 index 000000000..407440ad8 --- /dev/null +++ b/packages/ui/src/models/visualization/flows/nodes/root-node-mapper.test.ts @@ -0,0 +1,52 @@ +import { noopNodeMapper } from './mappers/testing/noop-node-mapper'; +import { RootNodeMapper } from './root-node-mapper'; + +describe('RootNodeMapper', () => { + it('should allow consumers to register mappers', () => { + const rootNodeMapper = new RootNodeMapper(); + + rootNodeMapper.registerMapper('log', noopNodeMapper); + + expect(() => rootNodeMapper.getVizNodeFromProcessor('path', { processorName: 'log' }, {})).not.toThrow(); + }); + + it('should allow consumers to register a default mapper', () => { + const rootNodeMapper = new RootNodeMapper(); + + rootNodeMapper.registerDefaultMapper(noopNodeMapper); + + expect(() => rootNodeMapper.getVizNodeFromProcessor('path', { processorName: 'log' }, {})).not.toThrow(); + }); + + it('should throw an error when no mapper is found', () => { + const rootNodeMapper = new RootNodeMapper(); + + expect(() => rootNodeMapper.getVizNodeFromProcessor('path', { processorName: 'log' }, {})).toThrowError( + 'No mapper found for processor: log', + ); + }); + + describe('getVizNodeFromProcessor', () => { + it('should delegate to the default mapper when no mapper is found', () => { + const rootNodeMapper = new RootNodeMapper(); + rootNodeMapper.registerDefaultMapper(noopNodeMapper); + jest.spyOn(noopNodeMapper, 'getVizNodeFromProcessor'); + + const vizNode = rootNodeMapper.getVizNodeFromProcessor('path', { processorName: 'log' }, {}); + + expect(noopNodeMapper.getVizNodeFromProcessor).toHaveBeenCalledWith('path', { processorName: 'log' }, {}); + expect(vizNode).toBeDefined(); + }); + + it('should delegate to the registered mapper', () => { + const rootNodeMapper = new RootNodeMapper(); + rootNodeMapper.registerMapper('log', noopNodeMapper); + jest.spyOn(noopNodeMapper, 'getVizNodeFromProcessor'); + + const vizNode = rootNodeMapper.getVizNodeFromProcessor('path', { processorName: 'log' }, {}); + + expect(noopNodeMapper.getVizNodeFromProcessor).toHaveBeenCalledWith('path', { processorName: 'log' }, {}); + expect(vizNode).toBeDefined(); + }); + }); +}); diff --git a/packages/ui/src/models/visualization/flows/nodes/root-node-mapper.ts b/packages/ui/src/models/visualization/flows/nodes/root-node-mapper.ts new file mode 100644 index 000000000..66324f54f --- /dev/null +++ b/packages/ui/src/models/visualization/flows/nodes/root-node-mapper.ts @@ -0,0 +1,31 @@ +import { ProcessorDefinition } from '@kaoto/camel-catalog/types'; +import { IVisualizationNode } from '../../base-visual-entity'; +import { ICamelElementLookupResult } from '../support/camel-component-types'; +import { INodeMapper } from './node-mapper'; + +export class RootNodeMapper implements INodeMapper { + private readonly mappers: Map = new Map(); + private defaultMapper: INodeMapper | undefined; + + registerMapper(processorName: keyof ProcessorDefinition, mapper: INodeMapper): void { + this.mappers.set(processorName, mapper); + } + + registerDefaultMapper(mapper: INodeMapper): void { + this.defaultMapper = mapper; + } + + getVizNodeFromProcessor( + path: string, + componentLookup: ICamelElementLookupResult, + entityDefinition: unknown, + ): IVisualizationNode { + const mapper = this.mappers.get(componentLookup.processorName) || this.defaultMapper; + + if (!mapper) { + throw new Error(`No mapper found for processor: ${componentLookup.processorName}`); + } + + return mapper.getVizNodeFromProcessor(path, componentLookup, entityDefinition); + } +} diff --git a/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.test.ts b/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.test.ts index d1a1e2988..b25a1d66c 100644 --- a/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.test.ts +++ b/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.test.ts @@ -7,6 +7,7 @@ import { CatalogKind } from '../../../catalog-kind'; import { NodeLabelType } from '../../../settings/settings.model'; import { CamelCatalogService } from '../camel-catalog.service'; import { CamelComponentSchemaService } from './camel-component-schema.service'; +import { CamelProcessorStepsProperties } from './camel-component-types'; describe('CamelComponentSchemaService', () => { let path: string; @@ -471,7 +472,7 @@ describe('CamelComponentSchemaService', () => { [ 'choice', [ - { name: 'when', type: 'clause-list' }, + { name: 'when', type: 'array-clause' }, { name: 'otherwise', type: 'single-clause' }, ], ], @@ -479,7 +480,7 @@ describe('CamelComponentSchemaService', () => { 'doTry', [ { name: 'steps', type: 'branch' }, - { name: 'doCatch', type: 'clause-list' }, + { name: 'doCatch', type: 'array-clause' }, { name: 'doFinally', type: 'single-clause' }, ], ], @@ -489,11 +490,11 @@ describe('CamelComponentSchemaService', () => { [ 'routeConfiguration', [ - { name: 'intercept', type: 'clause-list' }, - { name: 'interceptFrom', type: 'clause-list' }, - { name: 'interceptSendToEndpoint', type: 'clause-list' }, - { name: 'onException', type: 'clause-list' }, - { name: 'onCompletion', type: 'clause-list' }, + { name: 'intercept', type: 'array-clause' }, + { name: 'interceptFrom', type: 'array-clause' }, + { name: 'interceptSendToEndpoint', type: 'array-clause' }, + { name: 'onException', type: 'array-clause' }, + { name: 'onCompletion', type: 'array-clause' }, ], ], ['intercept', [{ name: 'steps', type: 'branch' }]], @@ -501,13 +502,16 @@ describe('CamelComponentSchemaService', () => { ['interceptSendToEndpoint', [{ name: 'steps', type: 'branch' }]], ['onException', [{ name: 'steps', type: 'branch' }]], ['onCompletion', [{ name: 'steps', type: 'branch' }]], - ])(`should return the steps properties for '%s'`, (processorName, result) => { - const stepsProperties = CamelComponentSchemaService.getProcessorStepsProperties( - processorName as keyof ProcessorDefinition, - ); - - expect(stepsProperties).toEqual(result); - }); + ] as [string, CamelProcessorStepsProperties[]][])( + `should return the steps properties for '%s'`, + (processorName, result) => { + const stepsProperties = CamelComponentSchemaService.getProcessorStepsProperties( + processorName as keyof ProcessorDefinition, + ); + + expect(stepsProperties).toEqual(result); + }, + ); }); describe('getIconName', () => { diff --git a/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.ts b/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.ts index bf2264424..d3983a7d1 100644 --- a/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.ts +++ b/packages/ui/src/models/visualization/flows/support/camel-component-schema.service.ts @@ -176,24 +176,24 @@ export class CamelComponentSchemaService { case 'choice': return [ - { name: 'when', type: 'clause-list' }, + { name: 'when', type: 'array-clause' }, { name: 'otherwise', type: 'single-clause' }, ]; case 'doTry': return [ { name: 'steps', type: 'branch' }, - { name: 'doCatch', type: 'clause-list' }, + { name: 'doCatch', type: 'array-clause' }, { name: 'doFinally', type: 'single-clause' }, ]; case 'routeConfiguration' as keyof ProcessorDefinition: return [ - { name: 'intercept', type: 'clause-list' }, - { name: 'interceptFrom', type: 'clause-list' }, - { name: 'interceptSendToEndpoint', type: 'clause-list' }, - { name: 'onException', type: 'clause-list' }, - { name: 'onCompletion', type: 'clause-list' }, + { name: 'intercept', type: 'array-clause' }, + { name: 'interceptFrom', type: 'array-clause' }, + { name: 'interceptSendToEndpoint', type: 'array-clause' }, + { name: 'onException', type: 'array-clause' }, + { name: 'onCompletion', type: 'array-clause' }, ]; default: diff --git a/packages/ui/src/models/visualization/flows/support/camel-component-types.ts b/packages/ui/src/models/visualization/flows/support/camel-component-types.ts index fcf0513da..0ae59bd25 100644 --- a/packages/ui/src/models/visualization/flows/support/camel-component-types.ts +++ b/packages/ui/src/models/visualization/flows/support/camel-component-types.ts @@ -20,7 +20,7 @@ export interface CamelProcessorStepsProperties { * Property handling type * single-clause: the property can have a single-clause type of processor, f.i. `otherwise` and `doFinally` * branch: the property have a list of `processors`, f.i. `steps` - * clause-list: the property can have a list of clause processors, usually in the shape of `expression`, f.i. `when` and `doCatch` + * array-clause: the property is an array of clause processors, usually in the shape of `expression`, f.i. `when` and `doCatch` */ - type: 'single-clause' | 'branch' | 'clause-list'; + type: 'single-clause' | 'branch' | 'array-clause'; } diff --git a/packages/ui/src/models/visualization/flows/support/camel-steps.service.ts b/packages/ui/src/models/visualization/flows/support/camel-steps.service.ts deleted file mode 100644 index eb145d74e..000000000 --- a/packages/ui/src/models/visualization/flows/support/camel-steps.service.ts +++ /dev/null @@ -1,104 +0,0 @@ -/* eslint-disable no-case-declarations */ -import { DoCatch, ProcessorDefinition, When1 } from '@kaoto/camel-catalog/types'; -import { getValue } from '../../../../utils'; -import { NodeIconResolver, NodeIconType } from '../../../../utils/node-icon-resolver'; -import { IVisualizationNode } from '../../base-visual-entity'; -import { createVisualizationNode } from '../../visualization-node'; -import { CamelComponentSchemaService } from './camel-component-schema.service'; -import { - CamelProcessorStepsProperties, - CamelRouteVisualEntityData, - ICamelElementLookupResult, -} from './camel-component-types'; - -export class CamelStepsService { - static getVizNodeFromProcessor( - path: string, - componentLookup: ICamelElementLookupResult, - entityDefinition: unknown, - ): IVisualizationNode { - const nodeIconType = componentLookup.componentName ? NodeIconType.Component : NodeIconType.EIP; - const data: CamelRouteVisualEntityData = { - path, - icon: NodeIconResolver.getIcon(CamelComponentSchemaService.getIconName(componentLookup), nodeIconType), - processorName: componentLookup.processorName, - componentName: componentLookup.componentName, - }; - - const vizNode = createVisualizationNode(componentLookup.componentName ?? componentLookup.processorName, data); - - const childrenStepsProperties = CamelComponentSchemaService.getProcessorStepsProperties( - componentLookup.processorName as keyof ProcessorDefinition, - ); - - if (childrenStepsProperties.length > 0) { - vizNode.data.isGroup = true; - } - - childrenStepsProperties.forEach((stepsProperty) => { - const childrenVizNodes = this.getVizNodesFromChildren(path, stepsProperty, entityDefinition); - childrenVizNodes.forEach((childVizNode) => { - vizNode.addChild(childVizNode); - }); - }); - - return vizNode; - } - - private static getVizNodesFromChildren( - path: string, - stepsProperty: CamelProcessorStepsProperties, - entityDefinition: unknown, - ): IVisualizationNode[] { - let singlePath: string; - - switch (stepsProperty.type) { - case 'branch': - singlePath = `${path}.${stepsProperty.name}`; - const stepsList = getValue(entityDefinition, singlePath, []) as ProcessorDefinition[]; - - return stepsList.reduce((accStepsNodes, step, index) => { - const singlePropertyName = Object.keys(step)[0]; - const childPath = `${singlePath}.${index}.${singlePropertyName}`; - const childComponentLookup = CamelComponentSchemaService.getCamelComponentLookup( - childPath, - getValue(step, singlePropertyName), - ); - - const vizNode = this.getVizNodeFromProcessor(childPath, childComponentLookup, entityDefinition); - - const previousVizNode = accStepsNodes[accStepsNodes.length - 1]; - if (previousVizNode !== undefined) { - previousVizNode.setNextNode(vizNode); - vizNode.setPreviousNode(previousVizNode); - } - - accStepsNodes.push(vizNode); - return accStepsNodes; - }, [] as IVisualizationNode[]); - - case 'single-clause': - const childPath = `${path}.${stepsProperty.name}`; - const childComponentLookup = CamelComponentSchemaService.getCamelComponentLookup(childPath, entityDefinition); - - /** If the single-clause property is not defined, we don't create a IVisualizationNode for it */ - if (getValue(entityDefinition, childPath) === undefined) return []; - - return [this.getVizNodeFromProcessor(childPath, childComponentLookup, entityDefinition)]; - - case 'clause-list': - singlePath = `${path}.${stepsProperty.name}`; - const expressionList = getValue(entityDefinition, singlePath, []) as When1[] | DoCatch[]; - - return expressionList.map((_step, index) => { - const childPath = `${singlePath}.${index}`; - const childComponentLookup = { processorName: stepsProperty.name as keyof ProcessorDefinition }; // when, doCatch - - return this.getVizNodeFromProcessor(childPath, childComponentLookup, entityDefinition); - }); - - default: - return []; - } - } -} diff --git a/packages/ui/src/tests/__snapshots__/nodes-edges.test.ts.snap b/packages/ui/src/tests/__snapshots__/nodes-edges.test.ts.snap index 07d25688f..c9d8e049d 100644 --- a/packages/ui/src/tests/__snapshots__/nodes-edges.test.ts.snap +++ b/packages/ui/src/tests/__snapshots__/nodes-edges.test.ts.snap @@ -72,7 +72,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice.when.0", @@ -119,7 +118,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice.otherwise", @@ -132,7 +130,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice", @@ -412,7 +409,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice.when.0", @@ -459,7 +455,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice.otherwise", @@ -472,7 +467,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice", @@ -572,7 +566,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice.when.0", @@ -619,7 +612,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice.otherwise", @@ -632,7 +624,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice", @@ -759,7 +750,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` [Circular], ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice.when.0", @@ -816,7 +806,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice.otherwise", @@ -829,7 +818,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice", @@ -1274,7 +1262,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice.when.0", @@ -1331,7 +1318,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice.otherwise", @@ -1344,7 +1330,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice", @@ -1784,7 +1769,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` [Circular], ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice.otherwise", @@ -1840,7 +1824,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice.when.0", @@ -1854,7 +1837,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` [Circular], ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice", @@ -2299,7 +2281,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice.otherwise", @@ -2355,7 +2336,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice.when.0", @@ -2369,7 +2349,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` [Circular], ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice", @@ -2827,7 +2806,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice.when.0", @@ -2874,7 +2852,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice.otherwise", @@ -2887,7 +2864,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice", @@ -3390,7 +3366,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice.when.0", @@ -3437,7 +3412,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice.otherwise", @@ -3450,7 +3424,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice", @@ -3512,7 +3485,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice.when.0", @@ -3559,7 +3531,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice.otherwise", @@ -3572,7 +3543,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice", @@ -3724,7 +3694,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice.when.0", @@ -3771,7 +3740,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice.otherwise", @@ -3784,7 +3752,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice", @@ -4103,7 +4070,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice.when.0", @@ -4150,7 +4116,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice.otherwise", @@ -4163,7 +4128,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice", @@ -4246,7 +4210,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice.when.0", @@ -4293,7 +4256,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice.otherwise", @@ -4306,7 +4268,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice", @@ -4429,7 +4390,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice.when.0", @@ -4476,7 +4436,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice.otherwise", @@ -4489,7 +4448,6 @@ exports[`Nodes and Edges should generate edges for steps with branches 1`] = ` }, ], "data": { - "componentName": undefined, "icon": "", "isGroup": true, "path": "from.steps.0.choice",