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/02 19:19:31 UTC

[camel-karavan] branch main updated: Kamelets Definition Enum UI #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


The following commit(s) were added to refs/heads/main by this push:
     new 0135be74 Kamelets Definition Enum UI #315
0135be74 is described below

commit 0135be7402ec837f9ba38fb5c58b4bceeadb4e4a
Author: Marat Gubaidullin <ma...@talismancloud.io>
AuthorDate: Mon Oct 2 15:19:21 2023 -0400

    Kamelets Definition Enum UI #315
---
 .../src/core/model/IntegrationDefinition.ts        |   1 +
 .../public/example/aws-s3-cdc-source.kamelet.yaml  | 219 +++++++++++++++++++++
 karavan-designer/src/App.tsx                       |   4 +-
 .../kamelet/KameletDefinitionPropertyCard.tsx      | 125 +++++++++++-
 .../designer/kamelet/KameletDefinitionsPanel.tsx   |  35 +++-
 karavan-designer/src/designer/kamelet/kamelet.css  |   1 +
 6 files changed, 372 insertions(+), 13 deletions(-)

diff --git a/karavan-core/src/core/model/IntegrationDefinition.ts b/karavan-core/src/core/model/IntegrationDefinition.ts
index 696e2da1..974f8e5e 100644
--- a/karavan-core/src/core/model/IntegrationDefinition.ts
+++ b/karavan-core/src/core/model/IntegrationDefinition.ts
@@ -25,6 +25,7 @@ export class DefinitionProperty {
     example?: any;
     format?: string;
     "x-descriptors"?: string[];
+    enum?: string[];
 
     public constructor(init?: Partial<DefinitionProperty>) {
         Object.assign(this, init);
diff --git a/karavan-designer/public/example/aws-s3-cdc-source.kamelet.yaml b/karavan-designer/public/example/aws-s3-cdc-source.kamelet.yaml
new file mode 100644
index 00000000..c093d002
--- /dev/null
+++ b/karavan-designer/public/example/aws-s3-cdc-source.kamelet.yaml
@@ -0,0 +1,219 @@
+apiVersion: camel.apache.org/v1
+kind: Kamelet
+metadata:
+  name: aws-s3-cdc-source
+  annotations:
+    camel.apache.org/kamelet.support.level: Preview
+    camel.apache.org/catalog.version: "4.1.0-SNAPSHOT"
+    camel.apache.org/kamelet.icon: >-
+      data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFRE [...]
+    camel.apache.org/provider: Apache Software Foundation
+    camel.apache.org/kamelet.group: AWS S3 CDC
+    camel.apache.org/kamelet.namespace: "AWS"
+    camel.apache.org/keda.type: aws-s3-cdc-queue
+  labels:
+    camel.apache.org/kamelet.type: source
+spec:
+  definition:
+    title: AWS S3 CDC Source
+    description: >-
+      Receive data from AWS SQS subscribed to Eventbridge Bus reporting events related to an S3 bucket or multiple buckets.
+
+      Access Key/Secret Key are the basic method for authenticating to the AWS
+      SQS Service.
+
+      To use this Kamelet you'll need to set up Eventbridge on your bucket and subscribe Eventbridge bus to an SQS Queue.
+      
+      For doing this you'll need to enable Evenbridge notification on your bucket and creating a rule on Eventbridge console related to all the events on S3 bucket and pointing to the SQS Queue specified as parameter in this Kamelet.
+    required:
+      - accessKey
+      - secretKey
+      - queueNameOrArn
+      - region
+    type: object
+    properties:
+      queueNameOrArn:
+        title: Queue Name
+        description: The SQS Queue Name or ARN
+        type: string
+      deleteAfterRead:
+        title: Auto-delete Messages
+        description: Delete messages after consuming them
+        type: boolean
+        x-descriptors:
+          - 'urn:alm:descriptor:com.tectonic.ui:checkbox'
+        default: true
+      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'
+          - 'urn:keda:authentication:awsAccessKeyID'
+          - 'urn:keda:required'
+      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'
+          - 'urn:keda:authentication:awsSecretAccessKey'
+          - 'urn:keda:required'
+      region:
+        title: AWS Region
+        description: The AWS region to access.
+        type: string
+        x-descriptors:
+          - 'urn:keda:metadata:awsRegion'
+          - 'urn:keda:required'
+        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
+          - aws-cn-global
+          - aws-us-gov-global
+          - aws-iso-global
+          - aws-iso-b-global
+      autoCreateQueue:
+        title: Autocreate Queue
+        description: Setting the autocreation of the SQS queue.
+        type: boolean
+        x-descriptors:
+          - 'urn:alm:descriptor:com.tectonic.ui:checkbox'
+        default: false
+      amazonAWSHost:
+        title: AWS Host
+        description: The hostname of the Amazon AWS cloud.
+        type: string
+        default: amazonaws.com
+      protocol:
+        title: Protocol
+        description: The underlying protocol used to communicate with SQS
+        type: string
+        example: http or https
+        default: https
+      queueURL:
+        title: Queue URL
+        description: The full SQS Queue URL (required if using KEDA)
+        type: string
+        x-descriptors:
+          - 'urn:keda:metadata:queueURL'
+          - 'urn:keda:required'
+      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
+      delay:
+        title: Delay
+        description: The number of milliseconds before the next poll of the selected stream
+        type: integer
+        default: 500
+      greedy:
+        title: Greedy Scheduler
+        description: >-
+          If greedy is enabled, then the polling will happen immediately again,
+          if the previous run polled 1 or more messages.
+        type: boolean
+        x-descriptors:
+          - 'urn:alm:descriptor:com.tectonic.ui:checkbox'
+        default: false
+      getObject:
+        title: Greedy Object in Bucket
+        description: >-
+          If getObject is enabled, then the file created in the bucket will be
+          get and returned as body, if not only the event will returned as body.
+        type: boolean
+        x-descriptors:
+          - 'urn:alm:descriptor:com.tectonic.ui:checkbox'
+        default: false
+  dependencies:
+    - 'camel:core'
+    - 'camel:aws2-sqs'
+    - 'camel:aws2-s3'
+    - 'camel:jsonpath'
+    - 'camel:kamelet'
+    - 'camel:jackson'
+  template:
+    from:
+      uri: 'aws2-sqs:{{queueNameOrArn}}'
+      parameters:
+        autoCreateQueue: '{{autoCreateQueue}}'
+        secretKey: '{{?secretKey}}'
+        accessKey: '{{?accessKey}}'
+        region: '{{region}}'
+        deleteAfterRead: '{{deleteAfterRead}}'
+        amazonAWSHost: '{{?amazonAWSHost}}'
+        protocol: '{{?protocol}}'
+        uriEndpointOverride: '{{?uriEndpointOverride}}'
+        overrideEndpoint: '{{overrideEndpoint}}'
+        delay: '{{delay}}'
+        greedy: '{{greedy}}'
+      steps:
+        - choice:
+            precondition: true
+            when:
+              - simple: '${properties:getObject:true}'
+                steps:
+                  - unmarshal:
+                      json:
+                        library: Jackson
+                        unmarshalType: com.fasterxml.jackson.databind.JsonNode
+                  - set-property:
+                      name: s3-event-name
+                      jsonpath: $.detail.reason
+                  - choice:
+                      when:
+                        - simple: '${exchangeProperty.s3-event-name} == "PutObject"'
+                          steps:
+                            - set-property:
+                                name: aws-s3-name
+                                jsonpath: $.detail.object.key
+                            - set-property:
+                                name: aws-s3-bucket
+                                jsonpath: $.detail.bucket.name
+                            - toD: >-
+                                aws2-s3:${exchangeProperty.aws-s3-bucket}?accessKey={{accessKey}}&secretKey={{secretKey}}&region={{region}}&operation=getObject&keyName=${exchangeProperty.aws-s3-name}
+        - to: 'kamelet:sink'
diff --git a/karavan-designer/src/App.tsx b/karavan-designer/src/App.tsx
index dcea7fc1..f23eb43d 100644
--- a/karavan-designer/src/App.tsx
+++ b/karavan-designer/src/App.tsx
@@ -70,7 +70,7 @@ 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/postgresql-source.kamelet.yaml")
+            fetch("example/aws-s3-cdc-source.kamelet.yaml")
             // fetch("components/supported-components.json"),
         ]).then(responses =>
             Promise.all(responses.map(response => response.text()))
@@ -90,7 +90,7 @@ class App extends React.Component<Props, State> {
 
             if (data[4]) {
                 // this.setState({yaml: data[4], name: "demo.camel.yaml"})
-                this.setState({yaml: data[4], name: "postgresql-source.kamelet.yaml"})
+                this.setState({yaml: data[4], name: "aws-s3-cdc-source.kamelet.yaml"})
             }
 
             if (data[5]) {
diff --git a/karavan-designer/src/designer/kamelet/KameletDefinitionPropertyCard.tsx b/karavan-designer/src/designer/kamelet/KameletDefinitionPropertyCard.tsx
index d8933df4..47c85e18 100644
--- a/karavan-designer/src/designer/kamelet/KameletDefinitionPropertyCard.tsx
+++ b/karavan-designer/src/designer/kamelet/KameletDefinitionPropertyCard.tsx
@@ -19,10 +19,18 @@ import {
     Button,
     Card,
     CardBody,
-    CardTitle, Flex, FlexItem,
-    FormGroup, FormSelect, FormSelectOption,
+    CardTitle,
+    Flex,
+    FlexItem,
+    FormGroup,
+    FormSelect,
+    FormSelectOption,
     Grid,
-    GridItem, Label, Modal, Switch,
+    GridItem,
+    Label,
+    LabelGroup,
+    Modal,
+    Switch,
     TextInput,
 } from '@patternfly/react-core';
 import '../karavan.css';
@@ -30,6 +38,8 @@ import './kamelet.css';
 import {useIntegrationStore} from "../DesignerStore";
 import {shallow} from "zustand/shallow";
 import {DefinitionProperty} from "karavan-core/lib/model/IntegrationDefinition";
+import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
+import AddIcon from "@patternfly/react-icons/dist/js/icons/plus-circle-icon";
 
 interface Props {
     index: number
@@ -45,7 +55,7 @@ export function KameletDefinitionPropertyCard(props: Props) {
     const key = props.propKey;
     const required = integration.spec.definition?.required || [];
 
-    function setPropertyValue(field: string, value: string) {
+    function setPropertyValue(field: string, value: any) {
         if (integration.spec.definition?.properties) {
             (integration.spec.definition?.properties as any)[key][field] = value;
             setIntegration(integration, true);
@@ -83,8 +93,9 @@ export function KameletDefinitionPropertyCard(props: Props) {
                         aria-label="FormSelect Input"
                         ouiaId="BasicFormSelect"
                     >
-                        {['string', 'number', 'boolean'].map((option, index) => (
-                            <FormSelectOption key={option} isDisabled={false} id={key + field} name={key + field} value={option} label={option} />
+                        {['string', 'number', 'integer', 'boolean'].map((option, index) => (
+                            <FormSelectOption key={option} isDisabled={false} id={key + field} name={key + field}
+                                              value={option} label={option}/>
                         ))}
                     </FormSelect>
                 </FormGroup>
@@ -92,9 +103,106 @@ export function KameletDefinitionPropertyCard(props: Props) {
         )
     }
 
+    function sortEnum(source: string, dest: string) {
+        const i = CamelUtil.cloneIntegration(integration);
+        if (i.spec.definition && integration.spec.definition?.properties[key]) {
+            const enums: string [] = i.spec.definition.properties[key].enum;
+            console.log(enums)
+            if (enums && Array.isArray(enums)) {
+                console.log("isArray")
+                const from = enums.findIndex(e => source);
+                const to = enums.findIndex(e => dest);
+                if (from > -1 && to > -1) {
+                    console.log("exchange");
+                    [enums[from], enums[to]] = [enums[to], enums[from]];
+                    i.spec.definition.properties[key].enum = enums;
+                    console.log("i.spec.definition.properties[key].enum", i.spec.definition.properties[key].enum);
+                    setIntegration(i, true);
+                }
+            }
+        }
+    }
+
+    function addEnum() {
+        const i = CamelUtil.cloneIntegration(integration);
+        if (i.spec.definition && integration.spec.definition?.properties[key]) {
+            let enums: string [] = i.spec.definition.properties[key].enum;
+            if (enums && Array.isArray(enums)) {
+                enums.push("enum")
+            } else {
+                enums = ['enum'];
+            }
+            i.spec.definition.properties[key].enum = enums;
+            setIntegration(i, true);
+        }
+    }
+
+    function deleteEnum(val: string) {
+        const enumVal = getPropertyValue('enum');
+        const i = CamelUtil.cloneIntegration(integration);
+        if (enumVal && Array.isArray(enumVal) && i.spec.definition) {
+            const enums: string[] = [...enumVal];
+            setPropertyValue('enum', enums.filter(e => e !== val));
+        }
+    }
+
+    function renameEnum(index: number, newVal: string) {
+        const enumVal = getPropertyValue('enum');
+        const i = CamelUtil.cloneIntegration(integration);
+        if (enumVal && Array.isArray(enumVal) && i.spec.definition) {
+            const enums: string[] = [...enumVal];
+            enums[index] = newVal;
+            setPropertyValue('enum', enums);
+        }
+    }
+
+    function getPropertyEnumField(field: string, label: string, isRequired: boolean, span: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12) {
+        const enumVal = getPropertyValue(field);
+        return (
+            <GridItem span={span}>
+                <FormGroup fieldId={key + field} isRequired={isRequired}>
+                    <LabelGroup
+                        categoryName={label}
+                        numLabels={enumVal?.length || 0}
+                        isEditable
+                        addLabelControl={
+                            <Button variant="link" icon={<AddIcon/>} onClick={event => addEnum()}>
+                                Add
+                            </Button>
+                        }
+                    >
+                        {enumVal && enumVal.map((val: string, index: number) => (
+                            <Label
+                                key={val}
+                                id={val}
+                                color="blue"
+                                isEditable
+                                onClose={() => deleteEnum(val)}
+                                onEditCancel={(_event, prevText) => {}}
+                                onEditComplete={(event, newText) => {
+                                    if (event.type === 'mousedown') {
+                                        renameEnum(index, val)
+                                    } else if (event.type === 'keydown' && (event as KeyboardEvent).key === 'Tab') {
+                                        renameEnum(index, newText)
+                                    } else if (event.type === 'keydown' && (event as KeyboardEvent).key === 'Enter') {
+                                        renameEnum(index, newText)
+                                    } else {
+                                        renameEnum(index, val)
+                                    }
+                                }}
+                            >
+                                {val}
+                            </Label>
+                        ))}
+                    </LabelGroup>
+                </FormGroup>
+            </GridItem>
+        )
+    }
+
     function renameProperty(newKey: string) {
         const oldKey = key;
-        newKey = newKey.replace(/[\W_]+/g,'');
+        newKey = newKey.replace(/[\W_]+/g, '');
         if (oldKey !== newKey) {
             if (integration.spec.definition?.properties) {
                 const o = (integration.spec.definition?.properties as any)
@@ -138,7 +246,6 @@ export function KameletDefinitionPropertyCard(props: Props) {
     }
 
     function setRequired(checked: boolean) {
-        console.log(required, key)
         const newRequired = [...required];
         if (checked && !newRequired.includes(key)) {
             newRequired.push(key);
@@ -146,7 +253,6 @@ export function KameletDefinitionPropertyCard(props: Props) {
             const index = newRequired.findIndex(r => r === key);
             newRequired.splice(index, 1);
         }
-        // console.log(newRequired)
         if (integration.spec.definition?.required) {
             integration.spec.definition.required.length = 0;
             integration.spec.definition.required.push(...newRequired)
@@ -212,6 +318,7 @@ export function KameletDefinitionPropertyCard(props: Props) {
                     {getPropertyField("format", "Format", false, 3)}
                     {getPropertyField("example", "Example", false, 6)}
                     {getPropertyField("default", "Default", false, 3)}
+                    {getPropertyValue('type') === 'string' && getPropertyEnumField("enum", "Enum", true, 12)}
                     {/*{getPropertyField("x-descriptors", "Descriptors", false, 12)}*/}
                 </Grid>
             </CardBody>
diff --git a/karavan-designer/src/designer/kamelet/KameletDefinitionsPanel.tsx b/karavan-designer/src/designer/kamelet/KameletDefinitionsPanel.tsx
index 009a54ac..0eb3f521 100644
--- a/karavan-designer/src/designer/kamelet/KameletDefinitionsPanel.tsx
+++ b/karavan-designer/src/designer/kamelet/KameletDefinitionsPanel.tsx
@@ -19,7 +19,9 @@ import {
     Button,
     Card,
     CardBody,
-    CardTitle, Flex, FlexItem,
+    CardTitle,
+    Flex,
+    FlexItem,
     Form,
     FormGroup,
     Grid,
@@ -32,6 +34,9 @@ import {useIntegrationStore} from "../DesignerStore";
 import {shallow} from "zustand/shallow";
 import AddIcon from "@patternfly/react-icons/dist/js/icons/plus-circle-icon";
 import {KameletDefinitionPropertyCard} from "./KameletDefinitionPropertyCard";
+import {CamelUtil} from "karavan-core/lib/api/CamelUtil";
+import {DefinitionProperty} from "karavan-core/lib/model/IntegrationDefinition";
+import {Simulate} from "react-dom/test-utils";
 
 export function KameletDefinitionsPanel() {
 
@@ -66,6 +71,30 @@ export function KameletDefinitionsPanel() {
     }
 
     const properties = integration.spec.definition?.properties ? Object.keys(integration.spec.definition?.properties) : [];
+
+    function addNewProperty() {
+        const i = CamelUtil.cloneIntegration(integration);
+        if (i.spec.definition && integration.spec.definition?.properties) {
+            const propertyName = generatePropertyName();
+            i.spec.definition.properties = Object.assign({[propertyName]: new DefinitionProperty()}, integration.spec.definition.properties);
+            setIntegration(i, true);
+        }
+    }
+
+    function generatePropertyName(count: number = 0): string {
+        const prefix = 'property';
+        const propName = 'property' + count;
+        if (integration.spec.definition?.properties) {
+            const keys = Object.keys(integration.spec.definition?.properties);
+            if (keys.includes(propName)) {
+                return generatePropertyName(count + 1);
+            } else {
+                return propName;
+            }
+        }
+        return prefix;
+    }
+
     return (
         <>
             <Card isCompact ouiaId="DefinitionsCard">
@@ -86,7 +115,9 @@ export function KameletDefinitionsPanel() {
                     <Flex>
                         <FlexItem>Properties</FlexItem>
                         <FlexItem align={{default: "alignRight"}}>
-                            <Button variant={"link"} icon={<AddIcon/>}>Add property</Button>
+                            <Button variant={"link"} icon={<AddIcon/>} onClick={event => addNewProperty()}>
+                                Add property
+                            </Button>
                         </FlexItem>
                     </Flex>
                 </CardTitle>
diff --git a/karavan-designer/src/designer/kamelet/kamelet.css b/karavan-designer/src/designer/kamelet/kamelet.css
index bbdf28a9..c72acad2 100644
--- a/karavan-designer/src/designer/kamelet/kamelet.css
+++ b/karavan-designer/src/designer/kamelet/kamelet.css
@@ -23,6 +23,7 @@
     padding-bottom: 106px;
 }
 
+.karavan .kamelet-designer .pf-v5-c-drawer__content,
 .karavan .kamelet-designer .main {
     background-color: var(--pf-v5-global--BackgroundColor--light-300);
 }