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