You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ma...@apache.org on 2023/10/26 00:05:06 UTC

[camel-karavan] 02/07: Kamelet Source in Sink Kamelet for #315

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

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

commit 2d8216b482315e970141e1ff6398b4f310c55178
Author: Marat Gubaidullin <ma...@talismancloud.io>
AuthorDate: Mon Oct 23 17:58:18 2023 -0400

    Kamelet Source in Sink Kamelet for #315
---
 karavan-core/src/core/model/KameletModels.ts       |   2 +-
 .../example/aws-cloudwatch-sink.kamelet.yaml       | 185 +++++++++++++++++++++
 karavan-designer/src/App.tsx                       |   3 +-
 .../src/designer/route/DslConnections.tsx          |   1 +
 karavan-designer/src/designer/route/DslElement.tsx |   4 +-
 .../src/designer/route/RouteDesigner.tsx           |  53 +++---
 .../src/designer/route/useRouteDesignerHook.tsx    |  31 +++-
 7 files changed, 254 insertions(+), 25 deletions(-)

diff --git a/karavan-core/src/core/model/KameletModels.ts b/karavan-core/src/core/model/KameletModels.ts
index e5c9855e..342d8a35 100644
--- a/karavan-core/src/core/model/KameletModels.ts
+++ b/karavan-core/src/core/model/KameletModels.ts
@@ -49,7 +49,7 @@ export class KameletSpec {
 }
 
 export class Labels {
-    'camel.apache.org/kamelet.type': string | any = '';
+    'camel.apache.org/kamelet.type': "sink" | "source" | "action" = 'source';
 
     public constructor(init?: Partial<Labels>) {
         Object.assign(this, init);
diff --git a/karavan-designer/public/example/aws-cloudwatch-sink.kamelet.yaml b/karavan-designer/public/example/aws-cloudwatch-sink.kamelet.yaml
new file mode 100644
index 00000000..d22ca1fc
--- /dev/null
+++ b/karavan-designer/public/example/aws-cloudwatch-sink.kamelet.yaml
@@ -0,0 +1,185 @@
+# ---------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ---------------------------------------------------------------------------
+
+apiVersion: camel.apache.org/v1
+kind: Kamelet
+metadata:
+  name: aws-cloudwatch-sink
+  annotations:
+    camel.apache.org/kamelet.support.level: "Stable"
+    camel.apache.org/catalog.version: "4.0.1"
+    camel.apache.org/kamelet.icon: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjIwNyIgaGVpZ2h0PSIyNTAwIiB2aWV3Qm94PSIwIDAgMjU2IDI5MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCI+PHBhdGggZD0iTTI1NiAxOTkuMzA1bC0xMjcuOTU3LTE4Ljc5N0wwIDE5OS4zMjlsMTI4LjAxIDQ3LjQzOUwyNTYgMTk5LjMwNSIgZmlsbD0iI0I3Q0E5RCIvPjxwYXRoIGQ9Ik0yNS42MjEgMTk3LjExM2wyMS42MyA2Ljc2MSAxLjk3MS0yLjIzOFY1MC4yODRsLTEuOTcxLTIuNTg1LTIxLjYzIDguMjc0djE0MS4xNCIgZmlsbD0iIzRCNjEyQyIvPjxwYXRoIG [...]
+    camel.apache.org/provider: "Apache Software Foundation"
+    camel.apache.org/kamelet.group: "AWS Cloudwatch"
+    camel.apache.org/kamelet.namespace: "AWS"
+  labels:
+    camel.apache.org/kamelet.type: "sink"
+spec:
+  definition:
+    title: "AWS CloudWatch Metrics Sink"
+    description: |-
+      Send data to Amazon CloudWatch metrics.
+
+      The basic authentication method for the AWS CloudWatch metrics service is to specify an access key and a secret key. These parameters are optional because the Kamelet provides a default credentials provider.
+      
+      If you use the default credentials provider, the CloudWatch client loads the credentials through this provider and doesn't use the basic authentication method.
+      
+      You can set the following properties in the header:
+
+      `metric-name` / `ce-metricname` for the metric name.
+      `metric-value` / `ce-metricvalue` for the metric value.
+      `metric-unit` / `ce-metricunit` for the metric unit.
+      `metric-timestamp` / `ce-metrictimestamp` for the metric timestamp.
+      `metric-dimension-name` / `ce-metricdimensionname` for the dimension name.
+      `metric-dimension-value` / `ce-metricdimensionvalue` for the dimension value.
+    required:
+      - cwNamespace
+      - region
+    type: object
+    properties:
+      cwNamespace:
+        title: Cloud Watch Namespace
+        description: The CloudWatch metric namespace.
+        type: string
+      accessKey:
+        title: Access Key
+        description: The access key obtained from AWS.
+        type: string
+        format: password
+        x-descriptors:
+          - urn:alm:descriptor:com.tectonic.ui:password
+          - urn:camel:group:credentials
+      secretKey:
+        title: Secret Key
+        description: The secret key obtained from AWS.
+        type: string
+        format: password
+        x-descriptors:
+          - urn:alm:descriptor:com.tectonic.ui:password
+          - urn:camel:group:credentials
+      region:
+        title: AWS Region
+        description: The AWS region to access.
+        type: string
+        enum: ["ap-south-1", "eu-south-1", "us-gov-east-1", "me-central-1", "ca-central-1", "eu-central-1", "us-iso-west-1", "us-west-1", "us-west-2", "af-south-1", "eu-north-1", "eu-west-3", "eu-west-2", "eu-west-1", "ap-northeast-3", "ap-northeast-2", "ap-northeast-1", "me-south-1", "sa-east-1", "ap-east-1", "cn-north-1", "us-gov-west-1", "ap-southeast-1", "ap-southeast-2", "us-iso-east-1", "ap-southeast-3", "us-east-1", "us-east-2", "cn-northwest-1", "us-isob-east-1", "aws-global", "a [...]
+      useDefaultCredentialsProvider:
+        title: Default Credentials Provider
+        description: If true, the CloudWatch client loads credentials through a default credentials provider. If false, it uses the basic authentication method (access key and secret key).
+        type: boolean
+        x-descriptors:
+          - 'urn:alm:descriptor:com.tectonic.ui:checkbox'
+        default: false
+      uriEndpointOverride:
+        title: Overwrite Endpoint URI
+        description: The overriding endpoint URI. To use this option, you must also select the `overrideEndpoint` option.
+        type: string
+      overrideEndpoint:
+        title: Endpoint Overwrite
+        description: Select this option to override the endpoint URI. To use this option, you must also provide a URI for the `uriEndpointOverride` option.
+        type: boolean
+        x-descriptors:
+          - 'urn:alm:descriptor:com.tectonic.ui:checkbox'
+        default: false
+  dependencies:
+    - "camel:core"
+    - "camel:aws2-cw"
+    - "camel:kamelet"
+  template:
+    from:
+      uri: kamelet:source
+      steps:
+        - choice:
+            when:
+              - simple: "${header[metric-name]}"
+                steps:
+                  - set-header:
+                      name: CamelAwsCwMetricName
+                      simple: "${header[metric-name]}"
+              - simple: "${header[ce-metricname]}"
+                steps:
+                  - set-header:
+                      name: CamelAwsCwMetricName
+                      simple: "${header[ce-metricname]}"
+        - choice:
+            when:
+              - simple: "${header[metric-value]}"
+                steps:
+                  - set-header:
+                      name: CamelAwsCwMetricValue
+                      simple: "${header[metric-value]}"
+              - simple: "${header[ce-metricvalue]}"
+                steps:
+                  - set-header:
+                      name: CamelAwsCwMetricValue
+                      simple: "${header[ce-metricvalue]}"
+        - choice:
+            when:
+              - simple: "${header[metric-unit]}"
+                steps:
+                  - set-header:
+                      name: CamelAwsCwMetricUnit
+                      simple: "${header[metric-unit]}"
+              - simple: "${header[ce-metricunit]}"
+                steps:
+                  - set-header:
+                      name: CamelAwsCwMetricUnit
+                      simple: "${header[ce-metricunit]}"
+        - choice:
+            when:
+              - simple: "${header[metric-timestamp]}"
+                steps:
+                  - set-header:
+                      name: CamelAwsCwMetricTimestamp
+                      simple: "${header[metric-timestamp]}"
+              - simple: "${header[ce-metrictimestamp]}"
+                steps:
+                  - set-header:
+                      name: CamelAwsCwMetricTimestamp
+                      simple: "${header[ce-metrictimestamp]}"
+        - choice:
+            when:
+              - simple: "${header[metric-dimension-name]}"
+                steps:
+                  - set-header:
+                      name: CamelAwsCwMetricDimensionName
+                      simple: "${header[metric-dimension-name]}"
+              - simple: "${header[ce-metricdimensionname]}"
+                steps:
+                  - set-header:
+                      name: CamelAwsCwMetricDimensionName
+                      simple: "${header[ce-metricdimensionname]}"
+        - choice:
+            when:
+              - simple: "${header[metric-dimension-value]}"
+                steps:
+                  - set-header:
+                      name: CamelAwsCwMetricDimensionValue
+                      simple: "${header[metric-dimension-value]}"
+              - simple: "${header[ce-metricdimensionvalue]}"
+                steps:
+                  - set-header:
+                      name: CamelAwsCwMetricDimensionValue
+                      simple: "${header[ce-metricdimensionvalue]}"
+        - to:
+            uri: "aws2-cw:{{cwNamespace}}"
+            parameters:
+              secretKey: "{{?secretKey}}"
+              accessKey: "{{?accessKey}}"
+              region: "{{region}}"
+              useDefaultCredentialsProvider: "{{useDefaultCredentialsProvider}}"
+              uriEndpointOverride: "{{?uriEndpointOverride}}"
+              overrideEndpoint: "{{overrideEndpoint}}"
\ No newline at end of file
diff --git a/karavan-designer/src/App.tsx b/karavan-designer/src/App.tsx
index 30196dab..af23ed39 100644
--- a/karavan-designer/src/App.tsx
+++ b/karavan-designer/src/App.tsx
@@ -82,7 +82,8 @@ class App extends React.Component<Props, State> {
             fetch("snippets/org.apache.camel.AggregationStrategy"),
             fetch("snippets/org.apache.camel.Processor"),
             // fetch("example/demo.camel.yaml")
-            fetch("example/aws-s3-cdc-source.kamelet.yaml")
+            fetch("example/aws-cloudwatch-sink.kamelet.yaml")
+            // fetch("example/aws-s3-cdc-source.kamelet.yaml")
             // fetch("components/supported-components.json"),
         ]).then(responses =>
             Promise.all(responses.map(response => response.text()))
diff --git a/karavan-designer/src/designer/route/DslConnections.tsx b/karavan-designer/src/designer/route/DslConnections.tsx
index 10338ae9..a636339e 100644
--- a/karavan-designer/src/designer/route/DslConnections.tsx
+++ b/karavan-designer/src/designer/route/DslConnections.tsx
@@ -63,6 +63,7 @@ export function DslConnections() {
             .filter(pos => ["FromDefinition"].includes(pos.step.dslName))
             .filter(pos => !TopologyUtils.isElementInternalComponent(pos.step))
             .filter(pos => !(pos.step.dslName === 'FromDefinition' && TopologyUtils.hasInternalUri(pos.step)))
+            .filter(pos => !(pos.step.dslName === 'FromDefinition' && (pos.step as any).uri === 'kamelet:source'))
             .sort((pos1: DslPosition, pos2: DslPosition) => {
                 const y1 = pos1.headerRect.y + pos1.headerRect.height / 2;
                 const y2 = pos2.headerRect.y + pos2.headerRect.height / 2;
diff --git a/karavan-designer/src/designer/route/DslElement.tsx b/karavan-designer/src/designer/route/DslElement.tsx
index c45dcbe4..9e4a8c70 100644
--- a/karavan-designer/src/designer/route/DslElement.tsx
+++ b/karavan-designer/src/designer/route/DslElement.tsx
@@ -42,7 +42,7 @@ interface Props {
 export function DslElement(props: Props) {
 
     const headerRef = React.useRef<HTMLDivElement>(null);
-    const {selectElement, moveElement, onShowDeleteConfirmation, openSelector, isKamelet} = useRouteDesignerHook();
+    const {selectElement, moveElement, onShowDeleteConfirmation, openSelector, isKamelet, isSourceKamelet, isActionKamelet} = useRouteDesignerHook();
 
     const [integration] = useIntegrationStore((s) => [s.integration, s.setIntegration], shallow)
 
@@ -244,6 +244,8 @@ export function DslElement(props: Props) {
     function getHeaderText(step: CamelElement): string {
         if (isKamelet() && step.dslName === 'ToDefinition' && (step as any).uri === 'kamelet:sink') {
             return "Sink";
+        } else if (isKamelet() && step.dslName === 'FromDefinition' && (step as any).uri === 'kamelet:source') {
+            return "Source";
         } else {
             return (step as any).description ? (step as any).description : CamelUi.getElementTitle(props.step);
         }
diff --git a/karavan-designer/src/designer/route/RouteDesigner.tsx b/karavan-designer/src/designer/route/RouteDesigner.tsx
index 215f334b..c57260db 100644
--- a/karavan-designer/src/designer/route/RouteDesigner.tsx
+++ b/karavan-designer/src/designer/route/RouteDesigner.tsx
@@ -40,7 +40,8 @@ import {DslElementMoveModal} from "./DslElementMoveModal";
 
 export function RouteDesigner() {
 
-    const {openSelector, createRouteConfiguration, onCommand, handleKeyDown, handleKeyUp, unselectElement} = useRouteDesignerHook();
+    const {openSelector, createRouteConfiguration, onCommand, handleKeyDown, handleKeyUp, unselectElement, onDslSelect,
+        isSourceKamelet, isActionKamelet, isKamelet, isSinkKamelet} = useRouteDesignerHook();
 
     const [integration] = useIntegrationStore((state) => [state.integration], shallow)
     const [showDeleteConfirmation, setPosition, width, height, top, left, hideLogDSL, showMoveConfirmation, setShowMoveConfirmation] =
@@ -104,6 +105,37 @@ export function RouteDesigner() {
         )
     }
 
+    function getGraphButtons() {
+        const routes = CamelUi.getRoutes(integration);
+        const showNewRoute = (isKamelet() && routes.length === 0) || !isKamelet();
+        const showNewRouteConfiguration = !isKamelet();
+        return (
+            <div className="add-flow">
+                {showNewRoute && <Button
+                    variant={routes.length === 0 ? "primary" : "secondary"}
+                    icon={<PlusIcon/>}
+                    onClick={e => {
+                        if (isSinkKamelet() || isActionKamelet()) {
+                            const dsl = CamelUi.getDslMetaModel('FromDefinition');
+                            dsl.uri = 'kamelet:source';
+                            onDslSelect(dsl, '', undefined);
+                        } else {
+                            openSelector(undefined, undefined)
+                        }
+                    }}
+                >
+                    Create route
+                </Button>}
+                {showNewRouteConfiguration && <Button
+                    variant="secondary"
+                    icon={<PlusIcon/>}
+                    onClick={e => createRouteConfiguration()}
+                >
+                    Create configuration
+                </Button>}
+            </div>
+        )
+    }
     function getGraph() {
         const routes = CamelUi.getRoutes(integration);
         const routeConfigurations = CamelUi.getRouteConfigurations(integration);
@@ -129,24 +161,7 @@ export function RouteDesigner() {
                                     step={route}
                                     parent={undefined}/>
                     ))}
-                    <div className="add-flow">
-                        <Button
-                            variant={routes.length === 0 ? "primary" : "secondary"}
-                            icon={<PlusIcon/>}
-                            onClick={e => {
-                                openSelector(undefined, undefined)
-                            }}
-                        >
-                            Create route
-                        </Button>
-                        <Button
-                            variant="secondary"
-                            icon={<PlusIcon/>}
-                            onClick={e => createRouteConfiguration()}
-                        >
-                            Create configuration
-                        </Button>
-                    </div>
+                    {getGraphButtons()}
                 </div>
             </div>)
     }
diff --git a/karavan-designer/src/designer/route/useRouteDesignerHook.tsx b/karavan-designer/src/designer/route/useRouteDesignerHook.tsx
index 5848acc9..0d97214d 100644
--- a/karavan-designer/src/designer/route/useRouteDesignerHook.tsx
+++ b/karavan-designer/src/designer/route/useRouteDesignerHook.tsx
@@ -19,7 +19,7 @@ import '../karavan.css';
 import {DslMetaModel} from "../utils/DslMetaModel";
 import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
 import {ChoiceDefinition, FromDefinition, LogDefinition, RouteConfigurationDefinition, RouteDefinition} from "karavan-core/lib/model/CamelDefinition";
-import {CamelElement} from "karavan-core/lib/model/IntegrationDefinition";
+import {CamelElement, MetadataLabels} from "karavan-core/lib/model/IntegrationDefinition";
 import {CamelDefinitionApiExt} from "karavan-core/lib/api/CamelDefinitionApiExt";
 import {CamelDefinitionApi} from "karavan-core/lib/api/CamelDefinitionApi";
 import {Command, EventBus} from "../utils/EventBus";
@@ -50,6 +50,30 @@ export function useRouteDesignerHook () {
         return integration.type === 'kamelet';
     }
 
+    function isSourceKamelet(): boolean {
+        if (isKamelet()){
+            const m: MetadataLabels | undefined = integration.metadata.labels;
+            return m !== undefined && m["camel.apache.org/kamelet.type"] === 'source';
+        }
+        return false;
+    }
+
+    function isSinkKamelet(): boolean {
+        if (isKamelet()){
+            const m: MetadataLabels | undefined = integration.metadata.labels;
+            return m !== undefined && m["camel.apache.org/kamelet.type"] === 'sink';
+        }
+        return false;
+    }
+
+    function isActionKamelet(): boolean {
+        if (isKamelet()){
+            const m: MetadataLabels | undefined = integration.metadata.labels;
+            return m !== undefined && m["camel.apache.org/kamelet.type"] === 'action';
+        }
+        return false;
+    }
+
     const onShowDeleteConfirmation = (id: string) => {
         let message: string;
         const uuidsToDelete:string [] = [id];
@@ -197,7 +221,7 @@ export function useRouteDesignerHook () {
         setSelectorTabIndex((parentId === undefined && parentDsl === undefined) ? 'kamelet' : 'eip');
     }
 
-    const onDslSelect = (dsl: DslMetaModel, parentId: string, position?: number | undefined) => {
+    function onDslSelect (dsl: DslMetaModel, parentId: string, position?: number | undefined) {
         switch (dsl.dsl) {
             case 'FromDefinition' :
                 const route = CamelDefinitionApi.createRouteDefinition({from: new FromDefinition({uri: dsl.uri})});
@@ -298,5 +322,6 @@ export function useRouteDesignerHook () {
     }
 
     return { deleteElement, selectElement, moveElement, onShowDeleteConfirmation, onDslSelect, openSelector,
-        createRouteConfiguration, onCommand, handleKeyDown, handleKeyUp, unselectElement, isKamelet}
+        createRouteConfiguration, onCommand, handleKeyDown, handleKeyUp, unselectElement, isKamelet, isSourceKamelet,
+        isActionKamelet, isSinkKamelet}
 }
\ No newline at end of file