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}}®ion={{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);
}