From 76d0cd5608647bca6f3f073e256770c9d323c6be Mon Sep 17 00:00:00 2001 From: Ricardo M Date: Fri, 3 Nov 2023 10:52:39 +0100 Subject: [PATCH 1/3] feat(viz): Replace Camel Route nodes Currently, only CamelK Pipes support to replace nodes, leveraging source and sink placeholders. This commit adds the same functionality for Camel Routes fix: https://github.com/KaotoIO/kaoto-next/issues/79 --- .../Visualization/Custom/ItemReplaceNode.tsx | 1 - .../flows/camel-route-visual-entity.ts | 18 ++++++++----- .../camel-component-default.service.ts | 25 +++++++++++++------ 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/packages/ui/src/components/Visualization/Custom/ItemReplaceNode.tsx b/packages/ui/src/components/Visualization/Custom/ItemReplaceNode.tsx index 718d271fa..36b4350a6 100644 --- a/packages/ui/src/components/Visualization/Custom/ItemReplaceNode.tsx +++ b/packages/ui/src/components/Visualization/Custom/ItemReplaceNode.tsx @@ -24,7 +24,6 @@ export const ItemReplaceNode: FunctionComponent = (props) => { /** Open Catalog modal, filtering the compatible nodes */ const definedComponent = await catalogModalContext?.getNewComponent(compatibleNodes); - console.log(definedComponent); if (!definedComponent) return; /** Add new node to the entities */ diff --git a/packages/ui/src/models/visualization/flows/camel-route-visual-entity.ts b/packages/ui/src/models/visualization/flows/camel-route-visual-entity.ts index 26f4b2c2a..5cae6cfc3 100644 --- a/packages/ui/src/models/visualization/flows/camel-route-visual-entity.ts +++ b/packages/ui/src/models/visualization/flows/camel-route-visual-entity.ts @@ -97,16 +97,18 @@ export class CamelRouteVisualEntity implements BaseVisualCamelEntity { targetProperty?: string; }) { if (options.data.path === undefined) return; - console.log(options); - const defaultValue = CamelComponentDefaultService.getDefaultNodeDefinitionValue(options.definedComponent); const stepsProperties = CamelComponentSchemaService.getProcessorStepsProperties( (options.data as CamelRouteVisualEntityData).processorName as keyof ProcessorDefinition, ); - if (options.mode === AddStepMode.InsertChildStep || options.mode === AddStepMode.InsertSpecialChildStep) { + /** Replace the root `from` step */ + if (options.mode === AddStepMode.ReplaceStep && options.data.path === 'from' && isDefined(this.route.from)) { + const fromValue = CamelComponentDefaultService.getDefaultFromDefinitionValue(options.definedComponent); + Object.assign(this.route.from, fromValue); + return; + } else if (options.mode === AddStepMode.InsertChildStep || options.mode === AddStepMode.InsertSpecialChildStep) { this.insertChildStep(options, stepsProperties, defaultValue); - return; } @@ -134,10 +136,14 @@ export class CamelRouteVisualEntity implements BaseVisualCamelEntity { * last: setHeader */ if (!Number.isInteger(Number(last)) && Number.isInteger(Number(penultimate))) { - const desiredIndex = options.mode === AddStepMode.PrependStep ? Number(penultimate) : Number(penultimate) + 1; + /** If we're in Append mode, we need to insert the step after the selected index hence `Number(penultimate) + 1` */ + const desiredStartIndex = options.mode === AddStepMode.AppendStep ? Number(penultimate) + 1 : Number(penultimate); + + /** If we're in Replace mode, we need to delete the existing step */ + const deleteCount = options.mode === AddStepMode.ReplaceStep ? 1 : 0; const stepsArray: ProcessorDefinition[] = get(this.route, pathArray.slice(0, -2), []); - stepsArray.splice(desiredIndex, 0, defaultValue); + stepsArray.splice(desiredStartIndex, deleteCount, defaultValue); return; } diff --git a/packages/ui/src/models/visualization/flows/support/camel-component-default.service.ts b/packages/ui/src/models/visualization/flows/support/camel-component-default.service.ts index 5234bd8ac..3482aa961 100644 --- a/packages/ui/src/models/visualization/flows/support/camel-component-default.service.ts +++ b/packages/ui/src/models/visualization/flows/support/camel-component-default.service.ts @@ -10,6 +10,17 @@ import { CatalogKind } from '../../../catalog-kind'; * This class is meant to provide working default values for Camel components. */ export class CamelComponentDefaultService { + /** + * Get the default definition for the `from` component + */ + static getDefaultFromDefinitionValue(definedComponent: DefinedComponent): ProcessorDefinition { + return parse(` + id: ${getCamelRandomId('from')} + uri: "${definedComponent.name}" + parameters: {} + `); + } + /** * Get the default value for a given component and property */ @@ -27,14 +38,12 @@ export class CamelComponentDefaultService { } private static getDefaultValueFromComponent(componentName: string): object { - switch (componentName) { - default: - return parse(` - to: - uri: "${componentName}" - id: ${getCamelRandomId('to')} - `); - } + return parse(` + to: + id: ${getCamelRandomId('to')} + uri: "${componentName}" + parameters: {} + `); } private static getDefaultValueFromKamelet(kameletName: string): object { From d13622b40d394f19914fd0e29c49fe97cc06dc21 Mon Sep 17 00:00:00 2001 From: Ricardo M Date: Fri, 3 Nov 2023 11:37:45 +0100 Subject: [PATCH 2/3] feat(viz): Improve remove from node from Camel Route --- .../visualization/flows/camel-route-visual-entity.test.ts | 4 ++-- .../models/visualization/flows/camel-route-visual-entity.ts | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/models/visualization/flows/camel-route-visual-entity.test.ts b/packages/ui/src/models/visualization/flows/camel-route-visual-entity.test.ts index 0c58e1b1d..37aa8f812 100644 --- a/packages/ui/src/models/visualization/flows/camel-route-visual-entity.test.ts +++ b/packages/ui/src/models/visualization/flows/camel-route-visual-entity.test.ts @@ -234,11 +234,11 @@ describe('Camel Route', () => { expect(vizNode.data.label).toEqual('timer:tutorial'); }); - it('should set an empty label if the uri is not available', () => { + it('should set a default label if the uri is not available', () => { camelEntity = new CamelRouteVisualEntity({ from: {} } as RouteDefinition); const vizNode = camelEntity.toVizNode(); - expect(vizNode.data.label).toEqual(''); + expect(vizNode.data.label).toEqual('from: Unknown'); }); it('should populate the viz node chain with the steps', () => { diff --git a/packages/ui/src/models/visualization/flows/camel-route-visual-entity.ts b/packages/ui/src/models/visualization/flows/camel-route-visual-entity.ts index 5cae6cfc3..3ebce3138 100644 --- a/packages/ui/src/models/visualization/flows/camel-route-visual-entity.ts +++ b/packages/ui/src/models/visualization/flows/camel-route-visual-entity.ts @@ -232,6 +232,11 @@ export class CamelRouteVisualEntity implements BaseVisualCamelEntity { const rootNode = this.getVizNodeFromProcessor('from', { processorName: 'from' as keyof ProcessorDefinition }); rootNode.data.entity = this; + if (!this.route.from?.uri) { + rootNode.data.label = 'from: Unknown'; + rootNode.data.icon = NodeIconResolver.getPlaceholderIcon(); + } + return rootNode; } From f95888687963a5c30ddf8cfe37249ff8d12eea82 Mon Sep 17 00:00:00 2001 From: Ricardo M Date: Fri, 3 Nov 2023 12:31:34 +0100 Subject: [PATCH 3/3] feat(viz): Filter appropriate components for Camel Route nodes --- .../camel-utils/camel-to-tile.adapter.test.ts | 13 +++++ .../src/camel-utils/camel-to-tile.adapter.ts | 6 +++ .../src/models/camel/camel-route-resource.ts | 47 +++++++++++++++++-- .../flows/camel-route-visual-entity.ts | 5 +- .../support/camel-component-schema.service.ts | 20 -------- 5 files changed, 64 insertions(+), 27 deletions(-) diff --git a/packages/ui/src/camel-utils/camel-to-tile.adapter.test.ts b/packages/ui/src/camel-utils/camel-to-tile.adapter.test.ts index 036f17334..62ac736a4 100644 --- a/packages/ui/src/camel-utils/camel-to-tile.adapter.test.ts +++ b/packages/ui/src/camel-utils/camel-to-tile.adapter.test.ts @@ -43,6 +43,19 @@ describe('camelComponentToTile', () => { expect(tile.tags).toEqual(['label1', 'label2']); expect(tile.version).toEqual('4.0.0'); }); + + it('should populate tags with `consumerOnly` and `producerOnly` when applicable', () => { + const componentDef = { + component: { + consumerOnly: true, + producerOnly: true, + }, + } as ICamelComponentDefinition; + + const tile = camelComponentToTile(componentDef); + + expect(tile.tags).toEqual(['consumerOnly', 'producerOnly']); + }); }); describe('camelProcessorToTile', () => { diff --git a/packages/ui/src/camel-utils/camel-to-tile.adapter.ts b/packages/ui/src/camel-utils/camel-to-tile.adapter.ts index b964d0666..a4d7fe1c0 100644 --- a/packages/ui/src/camel-utils/camel-to-tile.adapter.ts +++ b/packages/ui/src/camel-utils/camel-to-tile.adapter.ts @@ -12,6 +12,12 @@ export const camelComponentToTile = (componentDef: ICamelComponentDefinition): I if (label) { tags.push(...label.split(',')); } + if (componentDef.component.consumerOnly) { + tags.push('consumerOnly'); + } + if (componentDef.component.producerOnly) { + tags.push('producerOnly'); + } return { type: CatalogKind.Component, diff --git a/packages/ui/src/models/camel/camel-route-resource.ts b/packages/ui/src/models/camel/camel-route-resource.ts index 8b42314cc..f37d623bc 100644 --- a/packages/ui/src/models/camel/camel-route-resource.ts +++ b/packages/ui/src/models/camel/camel-route-resource.ts @@ -1,10 +1,11 @@ import { RouteDefinition } from '@kaoto-next/camel-catalog/types'; +import { ITile } from '../../components/Catalog'; import { isDefined } from '../../utils'; import { CatalogFilter } from '../catalog-filter'; +import { CatalogKind } from '../catalog-kind'; import { AddStepMode } from '../visualization/base-visual-entity'; import { CamelRouteVisualEntity, isCamelRoute } from '../visualization/flows'; import { flowTemplateService } from '../visualization/flows/flow-templates-service'; -import { CamelComponentSchemaService } from '../visualization/flows/support/camel-component-schema.service'; import { CamelRouteVisualEntityData } from '../visualization/flows/support/camel-component-types'; import { BeansEntity, isBeans } from '../visualization/metadata'; import { BeansAwareResource, CamelResource } from './camel-resource'; @@ -95,10 +96,50 @@ export class CamelRouteResource implements CamelResource, BeansAwareResource { /** Components Catalog related methods */ getCompatibleComponents(mode: AddStepMode, visualEntityData: CamelRouteVisualEntityData): CatalogFilter { + return { + filterFunction: this.getFilterFunction(mode, visualEntityData), + }; + } + + private getFilterFunction(mode: AddStepMode, visualEntityData: CamelRouteVisualEntityData): (item: ITile) => boolean { + if (mode === AddStepMode.ReplaceStep && visualEntityData.path === 'from') { + /** + * For the `from` step we want to show only components which are not `producerOnly`, + * as this mean that they can be used only as a consumer. + */ + return (item: ITile) => { + return item.type === CatalogKind.Component && !item.tags.includes('producerOnly'); + }; + } + if (mode === AddStepMode.InsertSpecialChildStep) { - return CamelComponentSchemaService.getCompatibleComponents(visualEntityData.processorName); + /** + * specialChildren is a map of processor names and their special children. + */ + const specialChildren: Record = { + choice: ['when', 'otherwise'], + doTry: ['doCatch', 'doFinally'], + }; + + /** + * For special child steps, we need to check which type of processor it is, in order to determine + * what kind of components we want to show. + */ + return (item: ITile) => { + if (item.type !== CatalogKind.Processor || specialChildren[visualEntityData.processorName] === undefined) { + return false; + } + + return specialChildren[visualEntityData.processorName].includes(item.name); + }; } - return {}; + /** + * For the rest, we want to filter out components that are `consumerOnly`, + * as this mean that they can be used only as a consumer. + */ + return (item: ITile) => { + return !item.tags.includes('consumerOnly'); + }; } } diff --git a/packages/ui/src/models/visualization/flows/camel-route-visual-entity.ts b/packages/ui/src/models/visualization/flows/camel-route-visual-entity.ts index 3ebce3138..d9dfdf50f 100644 --- a/packages/ui/src/models/visualization/flows/camel-route-visual-entity.ts +++ b/packages/ui/src/models/visualization/flows/camel-route-visual-entity.ts @@ -211,14 +211,11 @@ export class CamelRouteVisualEntity implements BaseVisualCamelEntity { const stepsProperties = CamelComponentSchemaService.getProcessorStepsProperties( (data as CamelRouteVisualEntityData).processorName as keyof ProcessorDefinition, ); - const catalogFilter = CamelComponentSchemaService.getCompatibleComponents( - (data as CamelRouteVisualEntityData).processorName as keyof ProcessorDefinition, - ); const canHavePreviousStep = CamelComponentSchemaService.canHavePreviousStep( (data as CamelRouteVisualEntityData).processorName, ); const canHaveChildren = stepsProperties.find((property) => property.type === 'branch') !== undefined; - const canHaveSpecialChildren = Object.keys(catalogFilter).length > 0; + const canHaveSpecialChildren = Object.keys(stepsProperties).length > 1; return { canHavePreviousStep, 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 2b40b9427..81c030d67 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 @@ -3,7 +3,6 @@ import type { JSONSchemaType } from 'ajv'; import { isDefined } from '../../../../utils'; import { ICamelComponentProperty } from '../../../camel-components-catalog'; import { ICamelProcessorProperty } from '../../../camel-processors-catalog'; -import { CatalogFilter } from '../../../catalog-filter'; import { CatalogKind } from '../../../catalog-kind'; import { VisualComponentSchema } from '../../base-visual-entity'; import { CamelCatalogService } from '../camel-catalog.service'; @@ -77,25 +76,6 @@ export class CamelComponentSchemaService { return !this.DISABLED_SIBLING_STEPS.includes(processorName); } - static getCompatibleComponents(processorName: keyof ProcessorDefinition): CatalogFilter { - switch (processorName) { - case 'choice': - return { - kinds: [CatalogKind.Processor], - names: ['when', 'otherwise'], - }; - - case 'doTry': - return { - kinds: [CatalogKind.Processor], - names: ['doCatch', 'doFinally'], - }; - - default: - return {}; - } - } - static getProcessorStepsProperties(processorName: keyof ProcessorDefinition): CamelProcessorStepsProperties[] { switch (processorName) { /** choice */ case 'when':