From 7ab1a54a87775bcf5db2dfb1294ac0f39fda8618 Mon Sep 17 00:00:00 2001 From: Ricardo M Date: Mon, 15 Apr 2024 15:56:18 +0200 Subject: [PATCH] feat(RouteConfiguration): Add support for RouteConfiguration entity This commit adds support for RouteConfiguration entity. In detail, the following entities were added: * intercept * interceptFrom * interceptSendToEndpoint * onCompletion Also the nested version of said entities were added fix: https://github.com/KaotoIO/kaoto/issues/492 --- .../src/models/camel/camel-route-resource.ts | 10 + .../abstract-camel-visual-entity.test.ts.snap | 10 +- ...-intercept-from-visual-entity.test.ts.snap | 121 +++++++++++ ...end-to-endpoint-visual-entity.test.ts.snap | 121 +++++++++++ ...camel-intercept-visual-entity.test.ts.snap | 121 +++++++++++ ...l-on-completion-visual-entity.test.ts.snap | 121 +++++++++++ .../flows/abstract-camel-visual-entity.ts | 38 +--- .../camel-error-handler-visual-entity.test.ts | 6 +- ...camel-intercept-from-visual-entity.test.ts | 110 ++++++++++ .../camel-intercept-from-visual-entity.ts | 117 ++++++++++ ...ept-send-to-endpoint-visual-entity.test.ts | 110 ++++++++++ ...ntercept-send-to-endpoint-visual-entity.ts | 127 +++++++++++ .../camel-intercept-visual-entity.test.ts | 104 +++++++++ .../flows/camel-intercept-visual-entity.ts | 100 +++++++++ .../camel-on-completion-visual-entity.test.ts | 107 ++++++++++ .../camel-on-completion-visual-entity.ts | 102 +++++++++ .../flows/camel-on-exception-visual-entity.ts | 196 ++--------------- ...camel-route-configuration-visual-entity.ts | 201 ++++++++++++++++++ .../flows/camel-route-visual-entity.ts | 46 +++- .../flows/kamelet-visual-entity.ts | 58 ++++- 20 files changed, 1699 insertions(+), 227 deletions(-) create mode 100644 packages/ui/src/models/visualization/flows/__snapshots__/camel-intercept-from-visual-entity.test.ts.snap create mode 100644 packages/ui/src/models/visualization/flows/__snapshots__/camel-intercept-send-to-endpoint-visual-entity.test.ts.snap create mode 100644 packages/ui/src/models/visualization/flows/__snapshots__/camel-intercept-visual-entity.test.ts.snap create mode 100644 packages/ui/src/models/visualization/flows/__snapshots__/camel-on-completion-visual-entity.test.ts.snap create mode 100644 packages/ui/src/models/visualization/flows/camel-intercept-from-visual-entity.test.ts create mode 100644 packages/ui/src/models/visualization/flows/camel-intercept-from-visual-entity.ts create mode 100644 packages/ui/src/models/visualization/flows/camel-intercept-send-to-endpoint-visual-entity.test.ts create mode 100644 packages/ui/src/models/visualization/flows/camel-intercept-send-to-endpoint-visual-entity.ts create mode 100644 packages/ui/src/models/visualization/flows/camel-intercept-visual-entity.test.ts create mode 100644 packages/ui/src/models/visualization/flows/camel-intercept-visual-entity.ts create mode 100644 packages/ui/src/models/visualization/flows/camel-on-completion-visual-entity.test.ts create mode 100644 packages/ui/src/models/visualization/flows/camel-on-completion-visual-entity.ts create mode 100644 packages/ui/src/models/visualization/flows/camel-route-configuration-visual-entity.ts diff --git a/packages/ui/src/models/camel/camel-route-resource.ts b/packages/ui/src/models/camel/camel-route-resource.ts index 35da16061..f79299914 100644 --- a/packages/ui/src/models/camel/camel-route-resource.ts +++ b/packages/ui/src/models/camel/camel-route-resource.ts @@ -4,8 +4,13 @@ import { createCamelPropertiesSorter, isDefined } from '../../utils'; import { AddStepMode } from '../visualization/base-visual-entity'; import { CamelRouteVisualEntity, isCamelFrom, isCamelRoute } from '../visualization/flows'; import { CamelErrorHandlerVisualEntity } from '../visualization/flows/camel-error-handler-visual-entity'; +import { CamelInterceptFromVisualEntity } from '../visualization/flows/camel-intercept-from-visual-entity'; +import { CamelInterceptSendToEndpointVisualEntity } from '../visualization/flows/camel-intercept-send-to-endpoint-visual-entity'; +import { CamelInterceptVisualEntity } from '../visualization/flows/camel-intercept-visual-entity'; +import { CamelOnCompletionVisualEntity } from '../visualization/flows/camel-on-completion-visual-entity'; import { CamelOnExceptionVisualEntity } from '../visualization/flows/camel-on-exception-visual-entity'; import { CamelRestConfigurationVisualEntity } from '../visualization/flows/camel-rest-configuration-visual-entity'; +import { CamelRouteConfigurationVisualEntity } from '../visualization/flows/camel-route-configuration-visual-entity'; import { NonVisualEntity } from '../visualization/flows/non-visual-entity'; import { CamelComponentFilterService } from '../visualization/flows/support/camel-component-filter.service'; import { CamelRouteVisualEntityData } from '../visualization/flows/support/camel-component-types'; @@ -18,8 +23,13 @@ import { SourceSchemaType } from './source-schema-type'; export class CamelRouteResource implements CamelResource, BeansAwareResource { static readonly SUPPORTED_ENTITIES = [ CamelOnExceptionVisualEntity, + CamelOnCompletionVisualEntity, CamelErrorHandlerVisualEntity, CamelRestConfigurationVisualEntity, + CamelRouteConfigurationVisualEntity, + CamelInterceptVisualEntity, + CamelInterceptFromVisualEntity, + CamelInterceptSendToEndpointVisualEntity, ] as const; static readonly PARAMETERS_ORDER = ['id', 'description', 'uri', 'parameters', 'steps']; readonly sortFn = createCamelPropertiesSorter(CamelRouteResource.PARAMETERS_ORDER) as ( diff --git a/packages/ui/src/models/visualization/flows/__snapshots__/abstract-camel-visual-entity.test.ts.snap b/packages/ui/src/models/visualization/flows/__snapshots__/abstract-camel-visual-entity.test.ts.snap index 56dacf90b..12e26c9e9 100644 --- a/packages/ui/src/models/visualization/flows/__snapshots__/abstract-camel-visual-entity.test.ts.snap +++ b/packages/ui/src/models/visualization/flows/__snapshots__/abstract-camel-visual-entity.test.ts.snap @@ -20,7 +20,7 @@ exports[`AbstractCamelVisualEntity getNodeInteraction should return the correct "canHaveSpecialChildren": false, "canRemoveFlow": false, "canRemoveStep": true, - "canReplaceStep": true, + "canReplaceStep": false, } `; @@ -32,7 +32,7 @@ exports[`AbstractCamelVisualEntity getNodeInteraction should return the correct "canHaveSpecialChildren": false, "canRemoveFlow": false, "canRemoveStep": true, - "canReplaceStep": true, + "canReplaceStep": false, } `; @@ -44,7 +44,7 @@ exports[`AbstractCamelVisualEntity getNodeInteraction should return the correct "canHaveSpecialChildren": false, "canRemoveFlow": false, "canRemoveStep": true, - "canReplaceStep": true, + "canReplaceStep": false, } `; @@ -68,7 +68,7 @@ exports[`AbstractCamelVisualEntity getNodeInteraction should return the correct "canHaveSpecialChildren": false, "canRemoveFlow": false, "canRemoveStep": true, - "canReplaceStep": true, + "canReplaceStep": false, } `; @@ -80,7 +80,7 @@ exports[`AbstractCamelVisualEntity getNodeInteraction should return the correct "canHaveSpecialChildren": false, "canRemoveFlow": false, "canRemoveStep": true, - "canReplaceStep": true, + "canReplaceStep": false, } `; diff --git a/packages/ui/src/models/visualization/flows/__snapshots__/camel-intercept-from-visual-entity.test.ts.snap b/packages/ui/src/models/visualization/flows/__snapshots__/camel-intercept-from-visual-entity.test.ts.snap new file mode 100644 index 000000000..135d1138d --- /dev/null +++ b/packages/ui/src/models/visualization/flows/__snapshots__/camel-intercept-from-visual-entity.test.ts.snap @@ -0,0 +1,121 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`CamelInterceptFromVisualEntity getNodeInteraction should return the correct interaction for the '{ + processorName: 'interceptSendToEndpoint', + path: 'interceptSendToEndpoint' +}' processor 1`] = ` +{ + "canHaveChildren": true, + "canHaveNextStep": false, + "canHavePreviousStep": false, + "canHaveSpecialChildren": false, + "canRemoveFlow": false, + "canRemoveStep": true, + "canReplaceStep": true, +} +`; + +exports[`CamelInterceptFromVisualEntity getNodeInteraction should return the correct interaction for the '{ processorName: 'from', path: 'from' }' processor 1`] = ` +{ + "canHaveChildren": true, + "canHaveNextStep": false, + "canHavePreviousStep": false, + "canHaveSpecialChildren": false, + "canRemoveFlow": false, + "canRemoveStep": true, + "canReplaceStep": true, +} +`; + +exports[`CamelInterceptFromVisualEntity getNodeInteraction should return the correct interaction for the '{ processorName: 'intercept', path: 'intercept' }' processor 1`] = ` +{ + "canHaveChildren": true, + "canHaveNextStep": false, + "canHavePreviousStep": false, + "canHaveSpecialChildren": false, + "canRemoveFlow": false, + "canRemoveStep": true, + "canReplaceStep": true, +} +`; + +exports[`CamelInterceptFromVisualEntity getNodeInteraction should return the correct interaction for the '{ processorName: 'interceptFrom', path: 'interceptFrom' }' processor 1`] = ` +{ + "canHaveChildren": true, + "canHaveNextStep": false, + "canHavePreviousStep": false, + "canHaveSpecialChildren": false, + "canRemoveFlow": true, + "canRemoveStep": false, + "canReplaceStep": false, +} +`; + +exports[`CamelInterceptFromVisualEntity getNodeInteraction should return the correct interaction for the '{ processorName: 'log', path: 'log' }' processor 1`] = ` +{ + "canHaveChildren": false, + "canHaveNextStep": true, + "canHavePreviousStep": true, + "canHaveSpecialChildren": false, + "canRemoveFlow": false, + "canRemoveStep": true, + "canReplaceStep": true, +} +`; + +exports[`CamelInterceptFromVisualEntity getNodeInteraction should return the correct interaction for the '{ processorName: 'onCompletion', path: 'onCompletion' }' processor 1`] = ` +{ + "canHaveChildren": true, + "canHaveNextStep": false, + "canHavePreviousStep": false, + "canHaveSpecialChildren": false, + "canRemoveFlow": false, + "canRemoveStep": true, + "canReplaceStep": true, +} +`; + +exports[`CamelInterceptFromVisualEntity getNodeInteraction should return the correct interaction for the '{ processorName: 'onException', path: 'onException' }' processor 1`] = ` +{ + "canHaveChildren": true, + "canHaveNextStep": false, + "canHavePreviousStep": false, + "canHaveSpecialChildren": false, + "canRemoveFlow": false, + "canRemoveStep": true, + "canReplaceStep": true, +} +`; + +exports[`CamelInterceptFromVisualEntity getNodeInteraction should return the correct interaction for the '{ processorName: 'route', path: 'route' }' processor 1`] = ` +{ + "canHaveChildren": false, + "canHaveNextStep": true, + "canHavePreviousStep": true, + "canHaveSpecialChildren": false, + "canRemoveFlow": false, + "canRemoveStep": true, + "canReplaceStep": true, +} +`; + +exports[`CamelInterceptFromVisualEntity getNodeInteraction should return the correct interaction for the '{ processorName: 'to', path: 'to' }' processor 1`] = ` +{ + "canHaveChildren": false, + "canHaveNextStep": true, + "canHavePreviousStep": true, + "canHaveSpecialChildren": false, + "canRemoveFlow": false, + "canRemoveStep": true, + "canReplaceStep": true, +} +`; + +exports[`CamelInterceptFromVisualEntity should serialize the entity 1`] = ` +{ + "interceptFrom": { + "id": "interceptFrom-1234", + "uri": "direct:a-reference", + }, +} +`; diff --git a/packages/ui/src/models/visualization/flows/__snapshots__/camel-intercept-send-to-endpoint-visual-entity.test.ts.snap b/packages/ui/src/models/visualization/flows/__snapshots__/camel-intercept-send-to-endpoint-visual-entity.test.ts.snap new file mode 100644 index 000000000..961aa0ecb --- /dev/null +++ b/packages/ui/src/models/visualization/flows/__snapshots__/camel-intercept-send-to-endpoint-visual-entity.test.ts.snap @@ -0,0 +1,121 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`CamelInterceptSendToEndpointVisualEntity getNodeInteraction should return the correct interaction for the '{ + processorName: 'interceptSendToEndpoint', + path: 'interceptSendToEndpoint' +}' processor 1`] = ` +{ + "canHaveChildren": true, + "canHaveNextStep": false, + "canHavePreviousStep": false, + "canHaveSpecialChildren": false, + "canRemoveFlow": true, + "canRemoveStep": false, + "canReplaceStep": false, +} +`; + +exports[`CamelInterceptSendToEndpointVisualEntity getNodeInteraction should return the correct interaction for the '{ processorName: 'from', path: 'from' }' processor 1`] = ` +{ + "canHaveChildren": true, + "canHaveNextStep": false, + "canHavePreviousStep": false, + "canHaveSpecialChildren": false, + "canRemoveFlow": false, + "canRemoveStep": true, + "canReplaceStep": true, +} +`; + +exports[`CamelInterceptSendToEndpointVisualEntity getNodeInteraction should return the correct interaction for the '{ processorName: 'intercept', path: 'intercept' }' processor 1`] = ` +{ + "canHaveChildren": true, + "canHaveNextStep": false, + "canHavePreviousStep": false, + "canHaveSpecialChildren": false, + "canRemoveFlow": false, + "canRemoveStep": true, + "canReplaceStep": true, +} +`; + +exports[`CamelInterceptSendToEndpointVisualEntity getNodeInteraction should return the correct interaction for the '{ processorName: 'interceptFrom', path: 'interceptFrom' }' processor 1`] = ` +{ + "canHaveChildren": true, + "canHaveNextStep": false, + "canHavePreviousStep": false, + "canHaveSpecialChildren": false, + "canRemoveFlow": false, + "canRemoveStep": true, + "canReplaceStep": true, +} +`; + +exports[`CamelInterceptSendToEndpointVisualEntity getNodeInteraction should return the correct interaction for the '{ processorName: 'log', path: 'log' }' processor 1`] = ` +{ + "canHaveChildren": false, + "canHaveNextStep": true, + "canHavePreviousStep": true, + "canHaveSpecialChildren": false, + "canRemoveFlow": false, + "canRemoveStep": true, + "canReplaceStep": true, +} +`; + +exports[`CamelInterceptSendToEndpointVisualEntity getNodeInteraction should return the correct interaction for the '{ processorName: 'onCompletion', path: 'onCompletion' }' processor 1`] = ` +{ + "canHaveChildren": true, + "canHaveNextStep": false, + "canHavePreviousStep": false, + "canHaveSpecialChildren": false, + "canRemoveFlow": false, + "canRemoveStep": true, + "canReplaceStep": true, +} +`; + +exports[`CamelInterceptSendToEndpointVisualEntity getNodeInteraction should return the correct interaction for the '{ processorName: 'onException', path: 'onException' }' processor 1`] = ` +{ + "canHaveChildren": true, + "canHaveNextStep": false, + "canHavePreviousStep": false, + "canHaveSpecialChildren": false, + "canRemoveFlow": false, + "canRemoveStep": true, + "canReplaceStep": true, +} +`; + +exports[`CamelInterceptSendToEndpointVisualEntity getNodeInteraction should return the correct interaction for the '{ processorName: 'route', path: 'route' }' processor 1`] = ` +{ + "canHaveChildren": false, + "canHaveNextStep": true, + "canHavePreviousStep": true, + "canHaveSpecialChildren": false, + "canRemoveFlow": false, + "canRemoveStep": true, + "canReplaceStep": true, +} +`; + +exports[`CamelInterceptSendToEndpointVisualEntity getNodeInteraction should return the correct interaction for the '{ processorName: 'to', path: 'to' }' processor 1`] = ` +{ + "canHaveChildren": false, + "canHaveNextStep": true, + "canHavePreviousStep": true, + "canHaveSpecialChildren": false, + "canRemoveFlow": false, + "canRemoveStep": true, + "canReplaceStep": true, +} +`; + +exports[`CamelInterceptSendToEndpointVisualEntity should serialize the entity 1`] = ` +{ + "interceptSendToEndpoint": { + "id": "interceptSendToEndpoint-1234", + "uri": "direct:a-reference", + }, +} +`; diff --git a/packages/ui/src/models/visualization/flows/__snapshots__/camel-intercept-visual-entity.test.ts.snap b/packages/ui/src/models/visualization/flows/__snapshots__/camel-intercept-visual-entity.test.ts.snap new file mode 100644 index 000000000..67b4050c7 --- /dev/null +++ b/packages/ui/src/models/visualization/flows/__snapshots__/camel-intercept-visual-entity.test.ts.snap @@ -0,0 +1,121 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`CamelInterceptVisualEntity getNodeInteraction should return the correct interaction for the '{ + processorName: 'interceptSendToEndpoint', + path: 'interceptSendToEndpoint' +}' processor 1`] = ` +{ + "canHaveChildren": true, + "canHaveNextStep": false, + "canHavePreviousStep": false, + "canHaveSpecialChildren": false, + "canRemoveFlow": false, + "canRemoveStep": true, + "canReplaceStep": true, +} +`; + +exports[`CamelInterceptVisualEntity getNodeInteraction should return the correct interaction for the '{ processorName: 'from', path: 'from' }' processor 1`] = ` +{ + "canHaveChildren": true, + "canHaveNextStep": false, + "canHavePreviousStep": false, + "canHaveSpecialChildren": false, + "canRemoveFlow": false, + "canRemoveStep": true, + "canReplaceStep": true, +} +`; + +exports[`CamelInterceptVisualEntity getNodeInteraction should return the correct interaction for the '{ processorName: 'intercept', path: 'intercept' }' processor 1`] = ` +{ + "canHaveChildren": true, + "canHaveNextStep": false, + "canHavePreviousStep": false, + "canHaveSpecialChildren": false, + "canRemoveFlow": true, + "canRemoveStep": false, + "canReplaceStep": false, +} +`; + +exports[`CamelInterceptVisualEntity getNodeInteraction should return the correct interaction for the '{ processorName: 'interceptFrom', path: 'interceptFrom' }' processor 1`] = ` +{ + "canHaveChildren": true, + "canHaveNextStep": false, + "canHavePreviousStep": false, + "canHaveSpecialChildren": false, + "canRemoveFlow": false, + "canRemoveStep": true, + "canReplaceStep": true, +} +`; + +exports[`CamelInterceptVisualEntity getNodeInteraction should return the correct interaction for the '{ processorName: 'log', path: 'log' }' processor 1`] = ` +{ + "canHaveChildren": false, + "canHaveNextStep": true, + "canHavePreviousStep": true, + "canHaveSpecialChildren": false, + "canRemoveFlow": false, + "canRemoveStep": true, + "canReplaceStep": true, +} +`; + +exports[`CamelInterceptVisualEntity getNodeInteraction should return the correct interaction for the '{ processorName: 'onCompletion', path: 'onCompletion' }' processor 1`] = ` +{ + "canHaveChildren": true, + "canHaveNextStep": false, + "canHavePreviousStep": false, + "canHaveSpecialChildren": false, + "canRemoveFlow": false, + "canRemoveStep": true, + "canReplaceStep": true, +} +`; + +exports[`CamelInterceptVisualEntity getNodeInteraction should return the correct interaction for the '{ processorName: 'onException', path: 'onException' }' processor 1`] = ` +{ + "canHaveChildren": true, + "canHaveNextStep": false, + "canHavePreviousStep": false, + "canHaveSpecialChildren": false, + "canRemoveFlow": false, + "canRemoveStep": true, + "canReplaceStep": true, +} +`; + +exports[`CamelInterceptVisualEntity getNodeInteraction should return the correct interaction for the '{ processorName: 'route', path: 'route' }' processor 1`] = ` +{ + "canHaveChildren": false, + "canHaveNextStep": true, + "canHavePreviousStep": true, + "canHaveSpecialChildren": false, + "canRemoveFlow": false, + "canRemoveStep": true, + "canReplaceStep": true, +} +`; + +exports[`CamelInterceptVisualEntity getNodeInteraction should return the correct interaction for the '{ processorName: 'to', path: 'to' }' processor 1`] = ` +{ + "canHaveChildren": false, + "canHaveNextStep": true, + "canHavePreviousStep": true, + "canHaveSpecialChildren": false, + "canRemoveFlow": false, + "canRemoveStep": true, + "canReplaceStep": true, +} +`; + +exports[`CamelInterceptVisualEntity should serialize the entity 1`] = ` +{ + "intercept": { + "disabled": false, + "id": "intercept-1234", + }, +} +`; diff --git a/packages/ui/src/models/visualization/flows/__snapshots__/camel-on-completion-visual-entity.test.ts.snap b/packages/ui/src/models/visualization/flows/__snapshots__/camel-on-completion-visual-entity.test.ts.snap new file mode 100644 index 000000000..48a74a02c --- /dev/null +++ b/packages/ui/src/models/visualization/flows/__snapshots__/camel-on-completion-visual-entity.test.ts.snap @@ -0,0 +1,121 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`CamelOnCompletionVisualEntity getNodeInteraction should return the correct interaction for the '{ + processorName: 'interceptSendToEndpoint', + path: 'interceptSendToEndpoint' +}' processor 1`] = ` +{ + "canHaveChildren": true, + "canHaveNextStep": false, + "canHavePreviousStep": false, + "canHaveSpecialChildren": false, + "canRemoveFlow": false, + "canRemoveStep": true, + "canReplaceStep": true, +} +`; + +exports[`CamelOnCompletionVisualEntity getNodeInteraction should return the correct interaction for the '{ processorName: 'from', path: 'from' }' processor 1`] = ` +{ + "canHaveChildren": true, + "canHaveNextStep": false, + "canHavePreviousStep": false, + "canHaveSpecialChildren": false, + "canRemoveFlow": false, + "canRemoveStep": true, + "canReplaceStep": true, +} +`; + +exports[`CamelOnCompletionVisualEntity getNodeInteraction should return the correct interaction for the '{ processorName: 'intercept', path: 'intercept' }' processor 1`] = ` +{ + "canHaveChildren": true, + "canHaveNextStep": false, + "canHavePreviousStep": false, + "canHaveSpecialChildren": false, + "canRemoveFlow": false, + "canRemoveStep": true, + "canReplaceStep": true, +} +`; + +exports[`CamelOnCompletionVisualEntity getNodeInteraction should return the correct interaction for the '{ processorName: 'log', path: 'log' }' processor 1`] = ` +{ + "canHaveChildren": false, + "canHaveNextStep": true, + "canHavePreviousStep": true, + "canHaveSpecialChildren": false, + "canRemoveFlow": false, + "canRemoveStep": true, + "canReplaceStep": true, +} +`; + +exports[`CamelOnCompletionVisualEntity getNodeInteraction should return the correct interaction for the '{ processorName: 'onCompletion', path: 'onCompletion' }' processor 1`] = ` +{ + "canHaveChildren": true, + "canHaveNextStep": false, + "canHavePreviousStep": false, + "canHaveSpecialChildren": false, + "canRemoveFlow": true, + "canRemoveStep": false, + "canReplaceStep": false, +} +`; + +exports[`CamelOnCompletionVisualEntity getNodeInteraction should return the correct interaction for the '{ processorName: 'onCompletion', path: 'onCompletion' }' processor 2`] = ` +{ + "canHaveChildren": true, + "canHaveNextStep": false, + "canHavePreviousStep": false, + "canHaveSpecialChildren": false, + "canRemoveFlow": true, + "canRemoveStep": false, + "canReplaceStep": false, +} +`; + +exports[`CamelOnCompletionVisualEntity getNodeInteraction should return the correct interaction for the '{ processorName: 'onException', path: 'onException' }' processor 1`] = ` +{ + "canHaveChildren": true, + "canHaveNextStep": false, + "canHavePreviousStep": false, + "canHaveSpecialChildren": false, + "canRemoveFlow": false, + "canRemoveStep": true, + "canReplaceStep": true, +} +`; + +exports[`CamelOnCompletionVisualEntity getNodeInteraction should return the correct interaction for the '{ processorName: 'route', path: 'route' }' processor 1`] = ` +{ + "canHaveChildren": false, + "canHaveNextStep": true, + "canHavePreviousStep": true, + "canHaveSpecialChildren": false, + "canRemoveFlow": false, + "canRemoveStep": true, + "canReplaceStep": true, +} +`; + +exports[`CamelOnCompletionVisualEntity getNodeInteraction should return the correct interaction for the '{ processorName: 'to', path: 'to' }' processor 1`] = ` +{ + "canHaveChildren": false, + "canHaveNextStep": true, + "canHavePreviousStep": true, + "canHaveSpecialChildren": false, + "canRemoveFlow": false, + "canRemoveStep": true, + "canReplaceStep": true, +} +`; + +exports[`CamelOnCompletionVisualEntity should serialize the entity 1`] = ` +{ + "onCompletion": { + "id": "onCompletion-1234", + "mode": "AfterConsumer", + }, +} +`; 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 ad2f2dcd4..c2857f29a 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 @@ -1,7 +1,7 @@ /* eslint-disable no-case-declarations */ -import { ProcessorDefinition, RouteDefinition } from '@kaoto-next/camel-catalog/types'; +import { ProcessorDefinition } from '@kaoto-next/camel-catalog/types'; import { SchemaService } from '../../../components/Form/schema.service'; -import { ROOT_PATH, getArrayProperty, getValue, isDefined, setValue } from '../../../utils'; +import { ROOT_PATH, getArrayProperty, getValue, setValue } from '../../../utils'; import { NodeIconResolver } from '../../../utils/node-icon-resolver'; import { DefinedComponent } from '../../camel-catalog-index'; import { EntityType } from '../../camel/entities'; @@ -20,12 +20,13 @@ import { CamelProcessorStepsProperties, CamelRouteVisualEntityData } from './sup import { CamelStepsService } from './support/camel-steps.service'; import { ModelValidationService } from './support/validators/model-validation.service'; -export abstract class AbstractCamelVisualEntity implements BaseVisualCamelEntity { - constructor(public route: RouteDefinition) {} +export abstract class AbstractCamelVisualEntity implements BaseVisualCamelEntity { + constructor(public route: T) {} abstract id: string; abstract type: EntityType; abstract setId(id: string): void; + abstract toJSON(): unknown; protected abstract getRootUri(): string | undefined; getId(): string { @@ -68,17 +69,11 @@ export abstract class AbstractCamelVisualEntity implements BaseVisualCamelEntity return SchemaService.OMIT_FORM_FIELDS; } - toJSON() { - return { route: this.route }; - } - updateModel(path: string | undefined, value: unknown): void { if (!path) return; const updatedValue = CamelComponentSchemaService.getUriSerializedDefinition(path, value); setValue(this.route, path, updatedValue); - - if (isDefined(this.route.id)) this.id = this.route.id; } /** @@ -106,12 +101,7 @@ export abstract class AbstractCamelVisualEntity implements BaseVisualCamelEntity (options.data as CamelRouteVisualEntityData).processorName as keyof ProcessorDefinition, ); - /** 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) { + if (options.mode === AddStepMode.InsertChildStep || options.mode === AddStepMode.InsertSpecialChildStep) { this.insertChildStep(options, stepsProperties, defaultValue); return; } @@ -144,15 +134,6 @@ export abstract class AbstractCamelVisualEntity implements BaseVisualCamelEntity removeStep(path?: string): void { if (!path) return; - /** - * If there's only one path segment, it means the target is the `from` property of the route - * therefore we replace it with an empty object - */ - if (path === 'from') { - setValue(this.route, 'from.uri', ''); - return; - } - const pathArray = path.split('.'); const last = pathArray[pathArray.length - 1]; const penultimate = pathArray[pathArray.length - 2]; @@ -207,6 +188,9 @@ export abstract class AbstractCamelVisualEntity implements BaseVisualCamelEntity const canHavePreviousStep = CamelComponentSchemaService.canHavePreviousStep( (data as CamelRouteVisualEntityData).processorName, ); + const canReplaceStep = CamelComponentSchemaService.canReplaceStep( + (data as CamelRouteVisualEntityData).processorName, + ); const canHaveChildren = stepsProperties.find((property) => property.type === 'branch') !== undefined; const canHaveSpecialChildren = Object.keys(stepsProperties).length > 1; @@ -215,7 +199,7 @@ export abstract class AbstractCamelVisualEntity implements BaseVisualCamelEntity canHaveNextStep: canHavePreviousStep, canHaveChildren, canHaveSpecialChildren, - canReplaceStep: true, + canReplaceStep, canRemoveStep: true, canRemoveFlow: data.path === ROOT_PATH, }; @@ -254,7 +238,7 @@ export abstract class AbstractCamelVisualEntity implements BaseVisualCamelEntity } private insertChildStep( - options: Parameters[0], + options: Parameters['addStep']>[0], stepsProperties: CamelProcessorStepsProperties[], defaultValue: ProcessorDefinition = {}, ) { diff --git a/packages/ui/src/models/visualization/flows/camel-error-handler-visual-entity.test.ts b/packages/ui/src/models/visualization/flows/camel-error-handler-visual-entity.test.ts index 6331a03fe..535792b08 100644 --- a/packages/ui/src/models/visualization/flows/camel-error-handler-visual-entity.test.ts +++ b/packages/ui/src/models/visualization/flows/camel-error-handler-visual-entity.test.ts @@ -1,12 +1,12 @@ -import { ErrorHandler, NoErrorHandler } from '@kaoto-next/camel-catalog/types'; +import { ErrorHandlerBuilderDeserializer, NoErrorHandler } from '@kaoto-next/camel-catalog/types'; import { useSchemasStore } from '../../../store'; import { errorHandlerSchema } from '../../../stubs/error-handler'; -import { CamelErrorHandlerVisualEntity } from './camel-error-handler-visual-entity'; import { EntityType } from '../../camel/entities'; +import { CamelErrorHandlerVisualEntity } from './camel-error-handler-visual-entity'; describe('CamelErrorHandlerVisualEntity', () => { const ERROR_HANDLER_ID_REGEXP = /^errorHandler-[a-zA-Z0-9]{4}$/; - let errorHandlerDef: { errorHandler: ErrorHandler }; + let errorHandlerDef: { errorHandler: ErrorHandlerBuilderDeserializer }; beforeAll(() => { useSchemasStore.getState().setSchema('errorHandler', { diff --git a/packages/ui/src/models/visualization/flows/camel-intercept-from-visual-entity.test.ts b/packages/ui/src/models/visualization/flows/camel-intercept-from-visual-entity.test.ts new file mode 100644 index 000000000..3867f8d8b --- /dev/null +++ b/packages/ui/src/models/visualization/flows/camel-intercept-from-visual-entity.test.ts @@ -0,0 +1,110 @@ +import { IVisualizationNodeData } from '../base-visual-entity'; +import { CamelInterceptFromVisualEntity } from './camel-intercept-from-visual-entity'; +import { ModelValidationService } from './support/validators/model-validation.service'; + +describe('CamelInterceptFromVisualEntity', () => { + describe('constructor', () => { + it('should allow to create an instance out of the string definition', () => { + const interceptFromVisualEntity = new CamelInterceptFromVisualEntity({ interceptFrom: 'a-reference' }); + + expect(interceptFromVisualEntity.getId()).toBeDefined(); + }); + + it('should allow to create an instance out of the object definition', () => { + const interceptFromVisualEntity = new CamelInterceptFromVisualEntity({ + interceptFrom: { id: 'a-reference', uri: 'direct:a-reference' }, + }); + + expect(interceptFromVisualEntity.getId()).toEqual('a-reference'); + }); + + it('should allow to create an instance out of the object definition without id', () => { + const interceptFromRaw = { + interceptFrom: { id: undefined, uri: 'direct:a-reference' }, + }; + const interceptFromVisualEntity = new CamelInterceptFromVisualEntity(interceptFromRaw); + + expect(interceptFromVisualEntity.getId()).toBeDefined(); + expect(interceptFromRaw.interceptFrom.id).toEqual(interceptFromVisualEntity.getId()); + }); + }); + + describe('isApplicable', () => { + it.each([ + [{ from: { id: 'from-1234', steps: [] } }, false], + [{ onCompletion: { id: 'onCompletionId' } }, false], + [{ onException: { id: 'onExceptionId' } }, false], + [{ intercept: { id: 'interceptId' } }, false], + [{ interceptFrom: { id: 'interceptFromId' } }, true], + [{ interceptSendToEndpoint: { id: 'interceptSendToEndpointId' } }, false], + ])('should return %s for %s', (definition, result) => { + expect(CamelInterceptFromVisualEntity.isApplicable(definition)).toEqual(result); + }); + }); + + it('should return the id', () => { + const interceptFromVisualEntity = new CamelInterceptFromVisualEntity({ + interceptFrom: { id: 'id', uri: 'direct:a-reference' }, + }); + expect(interceptFromVisualEntity.getId()).toEqual('id'); + }); + + it('should set the id', () => { + const interceptFromVisualEntity = new CamelInterceptFromVisualEntity({ interceptFrom: 'a-reference' }); + interceptFromVisualEntity.setId('new-id'); + expect(interceptFromVisualEntity.getId()).toEqual('new-id'); + expect(interceptFromVisualEntity.interceptFromDef.interceptFrom.id).toEqual('new-id'); + }); + + describe('getNodeInteraction', () => { + it.each([ + { processorName: 'route', path: 'route' }, + { processorName: 'from', path: 'from' }, + { processorName: 'to', path: 'to' }, + { processorName: 'log', path: 'log' }, + { processorName: 'onException', path: 'onException' }, + { processorName: 'onCompletion', path: 'onCompletion' }, + { processorName: 'intercept', path: 'intercept' }, + { processorName: 'interceptFrom', path: 'interceptFrom' }, + { processorName: 'interceptSendToEndpoint', path: 'interceptSendToEndpoint' }, + ] as const)(`should return the correct interaction for the '%s' processor`, (data) => { + const interceptFromVisualEntity = new CamelInterceptFromVisualEntity({ + interceptFrom: { id: 'id', uri: 'direct:a-reference' }, + }); + + const result = interceptFromVisualEntity.getNodeInteraction(data as IVisualizationNodeData); + expect(result).toMatchSnapshot(); + }); + }); + + it('should delegate the validation text to the ModelValidationService', () => { + const validateNodeStatusSpy = jest.spyOn(ModelValidationService, 'validateNodeStatus'); + + const interceptFromVisualEntity = new CamelInterceptFromVisualEntity({ + interceptFrom: { id: 'id', uri: 'direct:a-reference' }, + }); + interceptFromVisualEntity.getNodeValidationText('a-path'); + + expect(validateNodeStatusSpy).toHaveBeenCalledWith(expect.anything()); + }); + + it('should return the vizualization node', () => { + const interceptFromVisualEntity = new CamelInterceptFromVisualEntity({ + interceptFrom: { id: 'id', uri: 'direct:a-reference' }, + }); + const vizNode = interceptFromVisualEntity.toVizNode(); + + expect(vizNode.data.processorName).toBe(CamelInterceptFromVisualEntity.ROOT_PATH); + expect(vizNode.data.entity).toBe(interceptFromVisualEntity); + expect(vizNode.data.isGroup).toBeTruthy(); + }); + + it('should serialize the entity', () => { + const interceptFromVisualEntity = new CamelInterceptFromVisualEntity({ + interceptFrom: { id: undefined, uri: 'direct:a-reference' }, + }); + const result = interceptFromVisualEntity.toJSON(); + + expect(result).toMatchSnapshot(); + }); +}); 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 new file mode 100644 index 000000000..52176a558 --- /dev/null +++ b/packages/ui/src/models/visualization/flows/camel-intercept-from-visual-entity.ts @@ -0,0 +1,117 @@ +import { InterceptFrom, ProcessorDefinition } from '@kaoto-next/camel-catalog/types'; +import { getCamelRandomId } from '../../../camel-utils/camel-random-id'; +import { isDefined } from '../../../utils'; +import { EntityType } from '../../camel/entities/base-entity'; +import { + BaseVisualCamelEntity, + IVisualizationNode, + IVisualizationNodeData, + NodeInteraction, +} from '../base-visual-entity'; +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 { ModelValidationService } from './support/validators/model-validation.service'; + +export class CamelInterceptFromVisualEntity + extends AbstractCamelVisualEntity<{ interceptFrom: InterceptFrom }> + implements BaseVisualCamelEntity +{ + id: string; + interceptFromDef: { interceptFrom: Exclude }; + readonly type = EntityType.InterceptFrom; + static readonly ROOT_PATH = 'interceptFrom'; + + constructor(interceptFromRaw: { interceptFrom: InterceptFrom }) { + let interceptFromDef: { interceptFrom: Exclude }; + if (typeof interceptFromRaw.interceptFrom === 'string') { + interceptFromDef = { + interceptFrom: { + uri: interceptFromRaw.interceptFrom, + }, + }; + } else { + interceptFromDef = { interceptFrom: interceptFromRaw.interceptFrom }; + } + + super(interceptFromDef); + this.interceptFromDef = interceptFromDef; + const id = interceptFromDef.interceptFrom.id ?? getCamelRandomId(CamelInterceptFromVisualEntity.ROOT_PATH); + this.id = id; + this.interceptFromDef.interceptFrom.id = id; + } + + static isApplicable(interceptFromDef: unknown): interceptFromDef is { interceptFrom: InterceptFrom } { + if (!isDefined(interceptFromDef) || Array.isArray(interceptFromDef) || typeof interceptFromDef !== 'object') { + return false; + } + + const objectKeys = Object.keys(interceptFromDef!); + + return ( + objectKeys.length === 1 && + this.ROOT_PATH in interceptFromDef! && + (typeof interceptFromDef.interceptFrom === 'object' || typeof interceptFromDef.interceptFrom === 'string') + ); + } + + getId(): string { + return this.id; + } + + setId(id: string): void { + this.id = id; + this.interceptFromDef.interceptFrom.id = id; + } + + getNodeInteraction(data: IVisualizationNodeData): NodeInteraction { + const stepsProperties = CamelComponentSchemaService.getProcessorStepsProperties( + (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(stepsProperties).length > 1; + const canReplaceStep = data.path !== CamelInterceptFromVisualEntity.ROOT_PATH; + const canRemoveStep = data.path !== CamelInterceptFromVisualEntity.ROOT_PATH; + + return { + canHavePreviousStep, + canHaveNextStep: canHavePreviousStep, + canHaveChildren, + canHaveSpecialChildren, + canReplaceStep, + canRemoveStep, + canRemoveFlow: data.path === CamelInterceptFromVisualEntity.ROOT_PATH, + }; + } + + getNodeValidationText(path?: string | undefined): string | undefined { + const componentVisualSchema = this.getComponentSchema(path); + if (!componentVisualSchema) return undefined; + + return ModelValidationService.validateNodeStatus(componentVisualSchema); + } + + toVizNode(): IVisualizationNode { + const interceptFromGroupNode = CamelStepsService.getVizNodeFromProcessor( + CamelInterceptFromVisualEntity.ROOT_PATH, + { processorName: CamelInterceptFromVisualEntity.ROOT_PATH as keyof ProcessorDefinition }, + this.interceptFromDef, + ); + interceptFromGroupNode.data.entity = this; + interceptFromGroupNode.data.isGroup = true; + + return interceptFromGroupNode; + } + + toJSON(): { interceptFrom: InterceptFrom } { + return { interceptFrom: this.interceptFromDef.interceptFrom }; + } + + protected getRootUri(): string | undefined { + return undefined; + } +} diff --git a/packages/ui/src/models/visualization/flows/camel-intercept-send-to-endpoint-visual-entity.test.ts b/packages/ui/src/models/visualization/flows/camel-intercept-send-to-endpoint-visual-entity.test.ts new file mode 100644 index 000000000..e36589251 --- /dev/null +++ b/packages/ui/src/models/visualization/flows/camel-intercept-send-to-endpoint-visual-entity.test.ts @@ -0,0 +1,110 @@ +import { IVisualizationNodeData } from '../base-visual-entity'; +import { CamelInterceptSendToEndpointVisualEntity } from './camel-intercept-send-to-endpoint-visual-entity'; +import { ModelValidationService } from './support/validators/model-validation.service'; + +describe('CamelInterceptSendToEndpointVisualEntity', () => { + describe('constructor', () => { + it('should allow to create an instance out of the object definition', () => { + const interceptSendToEndpointVisualEntity = new CamelInterceptSendToEndpointVisualEntity({ + interceptSendToEndpoint: { id: 'a-reference', uri: 'direct:a-reference' }, + }); + + expect(interceptSendToEndpointVisualEntity.getId()).toEqual('a-reference'); + }); + + it('should allow to create an instance out of the object definition without id', () => { + const interceptSendToEndpointRaw = { + interceptSendToEndpoint: { id: undefined, uri: 'direct:a-reference' }, + }; + const interceptSendToEndpointVisualEntity = new CamelInterceptSendToEndpointVisualEntity( + interceptSendToEndpointRaw, + ); + + expect(interceptSendToEndpointVisualEntity.getId()).toBeDefined(); + expect(interceptSendToEndpointRaw.interceptSendToEndpoint.id).toEqual( + interceptSendToEndpointVisualEntity.getId(), + ); + }); + }); + + describe('isApplicable', () => { + it.each([ + [{ from: { id: 'from-1234', steps: [] } }, false], + [{ onCompletion: { id: 'onCompletionId' } }, false], + [{ onException: { id: 'onExceptionId' } }, false], + [{ intercept: { id: 'interceptId' } }, false], + [{ interceptFrom: { id: 'interceptFromId' } }, false], + [{ interceptSendToEndpoint: { id: 'interceptSendToEndpointId' } }, true], + ])('should return %s for %s', (definition, result) => { + expect(CamelInterceptSendToEndpointVisualEntity.isApplicable(definition)).toEqual(result); + }); + }); + + it('should return the id', () => { + const interceptSendToEndpointVisualEntity = new CamelInterceptSendToEndpointVisualEntity({ + interceptSendToEndpoint: { id: 'id', uri: 'direct:a-reference' }, + }); + expect(interceptSendToEndpointVisualEntity.getId()).toEqual('id'); + }); + + it('should set the id', () => { + const interceptSendToEndpointVisualEntity = new CamelInterceptSendToEndpointVisualEntity({ + interceptSendToEndpoint: 'a-reference', + }); + interceptSendToEndpointVisualEntity.setId('new-id'); + expect(interceptSendToEndpointVisualEntity.getId()).toEqual('new-id'); + expect(interceptSendToEndpointVisualEntity.interceptSendToEndpointDef.interceptSendToEndpoint.id).toEqual('new-id'); + }); + + describe('getNodeInteraction', () => { + it.each([ + { processorName: 'route', path: 'route' }, + { processorName: 'from', path: 'from' }, + { processorName: 'to', path: 'to' }, + { processorName: 'log', path: 'log' }, + { processorName: 'onException', path: 'onException' }, + { processorName: 'onCompletion', path: 'onCompletion' }, + { processorName: 'intercept', path: 'intercept' }, + { processorName: 'interceptFrom', path: 'interceptFrom' }, + { processorName: 'interceptSendToEndpoint', path: 'interceptSendToEndpoint' }, + ] as const)(`should return the correct interaction for the '%s' processor`, (data) => { + const interceptSendToEndpointVisualEntity = new CamelInterceptSendToEndpointVisualEntity({ + interceptSendToEndpoint: { id: 'id', uri: 'direct:a-reference' }, + }); + + const result = interceptSendToEndpointVisualEntity.getNodeInteraction(data as IVisualizationNodeData); + expect(result).toMatchSnapshot(); + }); + }); + + it('should delegate the validation text to the ModelValidationService', () => { + const validateNodeStatusSpy = jest.spyOn(ModelValidationService, 'validateNodeStatus'); + + const interceptSendToEndpointVisualEntity = new CamelInterceptSendToEndpointVisualEntity({ + interceptSendToEndpoint: { id: 'id', uri: 'direct:a-reference' }, + }); + interceptSendToEndpointVisualEntity.getNodeValidationText('a-path'); + + expect(validateNodeStatusSpy).toHaveBeenCalledWith(expect.anything()); + }); + + it('should return the vizualization node', () => { + const interceptSendToEndpointVisualEntity = new CamelInterceptSendToEndpointVisualEntity({ + interceptSendToEndpoint: { id: 'id', uri: 'direct:a-reference' }, + }); + const vizNode = interceptSendToEndpointVisualEntity.toVizNode(); + + expect(vizNode.data.processorName).toBe(CamelInterceptSendToEndpointVisualEntity.ROOT_PATH); + expect(vizNode.data.entity).toBe(interceptSendToEndpointVisualEntity); + expect(vizNode.data.isGroup).toBeTruthy(); + }); + + it('should serialize the entity', () => { + const interceptSendToEndpointVisualEntity = new CamelInterceptSendToEndpointVisualEntity({ + interceptSendToEndpoint: { id: undefined, uri: 'direct:a-reference' }, + }); + const result = interceptSendToEndpointVisualEntity.toJSON(); + + expect(result).toMatchSnapshot(); + }); +}); 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 new file mode 100644 index 000000000..064fb64f4 --- /dev/null +++ b/packages/ui/src/models/visualization/flows/camel-intercept-send-to-endpoint-visual-entity.ts @@ -0,0 +1,127 @@ +import { InterceptSendToEndpoint, ProcessorDefinition } from '@kaoto-next/camel-catalog/types'; +import { getCamelRandomId } from '../../../camel-utils/camel-random-id'; +import { isDefined } from '../../../utils'; +import { EntityType } from '../../camel/entities/base-entity'; +import { + BaseVisualCamelEntity, + IVisualizationNode, + IVisualizationNodeData, + NodeInteraction, +} from '../base-visual-entity'; +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 { ModelValidationService } from './support/validators/model-validation.service'; + +export class CamelInterceptSendToEndpointVisualEntity + extends AbstractCamelVisualEntity<{ interceptSendToEndpoint: InterceptSendToEndpoint }> + implements BaseVisualCamelEntity +{ + id: string; + interceptSendToEndpointDef: { interceptSendToEndpoint: Exclude }; + readonly type = EntityType.InterceptSendToEndpoint; + static readonly ROOT_PATH = 'interceptSendToEndpoint'; + + constructor(interceptSendToEndpointRaw: { interceptSendToEndpoint: InterceptSendToEndpoint }) { + let interceptSendToEndpointDef: { interceptSendToEndpoint: Exclude }; + if (typeof interceptSendToEndpointRaw.interceptSendToEndpoint === 'string') { + interceptSendToEndpointDef = { + interceptSendToEndpoint: { + id: getCamelRandomId(CamelInterceptSendToEndpointVisualEntity.ROOT_PATH), + uri: interceptSendToEndpointRaw.interceptSendToEndpoint, + }, + }; + } else { + interceptSendToEndpointDef = { interceptSendToEndpoint: interceptSendToEndpointRaw.interceptSendToEndpoint }; + } + + super(interceptSendToEndpointDef); + this.interceptSendToEndpointDef = interceptSendToEndpointDef; + const id = + interceptSendToEndpointDef.interceptSendToEndpoint.id ?? + getCamelRandomId(CamelInterceptSendToEndpointVisualEntity.ROOT_PATH); + this.id = id; + this.interceptSendToEndpointDef.interceptSendToEndpoint.id = id; + } + + static isApplicable( + interceptSendToEndpointDef: unknown, + ): interceptSendToEndpointDef is { interceptSendToEndpoint: InterceptSendToEndpoint } { + if ( + !isDefined(interceptSendToEndpointDef) || + Array.isArray(interceptSendToEndpointDef) || + typeof interceptSendToEndpointDef !== 'object' + ) { + return false; + } + + const objectKeys = Object.keys(interceptSendToEndpointDef!); + + return ( + objectKeys.length === 1 && + this.ROOT_PATH in interceptSendToEndpointDef! && + (typeof interceptSendToEndpointDef.interceptSendToEndpoint === 'object' || + typeof interceptSendToEndpointDef.interceptSendToEndpoint === 'string') + ); + } + + getId(): string { + return this.id; + } + + setId(id: string): void { + this.id = id; + this.interceptSendToEndpointDef.interceptSendToEndpoint.id = id; + } + + getNodeInteraction(data: IVisualizationNodeData): NodeInteraction { + const stepsProperties = CamelComponentSchemaService.getProcessorStepsProperties( + (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(stepsProperties).length > 1; + const canReplaceStep = data.path !== CamelInterceptSendToEndpointVisualEntity.ROOT_PATH; + const canRemoveStep = data.path !== CamelInterceptSendToEndpointVisualEntity.ROOT_PATH; + + return { + canHavePreviousStep, + canHaveNextStep: canHavePreviousStep, + canHaveChildren, + canHaveSpecialChildren, + canReplaceStep, + canRemoveStep, + canRemoveFlow: data.path === CamelInterceptSendToEndpointVisualEntity.ROOT_PATH, + }; + } + + getNodeValidationText(path?: string | undefined): string | undefined { + const componentVisualSchema = this.getComponentSchema(path); + if (!componentVisualSchema) return undefined; + + return ModelValidationService.validateNodeStatus(componentVisualSchema); + } + + toVizNode(): IVisualizationNode { + const interceptSendToEndpointGroupNode = CamelStepsService.getVizNodeFromProcessor( + CamelInterceptSendToEndpointVisualEntity.ROOT_PATH, + { processorName: CamelInterceptSendToEndpointVisualEntity.ROOT_PATH as keyof ProcessorDefinition }, + this.interceptSendToEndpointDef, + ); + interceptSendToEndpointGroupNode.data.entity = this; + interceptSendToEndpointGroupNode.data.isGroup = true; + + return interceptSendToEndpointGroupNode; + } + + toJSON(): { interceptSendToEndpoint: InterceptSendToEndpoint } { + return { interceptSendToEndpoint: this.interceptSendToEndpointDef.interceptSendToEndpoint }; + } + + protected getRootUri(): string | undefined { + return undefined; + } +} diff --git a/packages/ui/src/models/visualization/flows/camel-intercept-visual-entity.test.ts b/packages/ui/src/models/visualization/flows/camel-intercept-visual-entity.test.ts new file mode 100644 index 000000000..da02ddaec --- /dev/null +++ b/packages/ui/src/models/visualization/flows/camel-intercept-visual-entity.test.ts @@ -0,0 +1,104 @@ +import { IVisualizationNodeData } from '../base-visual-entity'; +import { CamelInterceptVisualEntity } from './camel-intercept-visual-entity'; +import { ModelValidationService } from './support/validators/model-validation.service'; + +describe('CamelInterceptVisualEntity', () => { + describe('constructor', () => { + it('should allow to create an instance out of the object definition', () => { + const interceptVisualEntity = new CamelInterceptVisualEntity({ + intercept: { id: 'a-reference', disabled: false }, + }); + + expect(interceptVisualEntity.getId()).toEqual('a-reference'); + }); + + it('should allow to create an instance out of the object definition without id', () => { + const interceptRaw = { + intercept: { id: undefined, uri: 'direct:a-reference' }, + }; + const interceptVisualEntity = new CamelInterceptVisualEntity(interceptRaw); + + expect(interceptVisualEntity.getId()).toBeDefined(); + expect(interceptRaw.intercept.id).toEqual(interceptVisualEntity.getId()); + }); + }); + + describe('isApplicable', () => { + it.each([ + [{ from: { id: 'from-1234', steps: [] } }, false], + [{ onCompletion: { id: 'onCompletionId' } }, false], + [{ onException: { id: 'onExceptionId' } }, false], + [{ intercept: { id: 'interceptId' } }, true], + [{ interceptFrom: { id: 'interceptFromId' } }, false], + [{ interceptSendToEndpoint: { id: 'interceptSendToEndpointId' } }, false], + ])('should return %s for %s', (definition, result) => { + expect(CamelInterceptVisualEntity.isApplicable(definition)).toEqual(result); + }); + }); + + it('should return the id', () => { + const interceptVisualEntity = new CamelInterceptVisualEntity({ + intercept: { id: 'id', disabled: false }, + }); + expect(interceptVisualEntity.getId()).toEqual('id'); + }); + + it('should set the id', () => { + const interceptVisualEntity = new CamelInterceptVisualEntity({ intercept: { id: 'a-reference', disabled: false } }); + interceptVisualEntity.setId('new-id'); + expect(interceptVisualEntity.getId()).toEqual('new-id'); + expect(interceptVisualEntity.interceptDef.intercept.id).toEqual('new-id'); + }); + + describe('getNodeInteraction', () => { + it.each([ + { processorName: 'route', path: 'route' }, + { processorName: 'from', path: 'from' }, + { processorName: 'to', path: 'to' }, + { processorName: 'log', path: 'log' }, + { processorName: 'onException', path: 'onException' }, + { processorName: 'onCompletion', path: 'onCompletion' }, + { processorName: 'intercept', path: 'intercept' }, + { processorName: 'interceptFrom', path: 'interceptFrom' }, + { processorName: 'interceptSendToEndpoint', path: 'interceptSendToEndpoint' }, + ] as const)(`should return the correct interaction for the '%s' processor`, (data) => { + const interceptVisualEntity = new CamelInterceptVisualEntity({ + intercept: { id: 'id', disabled: false }, + }); + + const result = interceptVisualEntity.getNodeInteraction(data as IVisualizationNodeData); + expect(result).toMatchSnapshot(); + }); + }); + + it('should delegate the validation text to the ModelValidationService', () => { + const validateNodeStatusSpy = jest.spyOn(ModelValidationService, 'validateNodeStatus'); + + const interceptVisualEntity = new CamelInterceptVisualEntity({ + intercept: { id: 'id', disabled: false }, + }); + interceptVisualEntity.getNodeValidationText('a-path'); + + expect(validateNodeStatusSpy).toHaveBeenCalledWith(expect.anything()); + }); + + it('should return the vizualization node', () => { + const interceptVisualEntity = new CamelInterceptVisualEntity({ + intercept: { id: 'id', disabled: false }, + }); + const vizNode = interceptVisualEntity.toVizNode(); + + expect(vizNode.data.processorName).toBe(CamelInterceptVisualEntity.ROOT_PATH); + expect(vizNode.data.entity).toBe(interceptVisualEntity); + expect(vizNode.data.isGroup).toBeTruthy(); + }); + + it('should serialize the entity', () => { + const interceptVisualEntity = new CamelInterceptVisualEntity({ + intercept: { id: undefined, disabled: false }, + }); + const result = interceptVisualEntity.toJSON(); + + expect(result).toMatchSnapshot(); + }); +}); 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 new file mode 100644 index 000000000..b27f63ef7 --- /dev/null +++ b/packages/ui/src/models/visualization/flows/camel-intercept-visual-entity.ts @@ -0,0 +1,100 @@ +import { Intercept, ProcessorDefinition } from '@kaoto-next/camel-catalog/types'; +import { getCamelRandomId } from '../../../camel-utils/camel-random-id'; +import { isDefined } from '../../../utils'; +import { EntityType } from '../../camel/entities/base-entity'; +import { + BaseVisualCamelEntity, + IVisualizationNode, + IVisualizationNodeData, + NodeInteraction, +} from '../base-visual-entity'; +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 { ModelValidationService } from './support/validators/model-validation.service'; + +export class CamelInterceptVisualEntity + extends AbstractCamelVisualEntity<{ intercept: Intercept }> + implements BaseVisualCamelEntity +{ + id: string; + readonly type = EntityType.Intercept; + static readonly ROOT_PATH = 'intercept'; + + constructor(public interceptDef: { intercept: Intercept }) { + super(interceptDef); + const id = interceptDef.intercept.id ?? getCamelRandomId(CamelInterceptVisualEntity.ROOT_PATH); + this.id = id; + this.interceptDef.intercept.id = id; + } + + static isApplicable(interceptDef: unknown): interceptDef is { intercept: Intercept } { + if (!isDefined(interceptDef) || Array.isArray(interceptDef) || typeof interceptDef !== 'object') { + return false; + } + + const objectKeys = Object.keys(interceptDef!); + + return objectKeys.length === 1 && this.ROOT_PATH in interceptDef! && typeof interceptDef.intercept === 'object'; + } + + getId(): string { + return this.id; + } + + setId(id: string): void { + this.id = id; + this.interceptDef.intercept.id = id; + } + + getNodeInteraction(data: IVisualizationNodeData): NodeInteraction { + const stepsProperties = CamelComponentSchemaService.getProcessorStepsProperties( + (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(stepsProperties).length > 1; + const canReplaceStep = data.path !== CamelInterceptVisualEntity.ROOT_PATH; + const canRemoveStep = data.path !== CamelInterceptVisualEntity.ROOT_PATH; + + return { + canHavePreviousStep, + canHaveNextStep: canHavePreviousStep, + canHaveChildren, + canHaveSpecialChildren, + canReplaceStep, + canRemoveStep, + canRemoveFlow: data.path === CamelInterceptVisualEntity.ROOT_PATH, + }; + } + + getNodeValidationText(path?: string | undefined): string | undefined { + const componentVisualSchema = this.getComponentSchema(path); + if (!componentVisualSchema) return undefined; + + return ModelValidationService.validateNodeStatus(componentVisualSchema); + } + + toVizNode(): IVisualizationNode { + const interceptGroupNode = CamelStepsService.getVizNodeFromProcessor( + CamelInterceptVisualEntity.ROOT_PATH, + { processorName: CamelInterceptVisualEntity.ROOT_PATH as keyof ProcessorDefinition }, + this.interceptDef, + ); + interceptGroupNode.data.entity = this; + interceptGroupNode.data.isGroup = true; + + return interceptGroupNode; + } + + toJSON(): { intercept: Intercept } { + return { intercept: this.interceptDef.intercept }; + } + + protected getRootUri(): string | undefined { + return undefined; + } +} diff --git a/packages/ui/src/models/visualization/flows/camel-on-completion-visual-entity.test.ts b/packages/ui/src/models/visualization/flows/camel-on-completion-visual-entity.test.ts new file mode 100644 index 000000000..c32a60cda --- /dev/null +++ b/packages/ui/src/models/visualization/flows/camel-on-completion-visual-entity.test.ts @@ -0,0 +1,107 @@ +import { OnCompletion } from '@kaoto-next/camel-catalog/types'; +import { IVisualizationNodeData } from '../base-visual-entity'; +import { CamelOnCompletionVisualEntity } from './camel-on-completion-visual-entity'; +import { ModelValidationService } from './support/validators/model-validation.service'; + +describe('CamelOnCompletionVisualEntity', () => { + describe('constructor', () => { + it('should allow to create an instance out of the object definition', () => { + const onCompletionVisualEntity = new CamelOnCompletionVisualEntity({ + onCompletion: { id: 'a-reference', mode: 'AfterConsumer' }, + }); + + expect(onCompletionVisualEntity.getId()).toEqual('a-reference'); + }); + + it('should allow to create an instance out of the object definition without id', () => { + const onCompletionRaw: { onCompletion: OnCompletion } = { + onCompletion: { id: undefined, mode: 'AfterConsumer' }, + }; + const onCompletionVisualEntity = new CamelOnCompletionVisualEntity(onCompletionRaw); + + expect(onCompletionVisualEntity.getId()).toBeDefined(); + expect(onCompletionRaw.onCompletion.id).toEqual(onCompletionVisualEntity.getId()); + }); + }); + + describe('isApplicable', () => { + it.each([ + [{ from: { id: 'from-1234', steps: [] } }, false], + [{ onCompletion: { id: 'onCompletionId' } }, true], + [{ onException: { id: 'onExceptionId' } }, false], + [{ intercept: { id: 'interceptId' } }, false], + [{ interceptFrom: { id: 'interceptFromId' } }, false], + [{ interceptSendToEndpoint: { id: 'interceptSendToEndpointId' } }, false], + ])('should return %s for %s', (definition, result) => { + expect(CamelOnCompletionVisualEntity.isApplicable(definition)).toEqual(result); + }); + }); + + it('should return the id', () => { + const onCompletionVisualEntity = new CamelOnCompletionVisualEntity({ + onCompletion: { id: 'id', mode: 'AfterConsumer' }, + }); + expect(onCompletionVisualEntity.getId()).toEqual('id'); + }); + + it('should set the id', () => { + const onCompletionVisualEntity = new CamelOnCompletionVisualEntity({ + onCompletion: { id: 'id', mode: 'AfterConsumer' }, + }); + onCompletionVisualEntity.setId('new-id'); + expect(onCompletionVisualEntity.getId()).toEqual('new-id'); + expect(onCompletionVisualEntity.onCompletionDef.onCompletion.id).toEqual('new-id'); + }); + + describe('getNodeInteraction', () => { + it.each([ + { processorName: 'route', path: 'route' }, + { processorName: 'from', path: 'from' }, + { processorName: 'to', path: 'to' }, + { processorName: 'log', path: 'log' }, + { processorName: 'onException', path: 'onException' }, + { processorName: 'onCompletion', path: 'onCompletion' }, + { processorName: 'intercept', path: 'intercept' }, + { processorName: 'onCompletion', path: 'onCompletion' }, + { processorName: 'interceptSendToEndpoint', path: 'interceptSendToEndpoint' }, + ] as const)(`should return the correct interaction for the '%s' processor`, (data) => { + const onCompletionVisualEntity = new CamelOnCompletionVisualEntity({ + onCompletion: { id: 'id', mode: 'AfterConsumer' }, + }); + + const result = onCompletionVisualEntity.getNodeInteraction(data as IVisualizationNodeData); + expect(result).toMatchSnapshot(); + }); + }); + + it('should delegate the validation text to the ModelValidationService', () => { + const validateNodeStatusSpy = jest.spyOn(ModelValidationService, 'validateNodeStatus'); + + const onCompletionVisualEntity = new CamelOnCompletionVisualEntity({ + onCompletion: { id: 'id', mode: 'AfterConsumer' }, + }); + onCompletionVisualEntity.getNodeValidationText('a-path'); + + expect(validateNodeStatusSpy).toHaveBeenCalledWith(expect.anything()); + }); + + it('should return the vizualization node', () => { + const onCompletionVisualEntity = new CamelOnCompletionVisualEntity({ + onCompletion: { id: 'id', mode: 'AfterConsumer' }, + }); + const vizNode = onCompletionVisualEntity.toVizNode(); + + expect(vizNode.data.processorName).toBe(CamelOnCompletionVisualEntity.ROOT_PATH); + expect(vizNode.data.entity).toBe(onCompletionVisualEntity); + expect(vizNode.data.isGroup).toBeTruthy(); + }); + + it('should serialize the entity', () => { + const onCompletionVisualEntity = new CamelOnCompletionVisualEntity({ + onCompletion: { id: undefined, mode: 'AfterConsumer' }, + }); + const result = onCompletionVisualEntity.toJSON(); + + expect(result).toMatchSnapshot(); + }); +}); 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 new file mode 100644 index 000000000..bc52aa6b2 --- /dev/null +++ b/packages/ui/src/models/visualization/flows/camel-on-completion-visual-entity.ts @@ -0,0 +1,102 @@ +import { OnCompletion, ProcessorDefinition } from '@kaoto-next/camel-catalog/types'; +import { getCamelRandomId } from '../../../camel-utils/camel-random-id'; +import { isDefined } from '../../../utils'; +import { EntityType } from '../../camel/entities/base-entity'; +import { + BaseVisualCamelEntity, + IVisualizationNode, + IVisualizationNodeData, + NodeInteraction, +} from '../base-visual-entity'; +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 { ModelValidationService } from './support/validators/model-validation.service'; + +export class CamelOnCompletionVisualEntity + extends AbstractCamelVisualEntity<{ onCompletion: OnCompletion }> + implements BaseVisualCamelEntity +{ + id: string; + readonly type = EntityType.OnCompletion; + static readonly ROOT_PATH = 'onCompletion'; + + constructor(public onCompletionDef: { onCompletion: OnCompletion }) { + super(onCompletionDef); + const id = onCompletionDef.onCompletion.id ?? getCamelRandomId(CamelOnCompletionVisualEntity.ROOT_PATH); + this.id = id; + this.onCompletionDef.onCompletion.id = id; + } + + static isApplicable(onCompletionDef: unknown): onCompletionDef is { onCompletion: OnCompletion } { + if (!isDefined(onCompletionDef) || Array.isArray(onCompletionDef) || typeof onCompletionDef !== 'object') { + return false; + } + + const objectKeys = Object.keys(onCompletionDef!); + + return ( + objectKeys.length === 1 && this.ROOT_PATH in onCompletionDef! && typeof onCompletionDef.onCompletion === 'object' + ); + } + + getId(): string { + return this.id; + } + + setId(id: string): void { + this.id = id; + this.onCompletionDef.onCompletion.id = id; + } + + getNodeInteraction(data: IVisualizationNodeData): NodeInteraction { + const stepsProperties = CamelComponentSchemaService.getProcessorStepsProperties( + (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(stepsProperties).length > 1; + const canReplaceStep = data.path !== CamelOnCompletionVisualEntity.ROOT_PATH; + const canRemoveStep = data.path !== CamelOnCompletionVisualEntity.ROOT_PATH; + + return { + canHavePreviousStep, + canHaveNextStep: canHavePreviousStep, + canHaveChildren, + canHaveSpecialChildren, + canReplaceStep, + canRemoveStep, + canRemoveFlow: data.path === CamelOnCompletionVisualEntity.ROOT_PATH, + }; + } + + getNodeValidationText(path?: string | undefined): string | undefined { + const componentVisualSchema = this.getComponentSchema(path); + if (!componentVisualSchema) return undefined; + + return ModelValidationService.validateNodeStatus(componentVisualSchema); + } + + toVizNode(): IVisualizationNode { + const onCompletionGroupNode = CamelStepsService.getVizNodeFromProcessor( + CamelOnCompletionVisualEntity.ROOT_PATH, + { processorName: CamelOnCompletionVisualEntity.ROOT_PATH as keyof ProcessorDefinition }, + this.onCompletionDef, + ); + onCompletionGroupNode.data.entity = this; + onCompletionGroupNode.data.isGroup = true; + + return onCompletionGroupNode; + } + + toJSON(): { onCompletion: OnCompletion } { + return { onCompletion: this.onCompletionDef.onCompletion }; + } + + protected getRootUri(): string | undefined { + return undefined; + } +} 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 eb344ffb4..f8619bc8e 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 @@ -1,33 +1,32 @@ import { OnException, ProcessorDefinition } from '@kaoto-next/camel-catalog/types'; import { getCamelRandomId } from '../../../camel-utils/camel-random-id'; -import { SchemaService } from '../../../components/Form/schema.service'; -import { getArrayProperty, getValue, isDefined, setValue } from '../../../utils'; -import { DefinedComponent } from '../../camel-catalog-index'; +import { isDefined } from '../../../utils'; import { EntityType } from '../../camel/entities/base-entity'; import { - AddStepMode, BaseVisualCamelEntity, IVisualizationNode, IVisualizationNodeData, NodeInteraction, - VisualComponentSchema, } from '../base-visual-entity'; import { AbstractCamelVisualEntity } from './abstract-camel-visual-entity'; -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 { CamelRouteVisualEntityData } from './support/camel-component-types'; import { CamelStepsService } from './support/camel-steps.service'; import { ModelValidationService } from './support/validators/model-validation.service'; -export class CamelOnExceptionVisualEntity implements BaseVisualCamelEntity { +export class CamelOnExceptionVisualEntity + extends AbstractCamelVisualEntity<{ onException: OnException }> + implements BaseVisualCamelEntity +{ id: string; readonly type = EntityType.OnException; private static readonly ROOT_PATH = 'onException'; constructor(public onExceptionDef: { onException: OnException }) { + super(onExceptionDef); const id = onExceptionDef.onException.id ?? getCamelRandomId(CamelOnExceptionVisualEntity.ROOT_PATH); this.id = id; - onExceptionDef.onException.id = id; + this.onExceptionDef.onException.id = id; } static isApplicable(onExceptionDef: unknown): onExceptionDef is { onException: OnException } { @@ -48,162 +47,7 @@ export class CamelOnExceptionVisualEntity implements BaseVisualCamelEntity { setId(id: string): void { this.id = id; - } - - getNodeLabel(path?: string): string { - if (!path) return ''; - - const componentModel = getValue(this.onExceptionDef, path); - const label = CamelComponentSchemaService.getNodeLabel( - CamelComponentSchemaService.getCamelComponentLookup(path, componentModel), - componentModel, - ); - - return label; - } - - getTooltipContent(path?: string): string { - if (!path) return ''; - const componentModel = getValue(this.onExceptionDef, path); - - const content = CamelComponentSchemaService.getTooltipContent( - CamelComponentSchemaService.getCamelComponentLookup(path, componentModel), - ); - - return content; - } - - getComponentSchema(path?: string | undefined): VisualComponentSchema | undefined { - if (!path) return undefined; - - const componentModel = getValue(this.onExceptionDef, path); - const visualComponentSchema = CamelComponentSchemaService.getVisualComponentSchema(path, componentModel); - - return visualComponentSchema; - } - - getOmitFormFields(): string[] { - return SchemaService.OMIT_FORM_FIELDS; - } - - updateModel(path: string | undefined, value: unknown): void { - if (!path) return; - - setValue(this.onExceptionDef, path, value); - } - - /** - * Add a step to the route - * - * path examples: - * from - * from.steps.0.setHeader - * from.steps.1.choice.when.0 - * from.steps.1.choice.when.0.steps.0.setHeader - * from.steps.1.choice.otherwise - * from.steps.1.choice.otherwise.steps.0.setHeader - * from.steps.2.doTry.doCatch.0 - * from.steps.2.doTry.doCatch.0.steps.0.setHeader - */ - addStep(options: { - definedComponent: DefinedComponent; - mode: AddStepMode; - data: IVisualizationNodeData; - targetProperty?: string; - }) { - if (options.data.path === undefined) return; - 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) { - this.insertChildStep(options, stepsProperties, defaultValue); - return; - } - - const pathArray = options.data.path.split('.'); - const last = pathArray[pathArray.length - 1]; - const penultimate = pathArray[pathArray.length - 2]; - - /** - * If the last segment is a string and the penultimate is a number, it means the target is member of an array - * therefore we need to look for the array and insert the element at the given index + 1 - * - * f.i. from.steps.0.setHeader - * penultimate: 0 - * last: setHeader - */ - if (!Number.isInteger(Number(last)) && Number.isInteger(Number(penultimate))) { - /** 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[] = getValue(this.onExceptionDef, pathArray.slice(0, -2), []); - stepsArray.splice(desiredStartIndex, deleteCount, defaultValue); - - return; - } - } - - removeStep(path?: string): void { - if (!path) return; - /** - * If there's only one path segment, it means the target is the `from` property of the route - * therefore we replace it with an empty object - */ - if (path === 'from') { - setValue(this.onExceptionDef, 'from.uri', ''); - return; - } - - const pathArray = path.split('.'); - const last = pathArray[pathArray.length - 1]; - const penultimate = pathArray[pathArray.length - 2]; - - /** - * If the last segment is a number, it means the target object is a member of an array - * therefore we need to look for the array and remove the element at the given index - * - * f.i. from.steps.1.choice.when.0 - * last: 0 - */ - let array = getValue(this.onExceptionDef, pathArray.slice(0, -1), []); - if (Number.isInteger(Number(last)) && Array.isArray(array)) { - array.splice(Number(last), 1); - - return; - } - - /** - * If the last segment is a word and the penultimate is a number, it means the target is an object - * potentially a Processor, that belongs to an array, therefore we remove it entirely - * - * f.i. from.steps.1.choice - * last: choice - * penultimate: 1 - */ - array = getValue(this.onExceptionDef, pathArray.slice(0, -2), []); - if (!Number.isInteger(Number(last)) && Number.isInteger(Number(penultimate)) && Array.isArray(array)) { - array.splice(Number(penultimate), 1); - - return; - } - - /** - * If both the last and penultimate segment are words, it means the target is a property of an object - * therefore we delete it - * - * f.i. from.steps.1.choice.otherwise - * last: otherwise - * penultimate: choice - */ - const object = getValue(this.onExceptionDef, pathArray.slice(0, -1), {}); - if (!Number.isInteger(Number(last)) && !Number.isInteger(Number(penultimate)) && typeof object === 'object') { - delete object[last]; - } + this.onExceptionDef.onException.id = id; } getNodeInteraction(data: IVisualizationNodeData): NodeInteraction { @@ -248,25 +92,11 @@ export class CamelOnExceptionVisualEntity implements BaseVisualCamelEntity { return onExceptionGroupNode; } - toJSON(): unknown { - return this.onExceptionDef; + toJSON(): { onException: OnException } { + return { onException: this.onExceptionDef.onException }; } - private insertChildStep( - options: Parameters[0], - stepsProperties: CamelProcessorStepsProperties[], - defaultValue: ProcessorDefinition = {}, - ) { - const property = stepsProperties.find((property) => - options.mode === AddStepMode.InsertChildStep ? 'steps' : options.definedComponent.name === property.name, - ); - if (property === undefined) return; - - if (property.type === 'single-clause') { - setValue(this.onExceptionDef, `${options.data.path}.${property.name}`, defaultValue); - } else { - const arrayPath = getArrayProperty(this.onExceptionDef, `${options.data.path}.${property.name}`); - arrayPath.unshift(defaultValue); - } + protected getRootUri(): string | undefined { + return undefined; } } 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 new file mode 100644 index 000000000..49140e912 --- /dev/null +++ b/packages/ui/src/models/visualization/flows/camel-route-configuration-visual-entity.ts @@ -0,0 +1,201 @@ +import { ProcessorDefinition, RouteConfigurationDefinition } from '@kaoto-next/camel-catalog/types'; +import Ajv, { ValidateFunction } from 'ajv-draft-04'; +import addFormats from 'ajv-formats'; +import { getCamelRandomId } from '../../../camel-utils/camel-random-id'; +import { SchemaService } from '../../../public-api'; +import { NodeIconResolver, getValue, isDefined, setValue } from '../../../utils'; +import { EntityType } from '../../camel/entities/base-entity'; +import { CatalogKind } from '../../catalog-kind'; +import { KaotoSchemaDefinition } from '../../kaoto-schema'; +import { + BaseVisualCamelEntity, + IVisualizationNode, + IVisualizationNodeData, + NodeInteraction, + VisualComponentSchema, +} from '../base-visual-entity'; +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'; + +export class CamelRouteConfigurationVisualEntity + extends AbstractCamelVisualEntity<{ routeConfiguration: RouteConfigurationDefinition }> + implements BaseVisualCamelEntity +{ + id: string; + readonly type = EntityType.RouteConfiguration; + private static readonly ROOT_PATH = 'routeConfiguration'; + private schemaValidator: ValidateFunction | undefined; + private readonly OMIT_FORM_FIELDS = [ + ...SchemaService.OMIT_FORM_FIELDS, + 'intercept', + 'interceptFrom', + 'interceptSendToEndpoint', + 'onException', + 'onCompletion', + ]; + + constructor(public routeConfigurationDef: { routeConfiguration: RouteConfigurationDefinition }) { + super(routeConfigurationDef); + const id = + routeConfigurationDef.routeConfiguration.id ?? getCamelRandomId(CamelRouteConfigurationVisualEntity.ROOT_PATH); + this.id = id; + this.routeConfigurationDef.routeConfiguration.id = id; + } + + static isApplicable( + routeConfigurationDef: unknown, + ): routeConfigurationDef is { routeConfiguration: RouteConfigurationDefinition } { + if ( + !isDefined(routeConfigurationDef) || + Array.isArray(routeConfigurationDef) || + typeof routeConfigurationDef !== 'object' + ) { + return false; + } + + const objectKeys = Object.keys(routeConfigurationDef!); + + return ( + objectKeys.length === 1 && + this.ROOT_PATH in routeConfigurationDef! && + typeof routeConfigurationDef.routeConfiguration === 'object' + ); + } + + getId(): string { + return this.id; + } + + setId(id: string): void { + this.id = id; + } + + getTooltipContent(path?: string): string { + if (path === CamelRouteConfigurationVisualEntity.ROOT_PATH) { + return 'routeConfiguration'; + } + + return super.getTooltipContent(path); + } + + getComponentSchema(path?: string | undefined): VisualComponentSchema | undefined { + if (path === CamelRouteConfigurationVisualEntity.ROOT_PATH) { + const schema = CamelCatalogService.getComponent(CatalogKind.Entity, 'routeConfiguration'); + return { + title: 'Route Configuration', + schema: schema?.propertiesSchema || {}, + definition: Object.assign({}, this.routeConfigurationDef.routeConfiguration), + }; + } + + return super.getComponentSchema(path); + } + + getOmitFormFields(): string[] { + return this.OMIT_FORM_FIELDS; + } + + updateModel(path: string | undefined, value: unknown): void { + if (!path) return; + + setValue(this.routeConfigurationDef, path, value); + + if (!isDefined(this.routeConfigurationDef.routeConfiguration)) { + this.routeConfigurationDef.routeConfiguration = {}; + } + } + + getNodeInteraction(data: IVisualizationNodeData): NodeInteraction { + if (data.path === CamelRouteConfigurationVisualEntity.ROOT_PATH) { + return { + canHavePreviousStep: false, + canHaveNextStep: false, + canHaveChildren: false, + canHaveSpecialChildren: true, + canRemoveStep: false, + canReplaceStep: false, + canRemoveFlow: true, + }; + } + + return super.getNodeInteraction(data); + } + + getNodeValidationText(): string | undefined { + const componentVisualSchema = this.getComponentSchema(); + if (!componentVisualSchema) return undefined; + + if (!this.schemaValidator) { + this.schemaValidator = this.getValidatorFunction(componentVisualSchema.schema); + } + + this.schemaValidator?.({ ...this.routeConfigurationDef.routeConfiguration }); + + return this.schemaValidator?.errors?.map((error) => `'${error.instancePath}' ${error.message}`).join(',\n'); + } + + toVizNode(): IVisualizationNode { + const routeConfigurationGroupNode = createVisualizationNode(this.id, { + path: CamelRouteConfigurationVisualEntity.ROOT_PATH, + entity: this, + isGroup: true, + icon: NodeIconResolver.getIcon(this.type), + processorName: CamelRouteConfigurationVisualEntity.ROOT_PATH, + }); + + CamelComponentSchemaService.getProcessorStepsProperties( + CamelRouteConfigurationVisualEntity.ROOT_PATH as keyof ProcessorDefinition, + ).forEach((stepsProperty) => { + const childEntities = getValue(this.routeConfigurationDef.routeConfiguration, stepsProperty.name, []); + if (!Array.isArray(childEntities)) return; + + childEntities.forEach((childEntity, index) => { + const childNode = CamelStepsService.getVizNodeFromProcessor( + `${CamelRouteConfigurationVisualEntity.ROOT_PATH}.${stepsProperty.name}.${index}.${ + Object.keys(childEntity)[0] + }`, + { + processorName: stepsProperty.name as keyof ProcessorDefinition, + }, + this.routeConfigurationDef, + ); + + routeConfigurationGroupNode.addChild(childNode); + }); + }); + + return routeConfigurationGroupNode; + } + + toJSON(): { routeConfiguration: RouteConfigurationDefinition } { + return { routeConfiguration: this.routeConfigurationDef.routeConfiguration }; + } + + protected getRootUri(): string | undefined { + return undefined; + } + + private getValidatorFunction( + schema: KaotoSchemaDefinition['schema'], + ): ValidateFunction | undefined { + return undefined; + const ajv = new Ajv({ + strict: false, + allErrors: true, + useDefaults: 'empty', + }); + addFormats(ajv); + + let schemaValidator: ValidateFunction | undefined; + try { + schemaValidator = ajv.compile(schema); + } catch (error) { + console.error('Could not compile schema', error); + } + + return schemaValidator; + } +} 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 7c777e3d0..2fdb39436 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 @@ -1,8 +1,11 @@ import { FromDefinition, RouteDefinition } from '@kaoto-next/camel-catalog/types'; import { getCamelRandomId } from '../../../camel-utils/camel-random-id'; -import { isDefined } from '../../../utils'; +import { isDefined, setValue } from '../../../utils'; +import { DefinedComponent } from '../../camel-catalog-index'; import { EntityType } from '../../camel/entities'; +import { AddStepMode, IVisualizationNodeData } from '../base-visual-entity'; import { AbstractCamelVisualEntity } from './abstract-camel-visual-entity'; +import { CamelComponentDefaultService } from './support/camel-component-default.service'; /** Very basic check to determine whether this object is a Camel Route */ export const isCamelRoute = (rawEntity: unknown): rawEntity is { route: RouteDefinition } => { @@ -33,7 +36,7 @@ export const isCamelFrom = (rawEntity: unknown): rawEntity is { from: FromDefini return isFromHolder && isValidUriField; }; -export class CamelRouteVisualEntity extends AbstractCamelVisualEntity { +export class CamelRouteVisualEntity extends AbstractCamelVisualEntity { id: string; readonly type = EntityType.Route; @@ -49,6 +52,45 @@ export class CamelRouteVisualEntity extends AbstractCamelVisualEntity { this.route.id = this.id; } + toJSON(): { route: RouteDefinition } { + return { route: this.route }; + } + + addStep(options: { + definedComponent: DefinedComponent; + mode: AddStepMode; + data: IVisualizationNodeData; + targetProperty?: string | undefined; + }): void { + /** 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; + } + + super.addStep(options); + } + + removeStep(path?: string): void { + if (!path) return; + /** + * If there's only one path segment, it means the target is the `from` property of the route + * therefore we replace it with an empty object + */ + if (path === 'from') { + setValue(this.route, 'from.uri', ''); + return; + } + + super.removeStep(path); + } + + updateModel(path: string | undefined, value: unknown): void { + super.updateModel(path, value); + if (isDefined(this.route.id)) this.id = this.route.id; + } + protected getRootUri(): string | undefined { return this.route.from?.uri; } diff --git a/packages/ui/src/models/visualization/flows/kamelet-visual-entity.ts b/packages/ui/src/models/visualization/flows/kamelet-visual-entity.ts index 77b18008f..f0ca4e184 100644 --- a/packages/ui/src/models/visualization/flows/kamelet-visual-entity.ts +++ b/packages/ui/src/models/visualization/flows/kamelet-visual-entity.ts @@ -1,14 +1,23 @@ +import { RouteDefinition } from '@kaoto-next/camel-catalog/types'; import { getCamelRandomId } from '../../../camel-utils/camel-random-id'; -import { ROOT_PATH, getCustomSchemaFromKamelet, updateKameletFromCustomSchema } from '../../../utils'; +import { + ROOT_PATH, + getCustomSchemaFromKamelet, + isDefined, + setValue, + updateKameletFromCustomSchema, +} from '../../../utils'; +import { DefinedComponent } from '../../camel-catalog-index'; import { EntityType } from '../../camel/entities'; import { CatalogKind } from '../../catalog-kind'; import { IKameletDefinition } from '../../kamelets-catalog'; import { KaotoSchemaDefinition } from '../../kaoto-schema'; -import { VisualComponentSchema } from '../base-visual-entity'; +import { AddStepMode, IVisualizationNodeData, VisualComponentSchema } from '../base-visual-entity'; import { AbstractCamelVisualEntity } from './abstract-camel-visual-entity'; import { CamelCatalogService } from './camel-catalog.service'; +import { CamelComponentDefaultService } from './support/camel-component-default.service'; -export class KameletVisualEntity extends AbstractCamelVisualEntity { +export class KameletVisualEntity extends AbstractCamelVisualEntity { id: string; readonly type = EntityType.Kamelet; @@ -29,6 +38,10 @@ export class KameletVisualEntity extends AbstractCamelVisualEntity { return this.kamelet.metadata.name; } + toJSON(): { route: RouteDefinition } { + return { route: this.route }; + } + getComponentSchema(path?: string | undefined): VisualComponentSchema | undefined { if (path === ROOT_PATH) { return { @@ -50,6 +63,41 @@ export class KameletVisualEntity extends AbstractCamelVisualEntity { } super.updateModel(path, value); + if (isDefined(this.route.id)) this.id = this.route.id; + } + + addStep(options: { + definedComponent: DefinedComponent; + mode: AddStepMode; + data: IVisualizationNodeData; + targetProperty?: string | undefined; + }): void { + /** 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; + } + + super.addStep(options); + } + + removeStep(path?: string): void { + if (!path) return; + /** + * If there's only one path segment, it means the target is the `from` property of the route + * therefore we replace it with an empty object + */ + if (path === 'from') { + setValue(this.route, 'from.uri', ''); + return; + } + + super.removeStep(path); + } + + protected getRootUri(): string | undefined { + return this.kamelet.spec.template.from?.uri; } private getRootKameletSchema(): KaotoSchemaDefinition['schema'] { @@ -64,8 +112,4 @@ export class KameletVisualEntity extends AbstractCamelVisualEntity { return schema; } - - protected getRootUri(): string | undefined { - return this.kamelet.spec.template.from?.uri; - } }